1*c8dee2aaSAndroid Build Coastguard Worker /* 2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2020 Google LLC 3*c8dee2aaSAndroid Build Coastguard Worker * 4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be 5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file. 6*c8dee2aaSAndroid Build Coastguard Worker */ 7*c8dee2aaSAndroid Build Coastguard Worker 8*c8dee2aaSAndroid Build Coastguard Worker #include "gm/gm.h" 9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkCanvas.h" 10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkData.h" 11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPaint.h" 12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSize.h" 13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkString.h" 14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSurface.h" 15*c8dee2aaSAndroid Build Coastguard Worker #include "include/effects/SkRuntimeEffect.h" 16*c8dee2aaSAndroid Build Coastguard Worker #include "tools/DecodeUtils.h" 17*c8dee2aaSAndroid Build Coastguard Worker #include "tools/Resources.h" 18*c8dee2aaSAndroid Build Coastguard Worker 19*c8dee2aaSAndroid Build Coastguard Worker class KawaseBlurFilter { 20*c8dee2aaSAndroid Build Coastguard Worker public: 21*c8dee2aaSAndroid Build Coastguard Worker // Downsample scale factor used to improve performance 22*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kInputScale = 0.25f; 23*c8dee2aaSAndroid Build Coastguard Worker // Downsample scale factor used to improve performance 24*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kInverseInputScale = 1.0f / kInputScale; 25*c8dee2aaSAndroid Build Coastguard Worker // Maximum number of render passes 26*c8dee2aaSAndroid Build Coastguard Worker static constexpr uint32_t kMaxPasses = 4; 27*c8dee2aaSAndroid Build Coastguard Worker // To avoid downscaling artifacts, we interpolate the blurred fbo with the full composited 28*c8dee2aaSAndroid Build Coastguard Worker // image, up to this radius. 29*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kMaxCrossFadeRadius = 30.0f; 30*c8dee2aaSAndroid Build Coastguard Worker KawaseBlurFilter()31*c8dee2aaSAndroid Build Coastguard Worker KawaseBlurFilter() { 32*c8dee2aaSAndroid Build Coastguard Worker SkString blurString(R"( 33*c8dee2aaSAndroid Build Coastguard Worker uniform shader src; 34*c8dee2aaSAndroid Build Coastguard Worker uniform float in_inverseScale; 35*c8dee2aaSAndroid Build Coastguard Worker uniform float2 in_blurOffset; 36*c8dee2aaSAndroid Build Coastguard Worker 37*c8dee2aaSAndroid Build Coastguard Worker half4 main(float2 xy) { 38*c8dee2aaSAndroid Build Coastguard Worker float2 scaled_xy = float2(xy.x * in_inverseScale, xy.y * in_inverseScale); 39*c8dee2aaSAndroid Build Coastguard Worker 40*c8dee2aaSAndroid Build Coastguard Worker half4 c = src.eval(scaled_xy); 41*c8dee2aaSAndroid Build Coastguard Worker c += src.eval(scaled_xy + float2( in_blurOffset.x, in_blurOffset.y)); 42*c8dee2aaSAndroid Build Coastguard Worker c += src.eval(scaled_xy + float2( in_blurOffset.x, -in_blurOffset.y)); 43*c8dee2aaSAndroid Build Coastguard Worker c += src.eval(scaled_xy + float2(-in_blurOffset.x, in_blurOffset.y)); 44*c8dee2aaSAndroid Build Coastguard Worker c += src.eval(scaled_xy + float2(-in_blurOffset.x, -in_blurOffset.y)); 45*c8dee2aaSAndroid Build Coastguard Worker 46*c8dee2aaSAndroid Build Coastguard Worker return half4(c.rgb * 0.2, 1.0); 47*c8dee2aaSAndroid Build Coastguard Worker } 48*c8dee2aaSAndroid Build Coastguard Worker )"); 49*c8dee2aaSAndroid Build Coastguard Worker 50*c8dee2aaSAndroid Build Coastguard Worker SkString mixString(R"( 51*c8dee2aaSAndroid Build Coastguard Worker uniform shader in_blur; 52*c8dee2aaSAndroid Build Coastguard Worker uniform shader in_original; 53*c8dee2aaSAndroid Build Coastguard Worker uniform float in_inverseScale; 54*c8dee2aaSAndroid Build Coastguard Worker uniform float in_mix; 55*c8dee2aaSAndroid Build Coastguard Worker 56*c8dee2aaSAndroid Build Coastguard Worker half4 main(float2 xy) { 57*c8dee2aaSAndroid Build Coastguard Worker float2 scaled_xy = float2(xy.x * in_inverseScale, xy.y * in_inverseScale); 58*c8dee2aaSAndroid Build Coastguard Worker 59*c8dee2aaSAndroid Build Coastguard Worker half4 blurred = in_blur.eval(scaled_xy); 60*c8dee2aaSAndroid Build Coastguard Worker half4 composition = in_original.eval(xy); 61*c8dee2aaSAndroid Build Coastguard Worker return mix(composition, blurred, in_mix); 62*c8dee2aaSAndroid Build Coastguard Worker } 63*c8dee2aaSAndroid Build Coastguard Worker )"); 64*c8dee2aaSAndroid Build Coastguard Worker 65*c8dee2aaSAndroid Build Coastguard Worker auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString); 66*c8dee2aaSAndroid Build Coastguard Worker if (!blurEffect) { 67*c8dee2aaSAndroid Build Coastguard Worker SkDEBUGFAILF("RuntimeShader error: %s\n", error.c_str()); 68*c8dee2aaSAndroid Build Coastguard Worker } 69*c8dee2aaSAndroid Build Coastguard Worker fBlurEffect = std::move(blurEffect); 70*c8dee2aaSAndroid Build Coastguard Worker 71*c8dee2aaSAndroid Build Coastguard Worker auto [mixEffect, error2] = SkRuntimeEffect::MakeForShader(mixString); 72*c8dee2aaSAndroid Build Coastguard Worker if (!mixEffect) { 73*c8dee2aaSAndroid Build Coastguard Worker SkDEBUGFAILF("RuntimeShader error: %s\n", error2.c_str()); 74*c8dee2aaSAndroid Build Coastguard Worker } 75*c8dee2aaSAndroid Build Coastguard Worker fMixEffect = std::move(mixEffect); 76*c8dee2aaSAndroid Build Coastguard Worker } 77*c8dee2aaSAndroid Build Coastguard Worker MakeSurface(SkCanvas * canvas,const SkImageInfo & info)78*c8dee2aaSAndroid Build Coastguard Worker static sk_sp<SkSurface> MakeSurface(SkCanvas* canvas, const SkImageInfo& info) { 79*c8dee2aaSAndroid Build Coastguard Worker if (sk_sp<SkSurface> surface = canvas->makeSurface(info)) { 80*c8dee2aaSAndroid Build Coastguard Worker return surface; 81*c8dee2aaSAndroid Build Coastguard Worker } 82*c8dee2aaSAndroid Build Coastguard Worker // serialize-8888 returns null from makeSurface; fallback to a raster surface. 83*c8dee2aaSAndroid Build Coastguard Worker return SkSurfaces::Raster(info); 84*c8dee2aaSAndroid Build Coastguard Worker } 85*c8dee2aaSAndroid Build Coastguard Worker draw(SkCanvas * canvas,sk_sp<SkImage> input,int blurRadius)86*c8dee2aaSAndroid Build Coastguard Worker void draw(SkCanvas* canvas, sk_sp<SkImage> input, int blurRadius) { 87*c8dee2aaSAndroid Build Coastguard Worker // NOTE: this is only experimental and the current blur params cause points to be sampled 88*c8dee2aaSAndroid Build Coastguard Worker // beyond the input blur radius. 89*c8dee2aaSAndroid Build Coastguard Worker 90*c8dee2aaSAndroid Build Coastguard Worker // Kawase is an approximation of Gaussian, but it behaves differently from it. 91*c8dee2aaSAndroid Build Coastguard Worker // A radius transformation is required for approximating them, and also to introduce 92*c8dee2aaSAndroid Build Coastguard Worker // non-integer steps, necessary to smoothly interpolate large radii. 93*c8dee2aaSAndroid Build Coastguard Worker float tmpRadius = (float)blurRadius / 6.0f; 94*c8dee2aaSAndroid Build Coastguard Worker float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius)); 95*c8dee2aaSAndroid Build Coastguard Worker float radiusByPasses = tmpRadius / (float)numberOfPasses; 96*c8dee2aaSAndroid Build Coastguard Worker 97*c8dee2aaSAndroid Build Coastguard Worker SkImageInfo scaledInfo = SkImageInfo::MakeN32Premul((float)input->width() * kInputScale, 98*c8dee2aaSAndroid Build Coastguard Worker (float)input->height() * kInputScale); 99*c8dee2aaSAndroid Build Coastguard Worker auto drawSurface = MakeSurface(canvas, scaledInfo); 100*c8dee2aaSAndroid Build Coastguard Worker 101*c8dee2aaSAndroid Build Coastguard Worker const float stepX = radiusByPasses; 102*c8dee2aaSAndroid Build Coastguard Worker const float stepY = radiusByPasses; 103*c8dee2aaSAndroid Build Coastguard Worker 104*c8dee2aaSAndroid Build Coastguard Worker // start by drawing and downscaling and doing the first blur pass 105*c8dee2aaSAndroid Build Coastguard Worker SkRuntimeShaderBuilder blurBuilder(fBlurEffect); 106*c8dee2aaSAndroid Build Coastguard Worker blurBuilder.child("src") = input->makeShader(SkSamplingOptions(SkFilterMode::kLinear)); 107*c8dee2aaSAndroid Build Coastguard Worker blurBuilder.uniform("in_inverseScale") = kInverseInputScale; 108*c8dee2aaSAndroid Build Coastguard Worker blurBuilder.uniform("in_blurOffset") = SkV2{stepX * kInverseInputScale, 109*c8dee2aaSAndroid Build Coastguard Worker stepY * kInverseInputScale}; 110*c8dee2aaSAndroid Build Coastguard Worker SkPaint paint; 111*c8dee2aaSAndroid Build Coastguard Worker paint.setShader(blurBuilder.makeShader()); 112*c8dee2aaSAndroid Build Coastguard Worker drawSurface->getCanvas()->drawIRect(scaledInfo.bounds(), paint); 113*c8dee2aaSAndroid Build Coastguard Worker 114*c8dee2aaSAndroid Build Coastguard Worker // DEBUG draw each of the stages 115*c8dee2aaSAndroid Build Coastguard Worker canvas->save(); 116*c8dee2aaSAndroid Build Coastguard Worker canvas->drawImage(drawSurface->makeImageSnapshot(), input->width() / 4, 0, 117*c8dee2aaSAndroid Build Coastguard Worker SkSamplingOptions()); 118*c8dee2aaSAndroid Build Coastguard Worker canvas->translate(input->width() / 4, input->height() * 0.75); 119*c8dee2aaSAndroid Build Coastguard Worker 120*c8dee2aaSAndroid Build Coastguard Worker // And now we'll ping pong between our surfaces, to accumulate the result of various 121*c8dee2aaSAndroid Build Coastguard Worker // offsets. 122*c8dee2aaSAndroid Build Coastguard Worker auto lastDrawTarget = drawSurface; 123*c8dee2aaSAndroid Build Coastguard Worker if (numberOfPasses > 1) { 124*c8dee2aaSAndroid Build Coastguard Worker auto readSurface = drawSurface; 125*c8dee2aaSAndroid Build Coastguard Worker drawSurface = MakeSurface(canvas, scaledInfo); 126*c8dee2aaSAndroid Build Coastguard Worker 127*c8dee2aaSAndroid Build Coastguard Worker for (auto i = 1; i < numberOfPasses; i++) { 128*c8dee2aaSAndroid Build Coastguard Worker const float stepScale = (float)i * kInputScale; 129*c8dee2aaSAndroid Build Coastguard Worker 130*c8dee2aaSAndroid Build Coastguard Worker blurBuilder.child("src") = readSurface->makeImageSnapshot()->makeShader( 131*c8dee2aaSAndroid Build Coastguard Worker SkSamplingOptions(SkFilterMode::kLinear)); 132*c8dee2aaSAndroid Build Coastguard Worker blurBuilder.uniform("in_inverseScale") = 1.0f; 133*c8dee2aaSAndroid Build Coastguard Worker blurBuilder.uniform("in_blurOffset") = SkV2{stepX * stepScale , stepY * stepScale}; 134*c8dee2aaSAndroid Build Coastguard Worker 135*c8dee2aaSAndroid Build Coastguard Worker paint.setShader(blurBuilder.makeShader()); 136*c8dee2aaSAndroid Build Coastguard Worker drawSurface->getCanvas()->drawIRect(scaledInfo.bounds(), paint); 137*c8dee2aaSAndroid Build Coastguard Worker 138*c8dee2aaSAndroid Build Coastguard Worker // DEBUG draw each of the stages 139*c8dee2aaSAndroid Build Coastguard Worker canvas->drawImage(drawSurface->makeImageSnapshot(), 0, 0, SkSamplingOptions()); 140*c8dee2aaSAndroid Build Coastguard Worker canvas->translate(0, input->height() * 0.75); 141*c8dee2aaSAndroid Build Coastguard Worker 142*c8dee2aaSAndroid Build Coastguard Worker // Swap buffers for next iteration 143*c8dee2aaSAndroid Build Coastguard Worker auto tmp = drawSurface; 144*c8dee2aaSAndroid Build Coastguard Worker drawSurface = readSurface; 145*c8dee2aaSAndroid Build Coastguard Worker readSurface = tmp; 146*c8dee2aaSAndroid Build Coastguard Worker } 147*c8dee2aaSAndroid Build Coastguard Worker lastDrawTarget = readSurface; 148*c8dee2aaSAndroid Build Coastguard Worker } 149*c8dee2aaSAndroid Build Coastguard Worker 150*c8dee2aaSAndroid Build Coastguard Worker // restore translations done for debug and offset 151*c8dee2aaSAndroid Build Coastguard Worker canvas->restore(); 152*c8dee2aaSAndroid Build Coastguard Worker SkAutoCanvasRestore acr(canvas, true); 153*c8dee2aaSAndroid Build Coastguard Worker canvas->translate(input->width(), 0); 154*c8dee2aaSAndroid Build Coastguard Worker 155*c8dee2aaSAndroid Build Coastguard Worker // do the final composition and when we scale our blur up. It will be interpolated 156*c8dee2aaSAndroid Build Coastguard Worker // with the larger composited texture to hide downscaling artifacts. 157*c8dee2aaSAndroid Build Coastguard Worker SkRuntimeShaderBuilder mixBuilder(fMixEffect); 158*c8dee2aaSAndroid Build Coastguard Worker mixBuilder.child("in_blur") = lastDrawTarget->makeImageSnapshot()->makeShader( 159*c8dee2aaSAndroid Build Coastguard Worker SkSamplingOptions(SkFilterMode::kLinear)); 160*c8dee2aaSAndroid Build Coastguard Worker mixBuilder.child("in_original") = 161*c8dee2aaSAndroid Build Coastguard Worker input->makeShader(SkSamplingOptions(SkFilterMode::kLinear)); 162*c8dee2aaSAndroid Build Coastguard Worker mixBuilder.uniform("in_inverseScale") = kInputScale; 163*c8dee2aaSAndroid Build Coastguard Worker mixBuilder.uniform("in_mix") = std::min(1.0f, (float)blurRadius / kMaxCrossFadeRadius); 164*c8dee2aaSAndroid Build Coastguard Worker 165*c8dee2aaSAndroid Build Coastguard Worker paint.setShader(mixBuilder.makeShader()); 166*c8dee2aaSAndroid Build Coastguard Worker canvas->drawIRect(input->bounds(), paint); 167*c8dee2aaSAndroid Build Coastguard Worker } 168*c8dee2aaSAndroid Build Coastguard Worker 169*c8dee2aaSAndroid Build Coastguard Worker private: 170*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkRuntimeEffect> fBlurEffect; 171*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkRuntimeEffect> fMixEffect; 172*c8dee2aaSAndroid Build Coastguard Worker }; 173*c8dee2aaSAndroid Build Coastguard Worker 174*c8dee2aaSAndroid Build Coastguard Worker class KawaseBlurRT : public skiagm::GM { 175*c8dee2aaSAndroid Build Coastguard Worker public: KawaseBlurRT()176*c8dee2aaSAndroid Build Coastguard Worker KawaseBlurRT() {} getName() const177*c8dee2aaSAndroid Build Coastguard Worker SkString getName() const override { return SkString("kawase_blur_rt"); } getISize()178*c8dee2aaSAndroid Build Coastguard Worker SkISize getISize() override { return {1280, 768}; } 179*c8dee2aaSAndroid Build Coastguard Worker onOnceBeforeDraw()180*c8dee2aaSAndroid Build Coastguard Worker void onOnceBeforeDraw() override { 181*c8dee2aaSAndroid Build Coastguard Worker fMandrill = ToolUtils::GetResourceAsImage("images/mandrill_256.png"); 182*c8dee2aaSAndroid Build Coastguard Worker } 183*c8dee2aaSAndroid Build Coastguard Worker onDraw(SkCanvas * canvas)184*c8dee2aaSAndroid Build Coastguard Worker void onDraw(SkCanvas* canvas) override { 185*c8dee2aaSAndroid Build Coastguard Worker canvas->drawImage(fMandrill, 0, 0); 186*c8dee2aaSAndroid Build Coastguard Worker canvas->translate(256, 0); 187*c8dee2aaSAndroid Build Coastguard Worker KawaseBlurFilter blurFilter; 188*c8dee2aaSAndroid Build Coastguard Worker blurFilter.draw(canvas, fMandrill, 45); 189*c8dee2aaSAndroid Build Coastguard Worker canvas->translate(512, 0); 190*c8dee2aaSAndroid Build Coastguard Worker blurFilter.draw(canvas, fMandrill, 55); 191*c8dee2aaSAndroid Build Coastguard Worker } 192*c8dee2aaSAndroid Build Coastguard Worker 193*c8dee2aaSAndroid Build Coastguard Worker private: 194*c8dee2aaSAndroid Build Coastguard Worker sk_sp<SkImage> fMandrill; 195*c8dee2aaSAndroid Build Coastguard Worker }; 196*c8dee2aaSAndroid Build Coastguard Worker DEF_GM(return new KawaseBlurRT;) 197