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

Commit 8423741c authored by Shan Huang's avatar Shan Huang Committed by Android (Google) Code Review
Browse files

Merge changes I7e4552d0,Idef0064b into main

* changes:
  Used fixed radii during downsampling and dynamic radii during upsampling.
  Add a flag for Kawase2 aliasing fix.
parents 352a79dd 6ab0410f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -107,6 +107,7 @@ filegroup {
        "skia/filters/GainmapFactory.cpp",
        "skia/filters/GaussianBlurFilter.cpp",
        "skia/filters/KawaseBlurDualFilter.cpp",
        "skia/filters/KawaseBlurDualFilterV2.cpp",
        "skia/filters/KawaseBlurFilter.cpp",
        "skia/filters/LutShader.cpp",
        "skia/filters/MouriMap.cpp",
+6 −1
Original line number Diff line number Diff line
@@ -80,6 +80,7 @@
#include "filters/GainmapFactory.h"
#include "filters/GaussianBlurFilter.h"
#include "filters/KawaseBlurDualFilter.h"
#include "filters/KawaseBlurDualFilterV2.h"
#include "filters/KawaseBlurFilter.h"
#include "filters/LutShader.h"
#include "filters/MouriMap.h"
@@ -303,7 +304,11 @@ SkiaRenderEngine::SkiaRenderEngine(Threaded threaded, PixelFormat pixelFormat,
        }
        case BlurAlgorithm::KAWASE_DUAL_FILTER: {
            ALOGD("Background Blurs Enabled (Kawase dual-filtering algorithm)");
            if (FlagManager::getInstance().window_blur_kawase2_fix_aliasing()) {
                mBlurFilter = new KawaseBlurDualFilterV2(mRuntimeEffectManager);
            } else {
                mBlurFilter = new KawaseBlurDualFilter(mRuntimeEffectManager);
            }
            break;
        }
        default: {
+249 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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.
 */

#define ATRACE_TAG ATRACE_TAG_GRAPHICS

#include "KawaseBlurDualFilterV2.h"
#include <SkAlphaType.h>
#include <SkBlendMode.h>
#include <SkCanvas.h>
#include <SkData.h>
#include <SkPaint.h>
#include <SkRRect.h>
#include <SkRuntimeEffect.h>
#include <SkShader.h>
#include <SkSize.h>
#include <SkString.h>
#include <SkSurface.h>
#include <SkTileMode.h>
#include <include/gpu/GpuTypes.h>
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <log/log.h>
#include <utils/Trace.h>

#include "RuntimeEffectManager.h"

namespace android {
namespace renderengine {
namespace skia {

KawaseBlurDualFilterV2::KawaseBlurDualFilterV2(RuntimeEffectManager& effectManager)
      : BlurFilter(effectManager) {
    // Samples each vertex of a diamond using a total of 4 samples.
    // This shader is used for the initial 4x down-sampling pass, with the sampling offset
    // handpicked to minimize aliasing.
    const SkString kQuarterResDownSampleBlurString(R"(
        uniform shader child;

        const float2 STEP_0 = float2( 0.25, 0.25);
        const float2 STEP_1 = float2( 0.25, -0.25);
        const float2 STEP_2 = float2(-0.25, -0.25);
        const float2 STEP_3 = float2(-0.25, 0.25);

        half4 main(float2 xy) {
            half3 c = child.eval(xy + STEP_0).rgb;
            c += child.eval(xy + STEP_1).rgb;
            c += child.eval(xy + STEP_2).rgb;
            c += child.eval(xy + STEP_3).rgb;

            return half4(c * 0.25, 1.0);
        }
    )");

    // Samples each vertex of a diamond plus the center pixel, using a total of 5 samples.
    // This shader is used for the 2x down-sampling passes after the initial pass.
    const SkString kHalfResDownSampleBlurString(R"(
        uniform shader child;

        const float2 STEP_0 = float2( 0.5, 0.5);
        const float2 STEP_1 = float2( 0.5, -0.5);
        const float2 STEP_2 = float2(-0.5, -0.5);
        const float2 STEP_3 = float2(-0.5, 0.5);

        half4 main(float2 xy) {
            half3 c = child.eval(xy).rgb * 4.0;
            c += child.eval(xy + STEP_0).rgb;
            c += child.eval(xy + STEP_1).rgb;
            c += child.eval(xy + STEP_2).rgb;
            c += child.eval(xy + STEP_3).rgb;

            return half4(c * 0.125, 1.0);
        }
    )");

    // A shader to sample each vertex of a unit regular heptagon, plus the original fragment
    // coordinate, using a total of 8 samples.
    const SkString kUpSampleBlurString(R"(
        uniform shader child;
        uniform float in_blurOffset;
        uniform float in_crossFade;
        uniform float in_weightedCrossFade;

        const float2 STEP_0 = float2( 1.0, 0.0);
        const float2 STEP_1 = float2( 0.623489802,  0.781831482);
        const float2 STEP_2 = float2(-0.222520934,  0.974927912);
        const float2 STEP_3 = float2(-0.900968868,  0.433883739);
        const float2 STEP_4 = float2( 0.900968868, -0.433883739);
        const float2 STEP_5 = float2(-0.222520934, -0.974927912);
        const float2 STEP_6 = float2(-0.623489802, -0.781831482);

        half4 main(float2 xy) {
            half3 c = child.eval(xy).rgb;

            c += child.eval(xy + STEP_0 * in_blurOffset).rgb;
            c += child.eval(xy + STEP_1 * in_blurOffset).rgb;
            c += child.eval(xy + STEP_2 * in_blurOffset).rgb;
            c += child.eval(xy + STEP_3 * in_blurOffset).rgb;
            c += child.eval(xy + STEP_4 * in_blurOffset).rgb;
            c += child.eval(xy + STEP_5 * in_blurOffset).rgb;
            c += child.eval(xy + STEP_6 * in_blurOffset).rgb;

            return half4(c * in_weightedCrossFade, in_crossFade);
        }
    )");

    mQuarterResDownSampleBlurEffect = effectManager.createAndStoreRuntimeEffect(
            RuntimeEffectManager::KnownId::kKawaseBlurDualFilterV2_QuarterResDownSampleBlurEffect,
            "KawaseBlurDualFilterV2_QuarterResDownSampleBlurEffect",
            kQuarterResDownSampleBlurString);
    mHalfResDownSampleBlurEffect = effectManager.createAndStoreRuntimeEffect(
            RuntimeEffectManager::KnownId::kKawaseBlurDualFilterV2_HalfResDownSampleBlurEffect,
            "KawaseBlurDualFilterV2_HalfResDownSampleBlurEffect", kHalfResDownSampleBlurString);
    mUpSampleBlurEffect =
            effectManager
                    .createAndStoreRuntimeEffect(RuntimeEffectManager::KnownId::
                                                         kKawaseBlurDualFilterV2_UpSampleBlurEffect,
                                                 "KawaseBlurDualFilterV2_UpSampleBlurEffect",
                                                 kUpSampleBlurString);
}

void KawaseBlurDualFilterV2::blurInto(const sk_sp<SkSurface>& drawSurface,
                                      const sk_sp<SkImage>& readImage, const float radius,
                                      const float alpha,
                                      const sk_sp<SkRuntimeEffect>& blurEffect) const {
    const float scale = static_cast<float>(drawSurface->width()) / readImage->width();
    SkMatrix blurMatrix = SkMatrix::Scale(scale, scale);
    blurInto(drawSurface,
             readImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                   SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
                                   blurMatrix),
             radius, alpha, blurEffect);
}

void KawaseBlurDualFilterV2::blurInto(const sk_sp<SkSurface>& drawSurface, sk_sp<SkShader> input,
                                      const float radius, const float alpha,
                                      const sk_sp<SkRuntimeEffect>& blurEffect) const {
    SkPaint paint;
    if (blurEffect == mUpSampleBlurEffect) {
        if (radius == 0) {
            paint.setShader(std::move(input));
            paint.setAlphaf(alpha);
        } else {
            SkRuntimeShaderBuilder blurBuilder(blurEffect);
            blurBuilder.child("child") = std::move(input);
            if (blurEffect == mUpSampleBlurEffect) {
                blurBuilder.uniform("in_crossFade") = alpha;
                blurBuilder.uniform("in_weightedCrossFade") = alpha * 0.125f;
                blurBuilder.uniform("in_blurOffset") = radius;
            }
            paint.setShader(blurBuilder.makeShader(nullptr));
        }
    } else {
        SkRuntimeShaderBuilder blurBuilder(blurEffect);
        blurBuilder.child("child") = std::move(input);
        paint.setShader(blurBuilder.makeShader(nullptr));
    }
    paint.setBlendMode(alpha == 1.0f ? SkBlendMode::kSrc : SkBlendMode::kSrcOver);
    drawSurface->getCanvas()->drawPaint(paint);
}

sk_sp<SkImage> KawaseBlurDualFilterV2::generate(SkiaGpuContext* context, const uint32_t blurRadius,
                                                const sk_sp<SkImage> input,
                                                const SkRect& blurRect) const {
    // Apply a conversion factor of (1 / sqrt(3)) to match Skia's built-in blur as used by
    // RenderEffect. See the comment in SkBlurMask.cpp for reasoning behind this.
    const float radius = blurRadius * 0.57735f;

    // Use a variable number of blur passes depending on the radius. The non-integer part of this
    // calculation is used to mix the final pass into the second-last with an alpha blend.
    constexpr int kMaxSurfaces = 4;
    const float filterDepth = std::min(kMaxSurfaces - 1.0f, radius * kInputScale / 2.5f);
    const int filterPasses = std::min(kMaxSurfaces - 1, static_cast<int>(ceil(filterDepth)));

    auto makeSurface = [&](float scale) -> sk_sp<SkSurface> {
        const auto newW = ceil(static_cast<float>(blurRect.width() / scale));
        const auto newH = ceil(static_cast<float>(blurRect.height() / scale));
        sk_sp<SkSurface> surface =
                context->createRenderTarget(input->imageInfo().makeWH(newW, newH));
        LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__);
        return surface;
    };

    // Render into surfaces downscaled by 1x, 2x, 4x and 8x from the initial downscale.
    sk_sp<SkSurface> surfaces[kMaxSurfaces] =
            {filterPasses >= 0 ? makeSurface(1 * kInverseInputScale) : nullptr,
             filterPasses >= 1 ? makeSurface(2 * kInverseInputScale) : nullptr,
             filterPasses >= 2 ? makeSurface(4 * kInverseInputScale) : nullptr,
             filterPasses >= 3 ? makeSurface(8 * kInverseInputScale) : nullptr};

    // Kawase is an approximation of Gaussian, but behaves differently because it is made up of many
    // simpler blurs. A transformation is required to approximate the same effect as Gaussian.
    float sumSquaredR = 0;
    float sumSquaredStep = 0;
    for (int i = 0; i < filterPasses; i++) {
        const float alpha = std::min(1.0f, filterDepth - i);
        sumSquaredR += powf(powf(2.0f, i - 1) * alpha * M_SQRT2, 2.0f);
        sumSquaredStep += powf(powf(2.0f, i) * alpha, 2.0f);
    }
    // Solve for R = sqrt(sum(r_i^2)).
    float step = sqrt(max(0.0f, powf(radius * kInputScale, 2) - sumSquaredR) /
                      (sumSquaredStep == 0 ? 1 : sumSquaredStep));

    // Start by downscaling and doing the first blur pass
    {
        // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
        // case one may expect Translate(blurRect.fLeft, blurRect.fTop) * Scale(kInverseInputScale)
        // but instead we must do the inverse.
        SkMatrix blurMatrix = SkMatrix::Translate(-blurRect.fLeft, -blurRect.fTop);
        blurMatrix.postScale(kInputScale, kInputScale);
        const auto sourceShader =
                input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
                                  SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone),
                                  blurMatrix);
        blurInto(surfaces[0], std::move(sourceShader),
                 0, // unused blur radius. The blur effect hardcodes the radius.
                 1.0f, mQuarterResDownSampleBlurEffect);
    }

    // Next the remaining downscale blur passes.
    for (int i = 0; i < filterPasses; i++) {
        blurInto(surfaces[i + 1], surfaces[i]->makeTemporaryImage(),
                 0, // unused blur radius. The blur effect hardcodes the radius.
                 1.0f, mHalfResDownSampleBlurEffect);
    }
    // Finally blur+upscale back to our original size.
    for (int i = filterPasses - 1; i >= 0; i--) {
        int index = kMaxSurfaces * 2 - 2 - i;
        blurInto(surfaces[i], surfaces[i + 1]->makeTemporaryImage(), step,
                 std::min(1.0f, filterDepth - i), mUpSampleBlurEffect);
    }

    return surfaces[0]->makeTemporaryImage();
}

} // namespace skia
} // namespace renderengine
} // namespace android
+61 −0
Original line number Diff line number Diff line
/*
 * Copyright 2025 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 <SkImage.h>
#include <SkRuntimeEffect.h>
#include <SkSurface.h>
#include "BlurFilter.h"

#include "RuntimeEffectManager.h"

namespace android {
namespace renderengine {
namespace skia {

/**
 * This is an implementation of a Kawase blur with dual-filtering passes, as described in here:
 * https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_slides.pdf
 * https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
 * It uses fixed blur radii in the downsampling passes to minimize aliasing, and dynamic radii
 * in the upsampling passes to produce sufficiently smooth blurs and support animation.
 */
