安卓RenderEffect原理

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情

安卓的RenderEffect可以给View添加一些渲染特效,例如模糊效果等。

RenderEffect定义:(frameworks/base/graphics/java/android/graphics/RenderEffect.java)

/**
 * Intermediate rendering step used to render drawing commands with a corresponding
 * visual effect. A {@link RenderEffect} can be configured on a {@link RenderNode} through
 * {@link RenderNode#setRenderEffect(RenderEffect)} and will be applied when drawn through
 * {@link Canvas#drawRenderNode(RenderNode)}.
 * Additionally a {@link RenderEffect} can be applied to a View's backing RenderNode through
 * {@link android.view.View#setRenderEffect(RenderEffect)}
 */
public final class RenderEffect {
    private static class RenderEffectHolder {
        public static final NativeAllocationRegistry RENDER_EFFECT_REGISTRY =
                NativeAllocationRegistry.createMalloced(
                        RenderEffect.class.getClassLoader(), nativeGetFinalizer());
    }

    private final long mNativeRenderEffect;

    /* only constructed from static factory methods */
    private RenderEffect(long nativeRenderEffect) {
        mNativeRenderEffect = nativeRenderEffect;
        RenderEffectHolder.RENDER_EFFECT_REGISTRY.registerNativeAllocation(
                this, mNativeRenderEffect);
    }

    /**
     * Obtain the pointer to the underlying RenderEffect to be configured
     * on a RenderNode object via {@link RenderNode#setRenderEffect(RenderEffect)}
     */
    /* package */ long getNativeInstance() {
        return mNativeRenderEffect;
    }
}

RenderEffect只有一个成员变量mNativeRenderEffect,保存了一个C++层的指针。所以RenderEffect的实现原理基本在C++层。

接下来本文以给View添加模糊效果为例分析RenderEffect的原理。

给View添加模糊效果

安卓的RenderEffect可以给View添加模糊效果,添加方法:

RenderEffect renderEffect = RenderEffect.createBlurEffect(
        radiusX, radiusY, Shader.TileMode.CLAMP);
view.setRenderEffect(renderEffect);

首先创建了RenderEffect对象,然后调用View.setRenderEffect传入刚才创建的RenderEffect对象,就完成了给View添加模糊效果。

创建RenderEffect

RenderEffect.createBlurEffect方法:(frameworks/base/graphics/java/android/graphics/RenderEffect.java)

    /**
     * Create a {@link RenderEffect} that blurs the contents of the
     * {@link android.graphics.RenderNode} that this RenderEffect is installed on with the
     * specified radius along the x and y axis.
     * @param radiusX Radius of blur along the X axis
     * @param radiusY Radius of blur along the Y axis
     * @param edgeTreatment Policy for how to blur content near edges of the blur kernel
     */
    @NonNull
    public static RenderEffect createBlurEffect(
            float radiusX,
            float radiusY,
            @NonNull TileMode edgeTreatment
    ) {
        return new RenderEffect(
                nativeCreateBlurEffect(
                        radiusX,
                        radiusY,
                        0,
                        edgeTreatment.nativeInt
                )
            );
    }

主要是使用了nativeCreateBlurEffect方法创建C++对象:(frameworks/base/libs/hwui/jni/RenderEffect.cpp)

static jlong createBlurEffect(JNIEnv* env , jobject, jfloat radiusX,
        jfloat radiusY, jlong inputFilterHandle, jint edgeTreatment) {
    auto* inputImageFilter = reinterpret_cast<SkImageFilter*>(inputFilterHandle);
    sk_sp<SkImageFilter> blurFilter =
            SkImageFilters::Blur(
                    Blur::convertRadiusToSigma(radiusX),
                    Blur::convertRadiusToSigma(radiusY),
                    static_cast<SkTileMode>(edgeTreatment),
                    sk_ref_sp(inputImageFilter),
                    nullptr);
    return reinterpret_cast<jlong>(blurFilter.release());
}

这里主要是调用SkImageFilters::Blur创建了一个SkBlurImageFilter对象:(external/skia/src/effects/imagefilters/SkBlurImageFilter.cpp)

