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

Commit 8e8b3bf3 authored by Derek Sollenberger's avatar Derek Sollenberger
Browse files

Update RenderEngine to properly respect the roundRectCrop.

Test: librenderengine_test; camera and SilkFX demo app
Bug: 186773838
Change-Id: Ib52d37ab08f1d0b7b4c41e187105ca7e002f3b99
parent f938c300
Loading
Loading
Loading
Loading
+103 −29
Original line number Diff line number Diff line
@@ -667,6 +667,17 @@ void drawStretch(const SkRect& bounds, const StretchEffect& stretchEffect,
    canvas->drawRect(stretchBounds, paint);
}

static SkRRect getBlurRRect(const BlurRegion& region) {
    const auto rect = SkRect::MakeLTRB(region.left, region.top, region.right, region.bottom);
    const SkVector radii[4] = {SkVector::Make(region.cornerRadiusTL, region.cornerRadiusTL),
                               SkVector::Make(region.cornerRadiusTR, region.cornerRadiusTR),
                               SkVector::Make(region.cornerRadiusBR, region.cornerRadiusBR),
                               SkVector::Make(region.cornerRadiusBL, region.cornerRadiusBL)};
    SkRRect roundedRect;
    roundedRect.setRectRadii(rect, radii);
    return roundedRect;
}

status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
                                        const std::vector<const LayerSettings*>& layers,
                                        const std::shared_ptr<ExternalTexture>& buffer,
@@ -840,7 +851,7 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
        // Layers have a local transform that should be applied to them
        canvas->concat(getSkM44(layer->geometry.positionTransform).asM33());

        const auto bounds = getSkRect(layer->geometry.boundaries);
        const auto [bounds, roundRectClip] = getBoundsAndClip(layer);
        if (mBlurFilter && layerHasBlur(layer)) {
            std::unordered_map<uint32_t, sk_sp<SkImage>> cachedBlurs;

@@ -850,7 +861,14 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
                blurInput = activeSurface->makeImageSnapshot();
            }
            // rect to be blurred in the coordinate space of blurInput
            const auto blurRect = canvas->getTotalMatrix().mapRect(bounds);
            const auto blurRect = canvas->getTotalMatrix().mapRect(bounds.rect());

            // if the clip needs to be applied then apply it now and make sure
            // it is restored before we attempt to draw any shadows.
            SkAutoCanvasRestore acr(canvas, true);
            if (!roundRectClip.isEmpty()) {
                canvas->clipRRect(roundRectClip, true);
            }

            // TODO(b/182216890): Filter out empty layers earlier
            if (blurRect.width() > 0 && blurRect.height() > 0) {
@@ -862,10 +880,10 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,

                    cachedBlurs[layer->backgroundBlurRadius] = blurredImage;

                    mBlurFilter->drawBlurRegion(canvas, getBlurRegion(layer), blurRect,
                                                blurredImage, blurInput);
                    mBlurFilter->drawBlurRegion(canvas, bounds, layer->backgroundBlurRadius, 1.0f,
                                                blurRect, blurredImage, blurInput);
                }
                SkAutoCanvasRestore acr(canvas, true);

                canvas->concat(getSkM44(layer->blurRegionTransform).asM33());
                for (auto region : layer->blurRegions) {
                    if (cachedBlurs[region.blurRadius] == nullptr) {
@@ -875,7 +893,8 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
                                                      blurRect);
                    }

                    mBlurFilter->drawBlurRegion(canvas, region, blurRect,
                    mBlurFilter->drawBlurRegion(canvas, getBlurRRect(region), region.blurRadius,
                                                region.alpha, blurRect,
                                                cachedBlurs[region.blurRadius], blurInput);
                }
            }
