Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3f77aa46 authored by Derek Sollenberger's avatar Derek Sollenberger
Browse files

Add support for crossfading blur with original input.

If we know that we need to sample the destination for the blur,
then we go ahead and render contents into an offscreen to avoid
an extra render target switch and texture copy.

If the radius is less than kMaxCrossFadeRadius then the final
composition of the blur mixes with the original content to hide
any downscaling artifacts.

Bug: 178503959
Bug: 176903027
Test: notification shade and SilkFX test app
Change-Id: If836461c1c62020447797e9b2d1050156d9c7d0c
parent ecb21465
Loading
Loading
Loading
Loading
+113 −94
Original line number Diff line number Diff line
@@ -286,6 +286,7 @@ SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGL
    }

    if (args.supportsBackgroundBlur) {
        ALOGD("Background Blurs Enabled");
        mBlurFilter = new BlurFilter();
    }
    mCapture = std::make_unique<SkiaCapture>();
@@ -516,6 +517,45 @@ sk_sp<SkShader> SkiaGLRenderEngine::createRuntimeEffectShader(sk_sp<SkShader> sh
    return shader;
}

void SkiaGLRenderEngine::initCanvas(SkCanvas* canvas, const DisplaySettings& display) {
    if (mCapture->isCaptureRunning()) {
        // Record display settings when capture is running.
        std::stringstream displaySettings;
        PrintTo(display, &displaySettings);
        // Store the DisplaySettings in additional information.
        canvas->drawAnnotation(SkRect::MakeEmpty(), "DisplaySettings",
                               SkData::MakeWithCString(displaySettings.str().c_str()));
    }

    // Before doing any drawing, let's make sure that we'll start at the origin of the display.
    // Some displays don't start at 0,0 for example when we're mirroring the screen. Also, virtual
    // displays might have different scaling when compared to the physical screen.

    canvas->clipRect(getSkRect(display.physicalDisplay));
    canvas->translate(display.physicalDisplay.left, display.physicalDisplay.top);

    const auto clipWidth = display.clip.width();
    const auto clipHeight = display.clip.height();
    auto rotatedClipWidth = clipWidth;
    auto rotatedClipHeight = clipHeight;
    // Scale is contingent on the rotation result.
    if (display.orientation & ui::Transform::ROT_90) {
        std::swap(rotatedClipWidth, rotatedClipHeight);
    }
    const auto scaleX = static_cast<SkScalar>(display.physicalDisplay.width()) /
            static_cast<SkScalar>(rotatedClipWidth);
    const auto scaleY = static_cast<SkScalar>(display.physicalDisplay.height()) /
            static_cast<SkScalar>(rotatedClipHeight);
    canvas->scale(scaleX, scaleY);

    // Canvas rotation is done by centering the clip window at the origin, rotating, translating
    // back so that the top left corner of the clip is at (0, 0).
    canvas->translate(rotatedClipWidth / 2, rotatedClipHeight / 2);
    canvas->rotate(toDegrees(display.orientation));
    canvas->translate(-clipWidth / 2, -clipHeight / 2);
    canvas->translate(-display.clip.left, -display.clip.top);
}

status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
                                        const std::vector<const LayerSettings*>& layers,
                                        const sp<GraphicBuffer>& buffer,