sk_sp<SkImageFilter> SkImageFilters::Blur(
        SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode, sk_sp<SkImageFilter> input,
        const CropRect& cropRect) {
    if (!SkScalarsAreFinite(sigmaX, sigmaY) || sigmaX < 0.f || sigmaY < 0.f) {
        // Non-finite or negative sigmas are error conditions. We allow 0 sigma for X and/or Y
        // for 1D blurs; onFilterImage() will detect when no visible blurring would occur based on
        // the Context mapping.
        return nullptr;
    }

    // Temporarily allow tiling with no crop rect
    if (tileMode != SkTileMode::kDecal && !cropRect) {
        return sk_make_sp<SkBlurImageFilter>(SkSize{sigmaX, sigmaY}, tileMode, std::move(input));
    }

    // The 'tileMode' behavior is not well-defined if there is no crop. We only apply it if
    // there is a provided 'cropRect'.
    sk_sp<SkImageFilter> filter = std::move(input);
    if (tileMode != SkTileMode::kDecal && cropRect) {
        // Historically the input image was restricted to the cropRect when tiling was not
        // kDecal, so that the kernel evaluated the tiled edge conditions, while a kDecal crop
        // only affected the output.
        filter = SkImageFilters::Crop(*cropRect, tileMode, std::move(filter));
    }

    filter = sk_make_sp<SkBlurImageFilter>(SkSize{sigmaX, sigmaY}, std::move(filter));
    if (cropRect) {
        // But regardless of the tileMode, the output is always decal cropped
        filter = SkImageFilters::Crop(*cropRect, SkTileMode::kDecal, std::move(filter));
    }
    return filter;
}

小结

创建java的RenderEffect实际上是创建了一个C++的SkImageFilter对象。

给View设置RenderEffect

View.setRenderEffect方法:(frameworks/base/core/java/android/view/View.java)

    /**
     * Configure the {@link android.graphics.RenderEffect} to apply to this View.
     * This will apply a visual effect to the results of the View before it is drawn. For example if
     * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, Shader.TileMode)}
     * is provided, the contents will be drawn in a separate layer, then this layer will be blurred
     * when this View is drawn.
     * @param renderEffect to be applied to the View. Passing null clears the previously configured
     *                     {@link RenderEffect}
     */
    public void setRenderEffect(@Nullable RenderEffect renderEffect) {
        if (mRenderNode.setRenderEffect(renderEffect)) {
            invalidateViewProperty(true, true);
        }
    }

主要是调用了RenderNode.setRenderEffect方法:(frameworks/base/graphics/java/android/graphics/RenderNode.java)

    /**
     * Configure the {@link android.graphics.RenderEffect} to apply to this RenderNode. This
     * will apply a visual effect to the end result of the contents of this RenderNode before
     * it is drawn into the destination. For example if
     * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, Shader.TileMode)}
     * is provided, the contents will be drawn in a separate layer, then this layer will
     * be blurred when this RenderNode is drawn into the destination.
     * @param renderEffect to be applied to the RenderNode. Passing null clears all previously
     *          configured RenderEffects
     * @return True if the value changed, false if the new value was the same as the previous value.
     */
    public boolean setRenderEffect(@Nullable RenderEffect renderEffect) {
        return nSetRenderEffect(mNativeRenderNode,
                renderEffect != null ? renderEffect.getNativeInstance() : 0);
    }

这里调用了C++层的setRenderEffect方法:

static jboolean android_view_RenderNode_setRenderEffect(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr,
        jlong renderEffectPtr) {
    SkImageFilter* imageFilter = reinterpret_cast<SkImageFilter*>(renderEffectPtr);
    return SET_AND_DIRTY(mutateLayerProperties().setImageFilter, imageFilter, RenderNode::GENERIC);
}

RenderNode有一个类型为RenderProperties的成员变量mStagingProperties,

RenderProperties有一个类型为LayerProperties的成员变量mLayerProperties,

LayerProperties有一个类型为sk_sp的成员变量mImageFilter,

这里将imageFilter设置到了mImageFilter上面:(frameworks/base/libs/hwui/RenderProperties.cpp)

bool LayerProperties::setImageFilter(SkImageFilter* imageFilter) {
    if (mImageFilter.get() == imageFilter) return false;
    mImageFilter = sk_ref_sp(imageFilter);
    return true;
}

小结

给View设置RenderEffect实际上是将SkImageFilter设置到了View.mRenderNode.mStagingProperties.mLayerProperties.mImageFilter上面。

录制RenderNode

在UI线程录制绘制操作的时候,会遍历整个View树。父View通过drawRenderNode方法录制一个子View的renderNode为子节点:(frameworks/base/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp)

void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
    // Record the child node. Drawable dtor will be invoked when mChildNodes deque is cleared.
    mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
    auto& renderNodeDrawable = mDisplayList->mChildNodes.back();
    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
        // 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
    if (renderNode->stagingProperties().isProjectionReceiver()) {
        mDisplayList->mProjectionReceiver = &renderNodeDrawable;
    }
}

这里mDisplayList->mChildNodes的类型为std::deque,通过emplace_back方法调用了RenderNodeDrawable的构造器,构建了mComposeLayer为true的子RenderNodeDrawable。

再通过drawDrawable方法去调用RecordingCanvas的onDrawDrawable方法,添加这个RenderNodeDrawable到DisplayListData中。

RenderNodeDrawable

