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