class KawaseBlurDualFilterV2 : public BlurFilter {
public:
    explicit KawaseBlurDualFilterV2(RuntimeEffectManager& effectManager);
    virtual ~KawaseBlurDualFilterV2() {}

    // Execute blur, saving it to a texture
    sk_sp<SkImage> generate(SkiaGpuContext* context, const uint32_t radius,
                            const sk_sp<SkImage> blurInput, const SkRect& blurRect) const override;

private:
    sk_sp<SkRuntimeEffect> mQuarterResDownSampleBlurEffect;
    sk_sp<SkRuntimeEffect> mHalfResDownSampleBlurEffect;
    sk_sp<SkRuntimeEffect> mUpSampleBlurEffect;

    void blurInto(const sk_sp<SkSurface>& drawSurface, const sk_sp<SkImage>& readImage,
                  const float radius, const float alpha, const sk_sp<SkRuntimeEffect>&) const;

    void blurInto(const sk_sp<SkSurface>& drawSurface, const sk_sp<SkShader> input,
                  const float radius, const float alpha, const sk_sp<SkRuntimeEffect>&) const;
};

} // namespace skia
} // namespace renderengine
} // namespace android
+3 −0
Original line number Diff line number Diff line
@@ -47,6 +47,9 @@ public:
        kGainmapEffect,
        kKawaseBlurDualFilter_LowSampleBlurEffect,
        kKawaseBlurDualFilter_HighSampleBlurEffect,
        kKawaseBlurDualFilterV2_QuarterResDownSampleBlurEffect,
        kKawaseBlurDualFilterV2_HalfResDownSampleBlurEffect,
        kKawaseBlurDualFilterV2_UpSampleBlurEffect,
        kKawaseBlurEffect,
        kLutEffect,
        kMouriMap_CrossTalkAndChunk16x16Effect,
Loading