RenderNodeDrawable的mComposeLayer成员变量定义如下:(frameworks/base/libs/hwui/pipeline/skia/RenderNodeDrawable.h)

    /**
     * If mRenderNode's layer type is RenderLayer this flag determines whether we
     * should draw into the contents of the layer or compose the existing contents
     * of the layer into the canvas.
     */
    const bool mComposeLayer;

mComposeLayer如果为false,则绘制这个layer的内容,如果为true,则合成已存在的内容到canvas上。

mComposeLayer在RenderNodeDrawable的构造器中赋值,并且默认值为true:(frameworks/base/libs/hwui/pipeline/skia/RenderNodeDrawable.h)

    /**
     * Creates a new RenderNodeDrawable backed by a render node.
     *
     * @param node that has to be drawn
     * @param canvas is a recording canvas used to extract its matrix
     * @param composeLayer if the node's layer type is RenderLayer this flag determines whether
     *      we should draw into the contents of the layer or compose the existing contents of the
     *      layer into the canvas.
     */
    explicit RenderNodeDrawable(RenderNode* node, SkCanvas* canvas, bool composeLayer = true,
                                bool inReorderingSection = false);

RenderNodeDrawable会随着mComposeLayer的值不同而在绘制时有不同的行为,所以每次构建RenderNodeDrawable的时候可以关注下其mComposeLayer的值是什么。

小结

父View录制绘制子View时,使用SkiaRecordingCanvas::drawRenderNode方法,将子View的renderNode装在了一个mComposeLayer为true的RenderNodeDrawable中,存到了SkiaRecordingCanvas.mDisplayList.mChildNodes里面。

创建layer的SkSurface

View的绘制操作在UI线程录制完成后,再切换到RenderThread线程,首先执行prepareTree操作,在prepareTree中将有RenderEffect的RenderNode视为layer,创建独立的SkSurface。

prepareTree调用栈

RenderNode::prepareTreeImpl

RenderNode::prepareTree

CanvasContext::prepareTree

DrawFrameTask::syncFrameState

DrawFrameTask::run

创建SkSurface

绘制前hwui会调用RenderNode的prepareTreeImpl方法:(frameworks/base/libs/hwui/RenderNode.cpp)

/**
 * Traverse down the the draw tree to prepare for a frame.
 *
 * MODE_FULL = UI Thread-driven (thus properties must be synced), otherwise RT driven
 *
 * While traversing down the tree, functorsNeedLayer flag is set to true if anything that uses the
 * stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer.
 */
void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
    ...
    pushLayerUpdate(info);
    ...
}

调用了RenderNode的pushLayerUpdate方法:(frameworks/base/libs/hwui/RenderNode.cpp)

void RenderNode::pushLayerUpdate(TreeInfo& info) {
#ifdef __ANDROID__ // Layoutlib does not support CanvasContext and Layers
    LayerType layerType = properties().effectiveLayerType();
    // If we are not a layer OR we cannot be rendered (eg, view was detached)
    // we need to destroy any Layers we may have had previously
    if (CC_LIKELY(layerType != LayerType::RenderLayer) || CC_UNLIKELY(!isRenderable()) ||
        CC_UNLIKELY(properties().getWidth() == 0) || CC_UNLIKELY(properties().getHeight() == 0) ||
        CC_UNLIKELY(!properties().fitsOnLayer())) {
        if (CC_UNLIKELY(hasLayer())) {
            this->setLayerSurface(nullptr);
        }
        return;
    }

    if (info.canvasContext.createOrUpdateLayer(this, *info.damageAccumulator, info.errorHandler)) {
        damageSelf(info);
    }

    if (!hasLayer()) {
        return;
    }

    SkRect dirty;
    info.damageAccumulator->peekAtDirty(&dirty);
    info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty);
    if (!dirty.isEmpty()) {
      mStretchMask.markDirty();
    }

    // There might be prefetched layers that need to be accounted for.
    // That might be us, so tell CanvasContext that this layer is in the
    // tree and should not be destroyed.
    info.canvasContext.markLayerInUse(this);
#endif
}

这里首先调用RenderProperties的effectiveLayerType方法判断这个RenderNode是否是一个layer:(frameworks/base/libs/hwui/RenderProperties.h)

    bool fitsOnLayer() const {
        const DeviceInfo* deviceInfo = DeviceInfo::get();
        return mPrimitiveFields.mWidth <= deviceInfo->maxTextureSize() &&
               mPrimitiveFields.mHeight <= deviceInfo->maxTextureSize();
    }

    bool promotedToLayer() const {
        return mLayerProperties.mType == LayerType::None && fitsOnLayer() &&
               (mComputedFields.mNeedLayerForFunctors || mLayerProperties.mImageFilter != nullptr ||
                mLayerProperties.getStretchEffect().requiresLayer() ||
                (!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 &&
                 mPrimitiveFields.mHasOverlappingRendering));
    }

    LayerType effectiveLayerType() const {
        return CC_UNLIKELY(promotedToLayer()) ? LayerType::RenderLayer : mLayerProperties.mType;
    }