@@ -570,57 +610,49 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
        }
    }

    sk_sp<SkSurface> surface =
    sk_sp<SkSurface> dstSurface =
            surfaceTextureRef->getTexture()->getOrCreateSurface(mUseColorManagement
                                                                        ? display.outputDataspace
                                                                        : ui::Dataspace::UNKNOWN,
                                                                grContext.get());

    SkCanvas* canvas = mCapture->tryCapture(surface.get());
    if (canvas == nullptr) {
    SkCanvas* dstCanvas = mCapture->tryCapture(dstSurface.get());
    if (dstCanvas == nullptr) {
        ALOGE("Cannot acquire canvas from Skia.");
        return BAD_VALUE;
    }
    // Clear the entire canvas with a transparent black to prevent ghost images.
    canvas->clear(SK_ColorTRANSPARENT);
    canvas->save();

    if (mCapture->isCaptureRunning()) {
        // Record display settings when capture is running.
        std::stringstream displaySettings;
        PrintTo(display, &displaySettings);
        // Store the DisplaySettings in additional information.
        canvas->drawAnnotation(SkRect::MakeEmpty(), "DisplaySettings",
                               SkData::MakeWithCString(displaySettings.str().c_str()));
    // Find if any layers have requested blur, we'll use that info to decide when to render to an
    // offscreen buffer and when to render to the native buffer.
    sk_sp<SkSurface> activeSurface(dstSurface);
    SkCanvas* canvas = dstCanvas;
    const LayerSettings* blurCompositionLayer = nullptr;
    if (mBlurFilter) {
        bool requiresCompositionLayer = false;
        for (const auto& layer : layers) {
            if (layer->backgroundBlurRadius > 0) {
                // when skbug.com/11208 and b/176903027 are resolved we can add the additional
                // restriction for layer->backgroundBlurRadius < BlurFilter::kMaxCrossFadeRadius
                requiresCompositionLayer = true;
            }
            for (auto region : layer->blurRegions) {
                if (region.blurRadius < BlurFilter::kMaxCrossFadeRadius) {
                    requiresCompositionLayer = true;
                }
            }
            if (requiresCompositionLayer) {
                activeSurface = dstSurface->makeSurface(dstSurface->imageInfo());
                canvas = activeSurface->getCanvas();
                blurCompositionLayer = layer;
                break;
            }
        }

    // Before doing any drawing, let's make sure that we'll start at the origin of the display.
    // Some displays don't start at 0,0 for example when we're mirroring the screen. Also, virtual
    // displays might have different scaling when compared to the physical screen.

    canvas->clipRect(getSkRect(display.physicalDisplay));
    canvas->translate(display.physicalDisplay.left, display.physicalDisplay.top);

    const auto clipWidth = display.clip.width();
    const auto clipHeight = display.clip.height();
    auto rotatedClipWidth = clipWidth;
    auto rotatedClipHeight = clipHeight;
    // Scale is contingent on the rotation result.
    if (display.orientation & ui::Transform::ROT_90) {
        std::swap(rotatedClipWidth, rotatedClipHeight);
    }
    const auto scaleX = static_cast<SkScalar>(display.physicalDisplay.width()) /
            static_cast<SkScalar>(rotatedClipWidth);
    const auto scaleY = static_cast<SkScalar>(display.physicalDisplay.height()) /
            static_cast<SkScalar>(rotatedClipHeight);
    canvas->scale(scaleX, scaleY);

    // Canvas rotation is done by centering the clip window at the origin, rotating, translating
    // back so that the top left corner of the clip is at (0, 0).
    canvas->translate(rotatedClipWidth / 2, rotatedClipHeight / 2);
    canvas->rotate(toDegrees(display.orientation));
    canvas->translate(-clipWidth / 2, -clipHeight / 2);
    canvas->translate(-display.clip.left, -display.clip.top);
    canvas->save();
    // Clear the entire canvas with a transparent black to prevent ghost images.
    canvas->clear(SK_ColorTRANSPARENT);
    initCanvas(canvas, display);

    // TODO: clearRegion was required for SurfaceView when a buffer is not yet available but the
    // view is still on-screen. The clear region could be re-specified as a black color layer,
@@ -647,8 +679,36 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,

    for (const auto& layer : layers) {
        ATRACE_NAME("DrawLayer");

        sk_sp<SkImage> blurInput;
        if (blurCompositionLayer == layer) {
            LOG_ALWAYS_FATAL_IF(activeSurface == dstSurface);
            LOG_ALWAYS_FATAL_IF(canvas == dstCanvas);

            // save a snapshot of the activeSurface to use as input to the blur shaders
            blurInput = activeSurface->makeImageSnapshot();

            // TODO we could skip this step if we know the blur will cover the entire image
            //  blit the offscreen framebuffer into the destination AHB
            SkPaint paint;
            paint.setBlendMode(SkBlendMode::kSrc);
            activeSurface->draw(dstCanvas, 0, 0, SkSamplingOptions(), &paint);

            // assign dstCanvas to canvas and ensure that the canvas state is up to date
            canvas = dstCanvas;
            canvas->save();
            initCanvas(canvas, display);

            LOG_ALWAYS_FATAL_IF(activeSurface->getCanvas()->getSaveCount() !=
                                dstSurface->getCanvas()->getSaveCount());
            LOG_ALWAYS_FATAL_IF(activeSurface->getCanvas()->getTotalMatrix() !=
                                dstSurface->getCanvas()->getTotalMatrix());

            // assign dstSurface to activeSurface
            activeSurface = dstSurface;
        }

        canvas->save();
        if (mCapture->isCaptureRunning()) {
            // Record the name of the layer if the capture is running.
            std::stringstream layerSettings;
@@ -657,7 +717,6 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
            canvas->drawAnnotation(SkRect::MakeEmpty(), layer->name.c_str(),
                                   SkData::MakeWithCString(layerSettings.str().c_str()));
        }

        // Layers have a local transform that should be applied to them
        canvas->concat(getSkM44(layer->geometry.positionTransform).asM33());

@@ -665,8 +724,11 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
        if (mBlurFilter && layerHasBlur(layer)) {
            std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;

            // image to be blurred
            sk_sp<SkImage> blurInput = surface->makeImageSnapshot();
            // if multiple layers have blur, then we need to take a snapshot now because
            // only the lowest layer will have blurImage populated earlier
            if (!blurInput) {
                blurInput = activeSurface->makeImageSnapshot();
            }
            // rect to be blurred in the coordinate space of blurInput
            const auto blurRect = canvas->getTotalMatrix().mapRect(bounds);

@@ -678,7 +740,8 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,

                cachedBlurs[layer->backgroundBlurRadius] = blurredImage;

                drawBlurRegion(canvas, getBlurRegion(layer), blurRect, blurredImage);
                mBlurFilter->drawBlurRegion(canvas, getBlurRegion(layer), blurRect, blurredImage,
                                            blurInput);
            }
            for (auto region : layer->blurRegions) {
                if (cachedBlurs[region.blurRadius] != nullptr) {
@@ -687,7 +750,9 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
                            mBlurFilter->generate(grContext.get(), region.blurRadius, blurInput,
                                                  blurRect);
                }
                drawBlurRegion(canvas, region, blurRect, cachedBlurs[region.blurRadius]);

                mBlurFilter->drawBlurRegion(canvas, region, blurRect,
                                            cachedBlurs[region.blurRadius], blurInput);
            }
        }

@@ -820,7 +885,8 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
    mCapture->endCapture();
    {
        ATRACE_NAME("flush surface");
        surface->flush();
        LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface);
        activeSurface->flush();
    }

    if (drawFence != nullptr) {
@@ -920,53 +986,6 @@ void SkiaGLRenderEngine::drawShadow(SkCanvas* canvas, const SkRect& casterRect,
                              flags);
}

void SkiaGLRenderEngine::drawBlurRegion(SkCanvas* canvas, const BlurRegion& effectRegion,
                                        const SkRect& layerRect, sk_sp<SkImage> blurredImage) {
    ATRACE_CALL();

    SkPaint paint;
    paint.setAlpha(static_cast<int>(effectRegion.alpha * 255));
    const auto matrix = getBlurShaderTransform(canvas, layerRect);
    SkSamplingOptions linearSampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
    paint.setShader(blurredImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linearSampling,
                                             &matrix));

    auto rect = SkRect::MakeLTRB(effectRegion.left, effectRegion.top, effectRegion.right,
                                 effectRegion.bottom);

    if (effectRegion.cornerRadiusTL > 0 || effectRegion.cornerRadiusTR > 0 ||
        effectRegion.cornerRadiusBL > 0 || effectRegion.cornerRadiusBR > 0) {
        const SkVector radii[4] =
                {SkVector::Make(effectRegion.cornerRadiusTL, effectRegion.cornerRadiusTL),
                 SkVector::Make(effectRegion.cornerRadiusTR, effectRegion.cornerRadiusTR),
                 SkVector::Make(effectRegion.cornerRadiusBL, effectRegion.cornerRadiusBL),
                 SkVector::Make(effectRegion.cornerRadiusBR, effectRegion.cornerRadiusBR)};
        SkRRect roundedRect;
        roundedRect.setRectRadii(rect, radii);
        canvas->drawRRect(roundedRect, paint);
    } else {
        canvas->drawRect(rect, paint);
    }
}

SkMatrix SkiaGLRenderEngine::getBlurShaderTransform(const SkCanvas* canvas,
                                                    const SkRect& layerRect) {
    // 1. Apply the blur shader matrix, which scales up the blured surface to its real size
    auto matrix = mBlurFilter->getShaderMatrix();
    // 2. Since the blurred surface has the size of the layer, we align it with the
    // top left corner of the layer position.
    matrix.postConcat(SkMatrix::Translate(layerRect.fLeft, layerRect.fTop));
    // 3. Finally, apply the inverse canvas matrix. The snapshot made in the BlurFilter is in the
    // original surface orientation. The inverse matrix has to be applied to align the blur
    // surface with the current orientation/position of the canvas.
    SkMatrix drawInverse;
    if (canvas->getTotalMatrix().invert(&drawInverse)) {
        matrix.postConcat(drawInverse);
    }

    return matrix;
}

EGLContext SkiaGLRenderEngine::createEglContext(EGLDisplay display, EGLConfig config,
                                                EGLContext shareContext,
                                                std::optional<ContextPriority> contextPriority,
+1 −3
Original line number Diff line number Diff line
@@ -88,11 +88,9 @@ private:

    base::unique_fd flush();
    bool waitFence(base::unique_fd fenceFd);
    void initCanvas(SkCanvas* canvas, const DisplaySettings& display);
    void drawShadow(SkCanvas* canvas, const SkRect& casterRect, float casterCornerRadius,
                    const ShadowSettings& shadowSettings);
    void drawBlurRegion(SkCanvas* canvas, const BlurRegion& blurRegion, const SkRect& layerRect,
                        sk_sp<SkImage> blurredImage);
    SkMatrix getBlurShaderTransform(const SkCanvas* canvas, const SkRect& layerRect);
    // If mUseColorManagement is correct and layer needsLinearEffect, it returns a linear runtime
    // shader. Otherwise it returns the input shader.
    sk_sp<SkShader> createRuntimeEffectShader(sk_sp<SkShader> shader, const LayerSettings* layer,
+86 −2
Original line number Diff line number Diff line
@@ -52,6 +52,22 @@ BlurFilter::BlurFilter() {
        LOG_ALWAYS_FATAL("RuntimeShader error: %s", error.c_str());
    }
    mBlurEffect = std::move(blurEffect);

    SkString mixString(R"(
        in shader blurredInput;
        in shader originalInput;
        uniform float mixFactor;

        half4 main(float2 xy) {
            return half4(mix(sample(originalInput), sample(blurredInput), mixFactor));
        }
    )");

    auto [mixEffect, mixError] = SkRuntimeEffect::Make(mixString);
    if (!mixEffect) {
        LOG_ALWAYS_FATAL("RuntimeShader error: %s", mixError.c_str());
    }
    mMixEffect = std::move(mixEffect);
}

sk_sp<SkImage> BlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
@@ -97,8 +113,76 @@ sk_sp<SkImage> BlurFilter::generate(GrRecordingContext* context, const uint32_t
    return tmpBlur;
}

SkMatrix BlurFilter::getShaderMatrix() const {
    return SkMatrix::Scale(kInverseInputScale, kInverseInputScale);
static SkMatrix getShaderTransform(const SkCanvas* canvas, const SkRect& blurRect, float scale) {
    // 1. Apply the blur shader matrix, which scales up the blured surface to its real size
    auto matrix = SkMatrix::Scale(scale, scale);
    // 2. Since the blurred surface has the size of the layer, we align it with the
    // top left corner of the layer position.
    matrix.postConcat(SkMatrix::Translate(blurRect.fLeft, blurRect.fTop));
    // 3. Finally, apply the inverse canvas matrix. The snapshot made in the BlurFilter is in the
    // original surface orientation. The inverse matrix has to be applied to align the blur
    // surface with the current orientation/position of the canvas.
    SkMatrix drawInverse;
    if (canvas != nullptr && canvas->getTotalMatrix().invert(&drawInverse)) {
        matrix.postConcat(drawInverse);
    }
    return matrix;
}

void BlurFilter::drawBlurRegion(SkCanvas* canvas, const BlurRegion& effectRegion,
                                const SkRect& blurRect, sk_sp<SkImage> blurredImage,
                                sk_sp<SkImage> input) {
    ATRACE_CALL();

    SkPaint paint;
    paint.setAlphaf(effectRegion.alpha);
    if (effectRegion.alpha == 1.0f) {
        paint.setBlendMode(SkBlendMode::kSrc);
    }

    const auto blurMatrix = getShaderTransform(canvas, blurRect, kInverseInputScale);
    SkSamplingOptions linearSampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
    const auto blurShader = blurredImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                                     linearSampling, &blurMatrix);

    if (effectRegion.blurRadius < kMaxCrossFadeRadius) {
        // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
        // case you might expect the matrix to simply be the canvas matrix.
        SkMatrix inputMatrix;
        if (!canvas->getTotalMatrix().invert(&inputMatrix)) {
            ALOGE("matrix was unable to be inverted");
        }

        SkRuntimeShaderBuilder blurBuilder(mMixEffect);
        blurBuilder.child("blurredInput") = blurShader;
        blurBuilder.child("originalInput") =
                input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linearSampling,
                                  inputMatrix);
        blurBuilder.uniform("mixFactor") = effectRegion.blurRadius / kMaxCrossFadeRadius;

        paint.setShader(blurBuilder.makeShader(nullptr, true));
    } else {
        paint.setShader(blurShader);
    }

    // TODO we should AA at least the drawRoundRect which would mean no SRC blending
    // TODO this round rect calculation doesn't match the one used to draw in RenderEngine
    auto rect = SkRect::MakeLTRB(effectRegion.left, effectRegion.top, effectRegion.right,
                                 effectRegion.bottom);

    if (effectRegion.cornerRadiusTL > 0 || effectRegion.cornerRadiusTR > 0 ||
        effectRegion.cornerRadiusBL > 0 || effectRegion.cornerRadiusBR > 0) {
        const SkVector radii[4] =
                {SkVector::Make(effectRegion.cornerRadiusTL, effectRegion.cornerRadiusTL),
                 SkVector::Make(effectRegion.cornerRadiusTR, effectRegion.cornerRadiusTR),
                 SkVector::Make(effectRegion.cornerRadiusBL, effectRegion.cornerRadiusBL),
                 SkVector::Make(effectRegion.cornerRadiusBR, effectRegion.cornerRadiusBR)};
        SkRRect roundedRect;
        roundedRect.setRectRadii(rect, radii);
        canvas->drawRRect(roundedRect, paint);
    } else {
        canvas->drawRect(rect, paint);
    }
}

} // namespace skia
+5 −2
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@
#include <SkImage.h>
#include <SkRuntimeEffect.h>
#include <SkSurface.h>
#include <ui/BlurRegion.h>

using namespace std;

@@ -50,11 +51,13 @@ public:
    // Execute blur, saving it to a texture
    sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
                            const sk_sp<SkImage> blurInput, const SkRect& blurRect) const;
    // Returns a matrix that should be applied to the blur shader
    SkMatrix getShaderMatrix() const;

    void drawBlurRegion(SkCanvas* canvas, const BlurRegion& blurRegion, const SkRect& blurRect,
                        sk_sp<SkImage> blurredImage, sk_sp<SkImage> input);

private:
    sk_sp<SkRuntimeEffect> mBlurEffect;
    sk_sp<SkRuntimeEffect> mMixEffect;
};

} // namespace skia