@@ -888,7 +907,7 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
        if (layer->shadow.length > 0) {
            const auto rect = layer->geometry.roundedCornersRadius > 0
                    ? getSkRect(layer->geometry.roundedCornersCrop)
                    : bounds;
                    : bounds.rect();
            // This would require a new parameter/flag to SkShadowUtils::DrawShadow
            LOG_ALWAYS_FATAL_IF(layer->disableBlending, "Cannot disableBlending with a shadow");
            drawShadow(canvas, rect, layer->geometry.roundedCornersRadius, layer->shadow);
@@ -953,7 +972,7 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
            // The shader does not respect the translation, so we add it to the texture
            // transform for the SkImage. This will make sure that the correct layer contents
            // are drawn in the correct part of the screen.
            matrix.postTranslate(layer->geometry.boundaries.left, layer->geometry.boundaries.top);
            matrix.postTranslate(bounds.rect().fLeft, bounds.rect().fTop);

            sk_sp<SkShader> shader;

@@ -1015,9 +1034,13 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,

        paint.setColorFilter(displayColorTransform);

        if (layer->geometry.roundedCornersRadius > 0) {
        if (!roundRectClip.isEmpty()) {
            canvas->clipRRect(roundRectClip, true);
        }

        if (!bounds.isRect()) {
            paint.setAntiAlias(true);
            canvas->drawRRect(getRoundedRect(layer), paint);
            canvas->drawRRect(bounds, paint);
        } else {
            auto& stretchEffect = layer->stretchEffect;
            // TODO (njawad) temporarily disable manipulation of geometry
@@ -1026,9 +1049,9 @@ status_t SkiaGLRenderEngine::drawLayers(const DisplaySettings& display,
            // Keep the method call in a dead code path to make -Werror happy
            // with unused methods
            if (stretchEffect.hasEffect() && /* DISABLES CODE */ (false)) {
                drawStretch(bounds, stretchEffect, canvas, paint);
                drawStretch(bounds.rect(), stretchEffect, canvas, paint);
            } else {
                canvas->drawRect(bounds, paint);
                canvas->drawRect(bounds.rect(), paint);
            }
        }
        if (kFlushAfterEveryLayer) {
@@ -1077,25 +1100,76 @@ inline SkRect SkiaGLRenderEngine::getSkRect(const Rect& rect) {
    return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
}

inline SkRRect SkiaGLRenderEngine::getRoundedRect(const LayerSettings* layer) {
    const auto rect = getSkRect(layer->geometry.roundedCornersCrop);
inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(
        const LayerSettings* layer) {
    const auto bounds = getSkRect(layer->geometry.boundaries);
    const auto crop = getSkRect(layer->geometry.roundedCornersCrop);
    const auto cornerRadius = layer->geometry.roundedCornersRadius;
    return SkRRect::MakeRectXY(rect, cornerRadius, cornerRadius);
}

inline BlurRegion SkiaGLRenderEngine::getBlurRegion(const LayerSettings* layer) {
    const auto rect = getSkRect(layer->geometry.boundaries);
    const auto cornersRadius = layer->geometry.roundedCornersRadius;
    return BlurRegion{.blurRadius = static_cast<uint32_t>(layer->backgroundBlurRadius),
                      .cornerRadiusTL = cornersRadius,
                      .cornerRadiusTR = cornersRadius,
                      .cornerRadiusBL = cornersRadius,
                      .cornerRadiusBR = cornersRadius,
                      .alpha = 1,
                      .left = static_cast<int>(rect.fLeft),
                      .top = static_cast<int>(rect.fTop),
                      .right = static_cast<int>(rect.fRight),
                      .bottom = static_cast<int>(rect.fBottom)};

    SkRRect clip;
    if (cornerRadius > 0) {
        // it the crop and the bounds are equivalent then we don't need a clip
        if (bounds == crop) {
            return {SkRRect::MakeRectXY(bounds, cornerRadius, cornerRadius), 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)) {
            bool intersectionIsRoundRect = true;
            // check each cropped corner to ensure that it exactly matches the crop or is full
            SkVector radii[4];

            const auto insetCrop = crop.makeInset(cornerRadius, cornerRadius);

            // compute the UpperLeft corner radius
            if (bounds.fLeft == crop.fLeft && bounds.fTop == crop.fTop) {
                radii[0].set(cornerRadius, cornerRadius);
            } else if (bounds.fLeft > insetCrop.fLeft && bounds.fTop > insetCrop.fTop) {
                radii[0].set(0, 0);
            } else {
                intersectionIsRoundRect = false;
            }
            // compute the UpperRight corner radius
            if (bounds.fRight == crop.fRight && bounds.fTop == crop.fTop) {
                radii[1].set(cornerRadius, cornerRadius);
            } else if (bounds.fRight < insetCrop.fRight && bounds.fTop > insetCrop.fTop) {
                radii[1].set(0, 0);
            } else {
                intersectionIsRoundRect = false;
            }
            // compute the BottomRight corner radius
            if (bounds.fRight == crop.fRight && bounds.fBottom == crop.fBottom) {
                radii[2].set(cornerRadius, cornerRadius);
            } else if (bounds.fRight < insetCrop.fRight && bounds.fBottom < insetCrop.fBottom) {
                radii[2].set(0, 0);
            } else {
                intersectionIsRoundRect = false;
            }
            // compute the BottomLeft corner radius
            if (bounds.fLeft == crop.fLeft && bounds.fBottom == crop.fBottom) {
                radii[3].set(cornerRadius, cornerRadius);
            } else if (bounds.fLeft > insetCrop.fLeft && bounds.fBottom < insetCrop.fBottom) {
                radii[3].set(0, 0);
            } else {
                intersectionIsRoundRect = false;
            }

            if (intersectionIsRoundRect) {
                SkRRect intersectionBounds;
                intersectionBounds.setRectRadii(bounds, radii);
                return {intersectionBounds, clip};
            }
        }

        // we didn't it any of our fast paths so set the clip to the cropRect
        clip.setRectXY(crop, cornerRadius, cornerRadius);
    }

    // if we hit this point then we either don't have rounded corners or we are going to rely
    // on the clip to round the corners for us
    return {SkRRect::MakeRect(bounds), clip};
}

inline bool SkiaGLRenderEngine::layerHasBlur(const LayerSettings* layer) {
+1 −2
Original line number Diff line number Diff line
@@ -88,8 +88,7 @@ private:
                                                         int hwcFormat, Protection protection);
    inline SkRect getSkRect(const FloatRect& layer);
    inline SkRect getSkRect(const Rect& layer);
    inline SkRRect getRoundedRect(const LayerSettings* layer);
    inline BlurRegion getBlurRegion(const LayerSettings* layer);
    inline std::pair<SkRRect, SkRRect> getBoundsAndClip(const LayerSettings* layer);
    inline bool layerHasBlur(const LayerSettings* layer);
    inline SkColor getSkColor(const vec4& color);
    inline SkM44 getSkM44(const mat4& matrix);
+12 −23
Original line number Diff line number Diff line
@@ -139,23 +139,21 @@ static SkMatrix getShaderTransform(const SkCanvas* canvas, const SkRect& blurRec
    return matrix;
}

void BlurFilter::drawBlurRegion(SkCanvas* canvas, const BlurRegion& effectRegion,
void BlurFilter::drawBlurRegion(SkCanvas* canvas, const SkRRect& effectRegion,
                                const uint32_t blurRadius, const float blurAlpha,
                                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);
    }
    paint.setAlphaf(blurAlpha);

    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) {
    if (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;
@@ -168,30 +166,21 @@ void BlurFilter::drawBlurRegion(SkCanvas* canvas, const BlurRegion& effectRegion
        blurBuilder.child("originalInput") =
                input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linearSampling,
                                  inputMatrix);
        blurBuilder.uniform("mixFactor") = effectRegion.blurRadius / kMaxCrossFadeRadius;
        blurBuilder.uniform("mixFactor") = 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);
    if (effectRegion.isRect()) {
        if (blurAlpha == 1.0f) {
            paint.setBlendMode(SkBlendMode::kSrc);
        }
        canvas->drawRect(effectRegion.rect(), paint);
    } else {
        canvas->drawRect(rect, paint);
        paint.setAntiAlias(true);
        canvas->drawRRect(effectRegion, paint);
    }
}

+13 −3
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@
#include <SkImage.h>
#include <SkRuntimeEffect.h>
#include <SkSurface.h>
#include <ui/BlurRegion.h>

using namespace std;

@@ -52,8 +51,19 @@ public:
    sk_sp<SkImage> generate(GrRecordingContext* context, const uint32_t radius,
                            const sk_sp<SkImage> blurInput, const SkRect& blurRect) const;

    void drawBlurRegion(SkCanvas* canvas, const BlurRegion& blurRegion, const SkRect& blurRect,
                        sk_sp<SkImage> blurredImage, sk_sp<SkImage> input);
    /**
     * Draw the blurred content (from the generate method) into the canvas.
     * @param canvas is the destination/output for the blur
     * @param effectRegion the RoundRect in canvas coordinates that determines the blur coverage
     * @param blurRadius radius of the blur used to determine the intensity of the crossfade effect
     * @param blurAlpha alpha value applied to the effectRegion when the blur is drawn
     * @param blurRect bounds of the blurredImage translated into canvas coordinates
     * @param blurredImage down-sampled blurred content that was produced by the generate() method
     * @param input original unblurred input that is used to crossfade with the blurredImage
     */
    void drawBlurRegion(SkCanvas* canvas, const SkRRect& effectRegion, const uint32_t blurRadius,
                        const float blurAlpha, const SkRect& blurRect, sk_sp<SkImage> blurredImage,
                        sk_sp<SkImage> input);

private:
    sk_sp<SkRuntimeEffect> mBlurEffect;
+50 −0
Original line number Diff line number Diff line
@@ -263,6 +263,11 @@ public:
        }
    }

    void expectBufferColor(const Point& point, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
                           uint8_t tolerance = 0) {
        expectBufferColor(Rect(point.x, point.y, point.x + 1, point.y + 1), r, g, b, a, tolerance);
    }

    void expectBufferColor(const Rect& rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a,
                           uint8_t tolerance = 0) {
        auto colorCompare = [tolerance](const uint8_t* colorA, const uint8_t* colorB) {
@@ -1887,6 +1892,51 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) {
                      0, 255, 0, 255);
}

TEST_P(RenderEngineTest, testRoundedCornersParentCrop) {
    initializeRenderEngine();

    renderengine::DisplaySettings settings;
    settings.physicalDisplay = fullscreenRect();
    settings.clip = fullscreenRect();
    settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;

    std::vector<const renderengine::LayerSettings*> layers;

    renderengine::LayerSettings redLayer;
    redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
    redLayer.geometry.boundaries = fullscreenRect().toFloatRect();
    redLayer.geometry.roundedCornersRadius = 5.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);

    // Green layer with 1/2 size with parent crop rect.
    renderengine::LayerSettings greenLayer = redLayer;
    greenLayer.geometry.boundaries =
            FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT / 2);
    greenLayer.source.solidColor = half3(0.0f, 1.0f, 0.0f);

    layers.push_back(&greenLayer);

    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);

    // top middle should be green and the bottom middle red
    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, 0), 0, 255, 0, 255);
    expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, DEFAULT_DISPLAY_HEIGHT / 2), 255, 0, 0, 255);

    // the bottom edge of the green layer should not be rounded
    expectBufferColor(Point(0, (DEFAULT_DISPLAY_HEIGHT / 2) - 1), 0, 255, 0, 255);
}

TEST_P(RenderEngineTest, testClear) {
    initializeRenderEngine();