由于我们之前设置了mImageFilter,mLayerProperties.mImageFilter != nullptr是true,导致promotedToLayer是true,从而导致effectiveLayerType方法返回LayerType::RenderLayer。

因此这个RenderNode被认为是layer,继续去调用CanvasContext的createOrUpdateLayer方法:(frameworks/base/libs/hwui/renderthread/CanvasContext.h)

    /**
     * Update or create a layer specific for the provided RenderNode. The layer
     * attached to the node will be specific to the RenderPipeline used by this
     * context
     *
     *  @return true if the layer has been created or updated
     */
    bool createOrUpdateLayer(RenderNode* node, const DamageAccumulator& dmgAccumulator,
                             ErrorHandler* errorHandler) {
        return mRenderPipeline->createOrUpdateLayer(node, dmgAccumulator, errorHandler);
    }

SkiaPipeline的createOrUpdateLayer方法:(frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp)

bool SkiaPipeline::createOrUpdateLayer(RenderNode* node, const DamageAccumulator& damageAccumulator,
                                       ErrorHandler* errorHandler) {
    // compute the size of the surface (i.e. texture) to be allocated for this layer
    const int surfaceWidth = ceilf(node->getWidth() / float(LAYER_SIZE)) * LAYER_SIZE;
    const int surfaceHeight = ceilf(node->getHeight() / float(LAYER_SIZE)) * LAYER_SIZE;

    SkSurface* layer = node->getLayerSurface();
    if (!layer || layer->width() != surfaceWidth || layer->height() != surfaceHeight) {
        SkImageInfo info;
        info = SkImageInfo::Make(surfaceWidth, surfaceHeight, getSurfaceColorType(),
                                 kPremul_SkAlphaType, getSurfaceColorSpace());
        SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
        SkASSERT(mRenderThread.getGrContext() != nullptr);
        node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
                                                       skgpu::Budgeted::kYes, info, 0,
                                                       this->getSurfaceOrigin(), &props));
        if (node->getLayerSurface()) {
            // update the transform in window of the layer to reset its origin wrt light source
            // position
            Matrix4 windowTransform;
            damageAccumulator.computeCurrentTransform(&windowTransform);
            node->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
        } else {
            String8 cachesOutput;
            mRenderThread.cacheManager().dumpMemoryUsage(cachesOutput,
                                                         &mRenderThread.renderState());
            ALOGE("%s", cachesOutput.c_str());
            if (errorHandler) {
                std::ostringstream err;
                err << "Unable to create layer for " << node->getName();
                const int maxTextureSize = DeviceInfo::get()->maxTextureSize();
                err << ", size " << info.width() << "x" << info.height() << " max size "
                    << maxTextureSize << " color type " << (int)info.colorType() << " has context "
                    << (int)(mRenderThread.getGrContext() != nullptr);
                errorHandler->onError(err.str());
            }
        }
        return true;
    }
    return false;
}

这里最终创建了这个RenderNode的LayerSurface。

如果一个View有是layer的RenderNode,这个View又被称为离屏View

LayerSurface创建完成后,在RenderNode的pushLayerUpdate方法后面还通过info.layerUpdateQueue->enqueueLayerWithDamage(this, dirty)将这个RenderNode添加到了layerUpdateQueue里面。

小结

RenderNode有ImageFilter之后,可以被看成是一个单独的layer,会在prepareTree阶段创建自己的SkSurface,并保存在layerUpdateQueue中。

绘制RenderNode

在RenderThread线程执行完prepareTree之后,就要开始绘制RenderNode了。

绘制调用栈

SkiaPipeline::renderFrame

SkiaVulkanPipeline::draw

CanvasContext::draw

DrawFrameTask::run

绘制layer

在SkiaPipeline的renderFrame方法:(frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp)

void SkiaPipeline::renderFrame(const LayerUpdateQueue& layers, const SkRect& clip,
                               const std::vector<sp<RenderNode>>& nodes, bool opaque,
                               const Rect& contentDrawBounds, sk_sp<SkSurface> surface,
                               const SkMatrix& preTransform) {
    bool previousSkpEnabled = Properties::skpCaptureEnabled;
    if (mPictureCapturedCallback) {
        Properties::skpCaptureEnabled = true;
    }

    // Initialize the canvas for the current frame, that might be a recording canvas if SKP
    // capture is enabled.
    SkCanvas* canvas = tryCapture(surface.get(), nodes[0].get(), layers);

    // draw all layers up front
    renderLayersImpl(layers, opaque);

    renderFrameImpl(clip, nodes, opaque, contentDrawBounds, canvas, preTransform);

    endCapture(surface.get());

    if (CC_UNLIKELY(Properties::debugOverdraw)) {
        renderOverdraw(clip, nodes, contentDrawBounds, surface, preTransform);
    }

    Properties::skpCaptureEnabled = previousSkpEnabled;
}

首先调用了SkiaPipeline的renderLayersImpl方法:(frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp)

void SkiaPipeline::renderLayersImpl(const LayerUpdateQueue& layers, bool opaque) {
    sk_sp<GrDirectContext> cachedContext;

    // Render all layers that need to be updated, in order.
    for (size_t i = 0; i < layers.entries().size(); i++) {
        RenderNode* layerNode = layers.entries()[i].renderNode.get();
        // only schedule repaint if node still on layer - possible it may have been
        // removed during a dropped frame, but layers may still remain scheduled so
        // as not to lose info on what portion is damaged
        if (CC_UNLIKELY(layerNode->getLayerSurface() == nullptr)) {
            continue;
        }
        SkASSERT(layerNode->getLayerSurface());
        SkiaDisplayList* displayList = layerNode->getDisplayList().asSkiaDl();
        if (!displayList || displayList->isEmpty()) {
            ALOGE("%p drawLayers(%s) : missing drawable", layerNode, layerNode->getName());
            return;
        }

        const Rect& layerDamage = layers.entries()[i].damage;

        SkCanvas* layerCanvas = layerNode->getLayerSurface()->getCanvas();

        int saveCount = layerCanvas->save();
        SkASSERT(saveCount == 1);

        layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());

        // TODO: put localized light center calculation and storage to a drawable related code.
        // It does not seem right to store something localized in a global state
        // fix here and in recordLayers
        const Vector3 savedLightCenter(LightingInfo::getLightCenterRaw());
        Vector3 transformedLightCenter(savedLightCenter);
        // map current light center into RenderNode's coordinate space
        layerNode->getSkiaLayer()->inverseTransformInWindow.mapPoint3d(transformedLightCenter);
        LightingInfo::setLightCenterRaw(transformedLightCenter);

        const RenderProperties& properties = layerNode->properties();
        const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
        if (properties.getClipToBounds() && layerCanvas->quickReject(bounds)) {
            return;
        }

        ATRACE_FORMAT("drawLayer [%s] %.1f x %.1f", layerNode->getName(), bounds.width(),
                      bounds.height());

        layerNode->getSkiaLayer()->hasRenderedSinceRepaint = false;
        layerCanvas->clear(SK_ColorTRANSPARENT);

        RenderNodeDrawable root(layerNode, layerCanvas, false);
        root.forceDraw(layerCanvas);
        layerCanvas->restoreToCount(saveCount);

        LightingInfo::setLightCenterRaw(savedLightCenter);

        // cache the current context so that we can defer flushing it until
        // either all the layers have been rendered or the context changes
        GrDirectContext* currentContext =
            GrAsDirectContext(layerNode->getLayerSurface()->getCanvas()->recordingContext());
        if (cachedContext.get() != currentContext) {
            if (cachedContext.get()) {
                ATRACE_NAME("flush layers (context changed)");
                cachedContext->flushAndSubmit();
            }
            cachedContext.reset(SkSafeRef(currentContext));
        }
    }

    if (cachedContext.get()) {
        ATRACE_NAME("flush layers");
        cachedContext->flushAndSubmit();
    }
}

这里遍历了所有的layer,使用mComposeLayer为false的RenderNodeDrawable的forceDraw方法,将这个layer的内容绘制到了之前创建的SkSurface上。

合成layer

SkiaPipeline::renderFrame后面还调用了SkiaPipeline::renderFrameImpl方法:(frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp)

void SkiaPipeline::renderFrameImpl(const SkRect& clip,
                                   const std::vector<sp<RenderNode>>& nodes, bool opaque,
                                   const Rect& contentDrawBounds, SkCanvas* canvas,
                                   const SkMatrix& preTransform) {
    SkAutoCanvasRestore saver(canvas, true);
    auto clipRestriction = preTransform.mapRect(clip).roundOut();
    if (CC_UNLIKELY(isCapturingSkp())) {
        canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction",
            nullptr);
    } else {
        // clip drawing to dirty region only when not recording SKP files (which should contain all
        // draw ops on every frame)
        canvas->androidFramework_setDeviceClipRestriction(clipRestriction);
    }
    canvas->concat(preTransform);

    if (!opaque) {
        canvas->clear(SK_ColorTRANSPARENT);
    }

    if (1 == nodes.size()) {
        if (!nodes[0]->nothingToDraw()) {
            RenderNodeDrawable root(nodes[0].get(), canvas);
            root.draw(canvas);
        }
    } else if (0 == nodes.size()) {
        // nothing to draw
    } else {
        ......
    }
}

这里对RootRenderNode,使用mComposeLayer为true的RenderNodeDrawable的forceDraw方法(RenderNodeDrawable::draw还是调用了RenderNodeDrawable::forceDraw),将所有内容合成绘制到了这个窗口的Surface上。

小结

hwui绘制时,先把每个layer的所有displayList绘制到之前创建的SkCanvas上,最后再一起合成绘制到最终的Surface上面。

绘制模糊效果

在合成layer的时候调用了RenderNodeDrawable的forceDraw方法:(frameworks/base/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp)

void RenderNodeDrawable::forceDraw(SkCanvas* canvas) const {
    RenderNode* renderNode = mRenderNode.get();
    MarkDraw _marker{*canvas, *renderNode};

    // We only respect the nothingToDraw check when we are composing a layer. This
    // ensures that we paint the layer even if it is not currently visible in the
    // event that the properties change and it becomes visible.
    if ((mProjectedDisplayList == nullptr && !renderNode->isRenderable()) ||
        (renderNode->nothingToDraw() && mComposeLayer)) {
        return;
    }

    SkiaDisplayList* displayList = renderNode->getDisplayList().asSkiaDl();

    SkAutoCanvasRestore acr(canvas, true);
    const RenderProperties& properties = this->getNodeProperties();
    // pass this outline to the children that may clip backward projected nodes
    displayList->mProjectedOutline =
            displayList->containsProjectionReceiver() ? &properties.getOutline() : nullptr;
    if (!properties.getProjectBackwards()) {
        drawContent(canvas);
        if (mProjectedDisplayList) {
            acr.restore();  // draw projected children using parent matrix
            LOG_ALWAYS_FATAL_IF(!mProjectedDisplayList->mProjectedOutline);
            const bool shouldClip = mProjectedDisplayList->mProjectedOutline->getPath();
            SkAutoCanvasRestore acr2(canvas, shouldClip);
            canvas->setMatrix(mProjectedDisplayList->mParentMatrix);
            if (shouldClip) {
                canvas->clipPath(*mProjectedDisplayList->mProjectedOutline->getPath());
            }
            drawBackwardsProjectedNodes(canvas, *mProjectedDisplayList);
        }
    }
    displayList->mProjectedOutline = nullptr;
}

调用了RenderNodeDrawable的drawContent方法:(frameworks/base/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp)

void RenderNodeDrawable::drawContent(SkCanvas* canvas) const {
    RenderNode* renderNode = mRenderNode.get();
    float alphaMultiplier = 1.0f;
    const RenderProperties& properties = renderNode->properties();

    // If we are drawing the contents of layer, we don't want to apply any of
    // the RenderNode's properties during this pass. Those will all be applied
    // when the layer is composited.
    if (mComposeLayer) {
        setViewProperties(properties, canvas, &alphaMultiplier);
    }
    SkiaDisplayList* displayList = mRenderNode->getDisplayList().asSkiaDl();
    displayList->mParentMatrix = canvas->getTotalMatrix();

    // TODO should we let the bound of the drawable do this for us?
    const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
    bool quickRejected = properties.getClipToBounds() && canvas->quickReject(bounds);
    if (!quickRejected) {
        auto clipBounds = canvas->getLocalClipBounds();
        SkIRect srcBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
        SkIPoint offset = SkIPoint::Make(0.0f, 0.0f);
        SkiaDisplayList* displayList = renderNode->getDisplayList().asSkiaDl();
        const LayerProperties& layerProperties = properties.layerProperties();
        // composing a hardware layer
        if (renderNode->getLayerSurface() && mComposeLayer) { // 如果这个RenderNode是layer
            ...
            if (!Properties::enableRenderEffectCache) {
                ...
            } else { // 使用imageFilter生成snapshotImage
                const auto snapshotResult = renderNode->updateSnapshotIfRequired(
                        recordingContext, layerProperties.getImageFilter(), clipBounds.roundOut());
                snapshotImage = snapshotResult->snapshot;
                srcBounds = snapshotResult->outSubset;
                offset = snapshotResult->outOffset;
            }
            ...
            const StretchEffect& stretch = properties.layerProperties().getStretchEffect();
            if (stretch.isEmpty() ||
                Properties::getStretchEffectBehavior() == StretchEffectBehavior::UniformScale) {
                ...
                // 将snapshotImage绘制到canvas上
                canvas->drawImageRect(snapshotImage, SkRect::Make(srcBounds),
                                      SkRect::Make(dstBounds), sampling, &paint,
                                      SkCanvas::kStrict_SrcRectConstraint);
            } else {
                ...
            }
            ...
        } else { // 如果这个RenderNode不是layer,直接将displayList绘制到canvas上
            if (alphaMultiplier < 1.0f) {
                // Non-layer draw for a view with getHasOverlappingRendering=false, will apply
                // the alpha to the paint of each nested draw.
                AlphaFilterCanvas alphaCanvas(canvas, alphaMultiplier);
                displayList->draw(&alphaCanvas);
            } else {
                displayList->draw(canvas);
            }
        }
    }
}

最终合成时,作为layer的RenderNode的内容首先经过imageFilter绘制到snapshotImage上,然后通过canvas->drawImageRect(snapshotImage, …)方法绘制到最终的Surface上面。

非layer的RenderNode则直接将displayList绘制到canvas上,displayList中可能还会有RenderNodeDrawable,则会递归调用这个RenderNodeDrawable::drawContent方法。

Android 12 高斯模糊-RenderEffect 赠送大家一套完整的Android学习资料吧。以前一直是自己在网上东平西凑的找,找到的东西也是零零散散,很多时候都是看着看着就没了,时间浪费了,问题却还没得到解决,很让人抓狂。后面我就自己整理了一套资料,还别说,真香!资料有条理,有系统,还很全面,我不方便直接放出来,大家可以先看看有没有用得到的地方吧。《Android架构视频+BATJ面试专题PDF+学习笔记》上东平西凑的找,找到的东西也是零零散散,很多时候都是看着看着就没了,时间浪费了,问题却还没得到解决,很让人抓狂。 阅读详情

相关推荐

Android12 弹窗外部、内部高斯模糊效果实现

Android 12 中提供了高斯模糊的属性,可以很轻松的实现弹窗相关的高斯模糊效果

侠亦狐的博客 5478

❤️Android 12 高斯模糊-RenderEffect❤️

Android 12 高斯模糊-新功能: RenderEffect 将模糊、色彩滤镜等效果应用于 RenderNode 或 View。

帅次的博客 8388

Android-实现View模糊效果

View模糊效果

安卓开发&音视频开发 1万+

Android Bitmap 使用Vukan、RenderEffect、GLSL实现模糊

使用 Vukan 模糊 使用 RenderEffect 模糊 使用 GLSL 模糊 RS、Vukan、RenderEffect、GLSL 效率对比

分享Android应用和系统开发、音视频编码、性能优化相关知识 3979

Android动态高斯模糊实战:从RenderScript到RenderEffect的性能优化之旅

本文深入探讨了Android动态高斯模糊的技术演进,从传统的RenderScript方案到Android 12引入的原生RenderEffect方案。通过对比分析两者的底层架构、性能差异及适用场景,为开发者提供了从RenderScript迁移至RenderEffect的完整性能优化指南与实战策略,帮助构建更流畅的沉浸式视觉体验。

lambda的博客 158

BlurView性能优化秘籍:API 31+硬件加速与RenderEffect深度解析

BlurView作为一款实现iOS风格动态模糊效果的Android开源库,在API 31+系统中通过RenderEffect实现了硬件加速的突破性优化。本文将从技术原理到实战配置,全面解析如何利用最新系统特性提升模糊效果的性能表现,让你的应用在视觉体验与流畅度之间找到完美平衡。 ## 🚀 为什么API 31+是BlurView性能的转折点 Android 12(API 31)引入的`Rend

gitblog_00652的博客 350

Android高斯模糊原理

可以使用`applyGaussianBlur`方法来对输入的Bitmap对象进行高斯模糊处理,返回一个模糊效果的Bitmap对象。记得在使用结束后释放相关资源,以避免内存泄漏。```3. 在你的Activity或Fragment中,创建一个方法来应用高斯模糊效果。// 计算平均值并将结果写入输出图像。// 对周围像素进行高斯模糊处理。

langjian2012的专栏 1428

Android动态高斯模糊背景实战:从RenderScript到RenderEffect的平滑迁移(附完整代码)

本文详细讲解了Android动态高斯模糊背景的实现与迁移策略。针对RenderScript在Android 12后被废弃的情况,提供了向官方推荐方案RenderEffect平滑迁移的完整实战指南。文章通过性能对比、智能版本适配工具类(BlurHelper)的完整代码,以及音乐播放器背景动态模糊的真实案例,帮助开发者高效实现兼容新旧系统的优雅模糊效果,并分享了关键的避坑指南与进阶优化技巧。

weixin_29194693的博客 391

安卓RenderEffect实战:从模糊到高级滤镜的底层实现与性能优化

本文深入解析了安卓RenderEffect的底层实现与性能优化。从模糊效果入手,详细剖析了RenderEffect在图形渲染管线中的工作原理、离屏Layer的创建与滤镜应用流程,并展示了颜色矩阵、链式组合等高级滤镜的实现方法。文章重点探讨了在滚动视图等复杂场景下的性能陷阱,并提供了控制Layer尺寸、动态开关效果等实战优化策略,帮助开发者打造既炫酷又流畅的UI特效。

sky77的博客 525

Android实现Bitmap高斯模糊效果(附带源码)

Android实现Bitmap高斯模糊效果(附带源码)

欢迎来到我的博客!这里是一个专注于计算机技术的分享平台,涵盖编程开发、算法研究、系统架构、软件工程等多个领域。无论你是初学者还是资深开发者,都能在这里找到有价值的内容。 1837

Android动态模糊性能优化指南:从SeekBar实时调节到局部模糊的实战技巧

本文深入探讨了Android动态模糊性能优化的实战技巧,从实时调节到局部渲染的进阶方案。通过分析动态高斯模糊的底层原理与性能瓶颈,提供分层处理架构设计、局部模糊精确定位技术及跨版本兼容的工程化实践,帮助开发者在不同设备上实现流畅的模糊效果。

fff88的博客 779

Android离屏渲染:从原理到性能调优实战

本文深入解析Android离屏渲染的原理与性能影响,提供从检测工具到优化方案的完整实战指南。通过分析圆角、阴影等常见场景的性能瓶颈,介绍GPU渲染分析、Perfetto等工具的使用,并详细讲解Android 12+的RenderEffect API等高效优化方案,帮助开发者显著提升应用流畅度。

weixin_42525632的博客 193

Android ARGB透明度实战:从基础原理到高效应用

本文深入解析Android开发中ARGB透明度的核心原理与实战应用。从ARGB(Alpha通道)的基础概念讲起,详细介绍了透明度百分比与十六进制值的换算方法,并提供了在XML布局和Kotlin/Java代码中设置透明度的具体示例。文章还涵盖了高级技巧,如透明度资源管理、性能优化策略(避免过度绘制)以及实现毛玻璃等视觉效果,最后通过一个半透明底部弹窗的完整案例,帮助开发者高效、优雅地应用透明度提升App视觉体验。

weixin_29311017的博客 198

【笔记】安卓毛玻璃效果(Blur)实现笔记(使用BlurView)(结尾附:源码)

本文只聚焦一个问题:如何在 Android 中实现“毛玻璃背景效果”。内容围绕实际项目落地,总结实现方式、三方库使用方法以及关键避坑点。

一个XR(AR|VR|MR)程序猿的博客(EQ-雪梨蛋花汤) 3419

AlertDialog高斯模糊进阶指南:Android12新特性与兼容方案对比

本文深入探讨了Android 12中AlertDialog高斯模糊的新特性与兼容方案,对比了原生API与传统实现方法的性能差异。通过详细代码示例和优化建议,帮助开发者在不同Android版本上实现高效的背景模糊效果,提升用户体验。重点关注Android12的高斯模糊API、RenderEffect使用及多版本适配策略。

weixin_30823001的博客 643

安卓UI性能优化实战:RenderEffect模糊效果背后的离屏渲染与性能开销分析

本文深入分析了安卓RenderEffect模糊效果的工作原理及其性能开销,揭示了离屏渲染机制对UI性能的影响。通过实战案例和优化策略,帮助开发者有效降低内存占用和GPU计算开销,提升应用流畅度。特别针对RenderEffect的性能陷阱,提供了层级合并、尺寸优化等实用解决方案。

weixin_29218963的博客 392

Android 12新特性实战:用RenderEffect API优雅实现动态圆角与阴影,告别传统离屏渲染

本文深入解析Android 12的RenderEffect API,展示如何通过硬件加速实现动态圆角与阴影效果,显著提升UI渲染性能。通过对比测试和实战代码,帮助开发者告别传统离屏渲染方案,优化移动应用界面设计。

weixin_30733003的博客 425

Android 12 高斯模糊实战:用RenderEffect给你的App界面加一层‘毛玻璃’滤镜

本文详细介绍了如何在Android 12中使用RenderEffect API实现高斯模糊效果,为应用界面添加类似iOS毛玻璃的高级UI质感。通过基础实现、典型应用场景、性能优化及与Jetpack Compose的集成等实战技巧,帮助开发者快速掌握这一提升应用视觉层次和沉浸感的关键技术。

weixin_30245867的博客 253

Android Liquid Glass终极指南:快速打造惊艳玻璃态界面

想要为你的Android应用添加iOS级玻璃态视觉效果吗?Android Liquid Glass就是你的最佳选择!这是一个专为Compose Multiplatform设计的开源库,让你轻松实现毛玻璃、折射、动态模糊等现代UI效果。无论你是刚入门的新手还是经验丰富的开发者,都能在几分钟内掌握这个强大的工具。 ## 🔮 理解玻璃态设计的核心概念 玻璃态设计(Glassmorphism)是近年

gitblog_00461的博客 1092
上一篇: 安卓Winscope原理
下一篇: 安卓Canvas梳理
SSSxCCC
博客等级 码龄10年 238粉丝 74原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SSSxCCC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值