Add special case circle blur for Ganesh

This makes the blurcircles bench go from ~33us to ~8us on Windows desktop.

It will require layout test suppressions

Review URL: https://codereview.chromium.org/1311583005
This commit is contained in:
robertphillips 2015-09-15 10:20:55 -07:00 коммит произвёл Commit bot
Родитель de5973b05b
Коммит 30c4cae7d3
16 изменённых файлов: 494 добавлений и 125 удалений

Просмотреть файл

@ -14,37 +14,43 @@
class BlurCirclesGM : public skiagm::GM {
public:
BlurCirclesGM()
: fName("blurcircles") {
}
BlurCirclesGM() { }
protected:
bool runAsBench() const override { return true; }
SkString onShortName() override {
return fName;
return SkString("blurcircles");
}
SkISize onISize() override {
return SkISize::Make(950, 950);
}
void onOnceBeforeDraw() override {
const float blurRadii[kNumBlurs] = { 1,5,10,20 };
for (int i = 0; i < kNumBlurs; ++i) {
fBlurFilters[i].reset(SkBlurMaskFilter::Create(
kNormal_SkBlurStyle,
SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(blurRadii[i])),
SkBlurMaskFilter::kHighQuality_BlurFlag));
}
}
void onDraw(SkCanvas* canvas) override {
canvas->scale(1.5f, 1.5f);
canvas->translate(50,50);
const float blurRadii[] = { 1,5,10,20 };
const int circleRadii[] = { 5,10,25,50 };
for (size_t i = 0; i < SK_ARRAY_COUNT(blurRadii); ++i) {
for (size_t i = 0; i < kNumBlurs; ++i) {
SkAutoCanvasRestore autoRestore(canvas, true);
canvas->translate(0, SkIntToScalar(150*i));
for (size_t j = 0; j < SK_ARRAY_COUNT(circleRadii); ++j) {
SkMaskFilter* filter = SkBlurMaskFilter::Create(
kNormal_SkBlurStyle,
SkBlurMask::ConvertRadiusToSigma(SkIntToScalar(blurRadii[i])),
SkBlurMaskFilter::kHighQuality_BlurFlag);
SkPaint paint;
paint.setColor(SK_ColorBLACK);
paint.setMaskFilter(filter)->unref();
paint.setMaskFilter(fBlurFilters[i]);
canvas->drawCircle(SkIntToScalar(50),SkIntToScalar(50),SkIntToScalar(circleRadii[j]),paint);
canvas->translate(SkIntToScalar(150), 0);
@ -52,7 +58,9 @@ protected:
}
}
private:
const SkString fName;
static const int kNumBlurs = 4;
SkAutoTUnref<SkMaskFilter> fBlurFilters[kNumBlurs];
typedef skiagm::GM INHERITED;
};

Просмотреть файл

@ -11,6 +11,9 @@
#
{
'sources': [
'<(skia_src_path)/effects/GrCircleBlurFragmentProcessor.cpp',
'<(skia_src_path)/effects/GrCircleBlurFragmentProcessor.h',
'<(skia_src_path)/effects/Sk1DPathEffect.cpp',
'<(skia_src_path)/effects/Sk2DPathEffect.cpp',
'<(skia_src_path)/effects/SkAlphaThresholdFilter.cpp',

Просмотреть файл

@ -218,6 +218,16 @@ static inline int SkAlphaBlend255(S16CPU src, S16CPU dst, U8CPU alpha) {
return dst + prod;
}
static inline U8CPU SkUnitScalarClampToByte(SkScalar x) {
if (x < 0) {
return 0;
}
if (x >= SK_Scalar1) {
return 255;
}
return SkScalarToFixed(x) >> 8;
}
#define SK_R16_BITS 5
#define SK_G16_BITS 6
#define SK_B16_BITS 5

Просмотреть файл

@ -78,17 +78,31 @@ public:
/**
* If asFragmentProcessor() fails the filter may be implemented on the GPU by a subclass
* overriding filterMaskGPU (declared below). That code path requires constructing a src mask
* as input. Since that is a potentially expensive operation, the subclass must also override
* this function to indicate whether filterTextureMaskGPU would succeeed if the mask were to be
* created.
* overriding filterMaskGPU (declared below). That code path requires constructing a
* src mask as input. Since that is a potentially expensive operation, the subclass must also
* override this function to indicate whether filterTextureMaskGPU would succeeed if the mask
* were to be created.
*
* 'maskRect' returns the device space portion of the mask that the filter needs. The mask
* passed into 'filterMaskGPU' should have the same extent as 'maskRect' but be translated
* to the upper-left corner of the mask (i.e., (maskRect.fLeft, maskRect.fTop) appears at
* (0, 0) in the mask).
* passed into 'filterMaskGPU' should have the same extent as 'maskRect' but be
* translated to the upper-left corner of the mask (i.e., (maskRect.fLeft, maskRect.fTop)
* appears at (0, 0) in the mask).
*
* Logically, how this works is:
* canFilterMaskGPU is called
* if (it returns true)
* the returned mask rect is used for quick rejecting
* either directFilterMaskGPU or directFilterRRectMaskGPU is then called
* if (neither of them handle the blur)
* the mask rect is used to generate the mask
* filterMaskGPU is called to filter the mask
*
* TODO: this should work as:
* if (canFilterMaskGPU(devShape, ...)) // rect, rrect, drrect, path
* filterMaskGPU(devShape, ...)
* this would hide the RRect special case and the mask generation
*/
virtual bool canFilterMaskGPU(const SkRect& devBounds,
virtual bool canFilterMaskGPU(const SkRRect& devRRect,
const SkIRect& clipBounds,
const SkMatrix& ctm,
SkRect* maskRect) const;

Просмотреть файл

@ -96,8 +96,13 @@ public:
inline bool isRect() const { return kRect_Type == this->getType(); }
inline bool isOval() const { return kOval_Type == this->getType(); }
inline bool isSimple() const { return kSimple_Type == this->getType(); }
// TODO: should isSimpleCircular & isCircle take a tolerance? This could help
// instances where the mapping to device space is noisy.
inline bool isSimpleCircular() const {
return this->isSimple() && fRadii[0].fX == fRadii[0].fY;
return this->isSimple() && SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY);
}
inline bool isCircle() const {
return this->isOval() && SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY);
}
inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); }
inline bool isComplex() const { return kComplex_Type == this->getType(); }
@ -140,6 +145,12 @@ public:
return rr;
}
static SkRRect MakeOval(const SkRect& oval) {
SkRRect rr;
rr.setOval(oval);
return rr;
}
/**
* Set this RR to match the supplied oval. All x radii will equal half the
* width and all y radii will equal half the height.

Просмотреть файл

@ -224,7 +224,7 @@ static inline bool SkScalarNearlyZero(SkScalar x,
}
static inline bool SkScalarNearlyEqual(SkScalar x, SkScalar y,
SkScalar tolerance = SK_ScalarNearlyZero) {
SkScalar tolerance = SK_ScalarNearlyZero) {
SkASSERT(tolerance >= 0);
return SkScalarAbs(x-y) <= tolerance;
}

Просмотреть файл

@ -70,21 +70,11 @@ void SkRGBToHSV(U8CPU r, U8CPU g, U8CPU b, SkScalar hsv[3]) {
hsv[2] = v;
}
static inline U8CPU UnitScalarToByte(SkScalar x) {
if (x < 0) {
return 0;
}
if (x >= SK_Scalar1) {
return 255;
}
return SkScalarToFixed(x) >> 8;
}
SkColor SkHSVToColor(U8CPU a, const SkScalar hsv[3]) {
SkASSERT(hsv);
U8CPU s = UnitScalarToByte(hsv[1]);
U8CPU v = UnitScalarToByte(hsv[2]);
U8CPU s = SkUnitScalarClampToByte(hsv[1]);
U8CPU v = SkUnitScalarClampToByte(hsv[2]);
if (0 == s) { // shade of gray
return SkColorSetARGB(a, v, v, v);

Просмотреть файл

@ -309,7 +309,7 @@ bool SkMaskFilter::asFragmentProcessor(GrFragmentProcessor**, GrTexture*, const
return false;
}
bool SkMaskFilter::canFilterMaskGPU(const SkRect& devBounds,
bool SkMaskFilter::canFilterMaskGPU(const SkRRect& devRRect,
const SkIRect& clipBounds,
const SkMatrix& ctm,
SkRect* maskRect) const {

Просмотреть файл

@ -0,0 +1,259 @@
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrCircleBlurFragmentProcessor.h"
#if SK_SUPPORT_GPU
#include "GrContext.h"
#include "GrTextureProvider.h"
#include "gl/GrGLFragmentProcessor.h"
#include "gl/builders/GrGLProgramBuilder.h"
class GrGLCircleBlurFragmentProcessor : public GrGLFragmentProcessor {
public:
GrGLCircleBlurFragmentProcessor(const GrProcessor&) {}
void emitCode(EmitArgs&) override;
protected:
void onSetData(const GrGLProgramDataManager&, const GrProcessor&) override;
private:
GrGLProgramDataManager::UniformHandle fDataUniform;
typedef GrGLFragmentProcessor INHERITED;
};
void GrGLCircleBlurFragmentProcessor::emitCode(EmitArgs& args) {
const char *dataName;
// The data is formatted as:
// x,y - the center of the circle
// z - the distance at which the intensity starts falling off (e.g., the start of the table)
// w - the size of the profile texture
fDataUniform = args.fBuilder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
kVec4f_GrSLType,
kDefault_GrSLPrecision,
"data",
&dataName);
GrGLFragmentBuilder* fsBuilder = args.fBuilder->getFragmentShaderBuilder();
const char *fragmentPos = fsBuilder->fragmentPosition();
if (args.fInputColor) {
fsBuilder->codeAppendf("vec4 src=%s;", args.fInputColor);
} else {
fsBuilder->codeAppendf("vec4 src=vec4(1);");
}
fsBuilder->codeAppendf("vec2 vec = %s.xy - %s.xy;", fragmentPos, dataName);
fsBuilder->codeAppendf("float dist = (length(vec) - %s.z + 0.5) / %s.w;", dataName, dataName);
fsBuilder->codeAppendf("float intensity = ");
fsBuilder->appendTextureLookup(args.fSamplers[0], "vec2(dist, 0.5)");
fsBuilder->codeAppend(".a;");
fsBuilder->codeAppendf("%s = src * intensity;\n", args.fOutputColor );
}
void GrGLCircleBlurFragmentProcessor::onSetData(const GrGLProgramDataManager& pdman,
const GrProcessor& proc) {
const GrCircleBlurFragmentProcessor& cbfp = proc.cast<GrCircleBlurFragmentProcessor>();
const SkRect& circle = cbfp.circle();
// The data is formatted as:
// x,y - the center of the circle
// z - the distance at which the intensity starts falling off (e.g., the start of the table)
// w - the size of the profile texture
pdman.set4f(fDataUniform, circle.centerX(), circle.centerY(), cbfp.offset(),
SkIntToScalar(cbfp.profileSize()));
}
///////////////////////////////////////////////////////////////////////////////
GrCircleBlurFragmentProcessor::GrCircleBlurFragmentProcessor(const SkRect& circle,
float sigma,
float offset,
GrTexture* blurProfile)
: fCircle(circle)
, fSigma(sigma)
, fOffset(offset)
, fBlurProfileAccess(blurProfile, GrTextureParams::kBilerp_FilterMode) {
this->initClassID<GrCircleBlurFragmentProcessor>();
this->addTextureAccess(&fBlurProfileAccess);
this->setWillReadFragmentPosition();
}
GrGLFragmentProcessor* GrCircleBlurFragmentProcessor::onCreateGLInstance() const {
return new GrGLCircleBlurFragmentProcessor(*this);
}
void GrCircleBlurFragmentProcessor::onGetGLProcessorKey(const GrGLSLCaps& caps,
GrProcessorKeyBuilder* b) const {
GrGLCircleBlurFragmentProcessor::GenKey(*this, caps, b);
}
void GrCircleBlurFragmentProcessor::onComputeInvariantOutput(GrInvariantOutput* inout) const {
inout->mulByUnknownSingleComponent();
}
// Evaluate an AA circle function centered at the origin with 'radius' at (x,y)
static inline float disk(float x, float y, float radius) {
float distSq = x*x + y*y;
if (distSq <= (radius-0.5f)*(radius-0.5f)) {
return 1.0f;
} else if (distSq >= (radius+0.5f)*(radius+0.5f)) {
return 0.0f;
} else {
float ramp = radius + 0.5f - sqrt(distSq);
SkASSERT(ramp >= 0.0f && ramp <= 1.0f);
return ramp;
}
}
// Create the top half of an even-sized Gaussian kernel
static void make_half_kernel(float* kernel, int kernelWH, float sigma) {
SkASSERT(!(kernelWH & 1));
const float kernelOff = (kernelWH-1)/2.0f;
float b = 1.0f / (2.0f * sigma * sigma);
// omit the scale term since we're just going to renormalize
float tot = 0.0f;
for (int y = 0; y < kernelWH/2; ++y) {
for (int x = 0; x < kernelWH/2; ++x) {
// TODO: use a cheap approximation of the 2D Guassian?
float x2 = (x-kernelOff) * (x-kernelOff);
float y2 = (y-kernelOff) * (y-kernelOff);
// The kernel is symmetric so only compute it once for both sides
kernel[y*kernelWH+(kernelWH-x-1)] = kernel[y*kernelWH+x] = exp(-(x2 + y2) * b);
tot += 2.0f * kernel[y*kernelWH+x];
}
}
// Still normalize the half kernel to 1.0 (rather than 0.5) so we don't
// have to scale by 2.0 after convolution.
for (int y = 0; y < kernelWH/2; ++y) {
for (int x = 0; x < kernelWH; ++x) {
kernel[y*kernelWH+x] /= tot;
}
}
}
// Apply the half-kernel at 't' away from the center of the circle
static uint8_t eval_at(float t, float halfWidth, float* halfKernel, int kernelWH) {
SkASSERT(!(kernelWH & 1));
const float kernelOff = (kernelWH-1)/2.0f;
float acc = 0;
for (int y = 0; y < kernelWH/2; ++y) {
if (kernelOff-y > halfWidth+0.5f) {
// All disk() samples in this row will be 0.0f
continue;
}
for (int x = 0; x < kernelWH; ++x) {
float image = disk(t - kernelOff + x, -kernelOff + y, halfWidth);
float kernel = halfKernel[y*kernelWH+x];
acc += kernel * image;
}
}
return SkUnitScalarClampToByte(acc);
}
static inline void compute_profile_offset_and_size(float halfWH, float sigma,
float* offset, int* size) {
if (3*sigma <= halfWH) {
// The circle is bigger than the Gaussian. In this case we know the interior of the
// blurred circle is solid.
*offset = halfWH - 3 * sigma; // This location maps to 0.5f in the weights texture.
// It should always be 255.
*size = SkScalarCeilToInt(6*sigma);
} else {
// The Gaussian is bigger than the circle.
*offset = 0.0f;
*size = SkScalarCeilToInt(halfWH + 3*sigma);
}
}
static uint8_t* create_profile(float halfWH, float sigma) {
int kernelWH = SkScalarCeilToInt(6.0f*sigma);
kernelWH = (kernelWH + 1) & ~1; // make it the next even number up
SkAutoTArray<float> halfKernel(kernelWH*kernelWH/2);
make_half_kernel(halfKernel.get(), kernelWH, sigma);
float offset;
int numSteps;
compute_profile_offset_and_size(halfWH, sigma, &offset, &numSteps);
uint8_t* weights = new uint8_t[numSteps];
for (int i = 0; i < numSteps; ++i) {
weights[i] = eval_at(offset+i, halfWH, halfKernel.get(), kernelWH);
}
return weights;
}
GrTexture* GrCircleBlurFragmentProcessor::CreateCircleBlurProfileTexture(
GrTextureProvider* textureProvider,
const SkRect& circle,
float sigma,
float* offset) {
float halfWH = circle.width() / 2.0f;
int size;
compute_profile_offset_and_size(halfWH, sigma, offset, &size);
GrSurfaceDesc texDesc;
texDesc.fWidth = size;
texDesc.fHeight = 1;
texDesc.fConfig = kAlpha_8_GrPixelConfig;
static const GrUniqueKey::Domain kDomain = GrUniqueKey::GenerateDomain();
GrUniqueKey key;
GrUniqueKey::Builder builder(&key, kDomain, 2);
// The profile curve varies with both the sigma of the Gaussian and the size of the
// disk. Quantizing to 16.16 should be close enough though.
builder[0] = SkScalarToFixed(sigma);
builder[1] = SkScalarToFixed(halfWH);
builder.finish();
GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key);
if (!blurProfile) {
SkAutoTDeleteArray<uint8_t> profile(create_profile(halfWH, sigma));
blurProfile = textureProvider->createTexture(texDesc, true, profile.get(), 0);
if (blurProfile) {
textureProvider->assignUniqueKeyToTexture(key, blurProfile);
}
}
return blurProfile;
}
GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrCircleBlurFragmentProcessor);
const GrFragmentProcessor* GrCircleBlurFragmentProcessor::TestCreate(GrProcessorTestData* d) {
SkScalar wh = d->fRandom->nextRangeScalar(100.f, 1000.f);
SkScalar sigma = d->fRandom->nextRangeF(1.f,10.f);
SkRect circle = SkRect::MakeWH(wh, wh);
return GrCircleBlurFragmentProcessor::Create(d->fContext->textureProvider(), circle, sigma);
}
#endif

Просмотреть файл

@ -0,0 +1,78 @@
/*
* Copyright 2015 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef GrCircleBlurFragmentProcessor_DEFINED
#define GrCircleBlurFragmentProcessor_DEFINED
#include "SkTypes.h"
#if SK_SUPPORT_GPU
#include "GrFragmentProcessor.h"
#include "GrProcessorUnitTest.h"
class GrTextureProvider;
// This FP handles the special case of a blurred circle. It uses a 1D
// profile that is just rotated about the origin of the circle.
class GrCircleBlurFragmentProcessor : public GrFragmentProcessor {
public:
~GrCircleBlurFragmentProcessor() override {};
const char* name() const override { return "CircleBlur"; }
static const GrFragmentProcessor* Create(GrTextureProvider*textureProvider,
const SkRect& circle, float sigma) {
float offset;
SkAutoTUnref<GrTexture> blurProfile(CreateCircleBlurProfileTexture(textureProvider,
circle,
sigma,
&offset));
if (!blurProfile) {
return nullptr;
}
return new GrCircleBlurFragmentProcessor(circle, sigma, offset, blurProfile);
}
const SkRect& circle() const { return fCircle; }
float sigma() const { return fSigma; }
float offset() const { return fOffset; }
int profileSize() const { return fBlurProfileAccess.getTexture()->width(); }
private:
GrCircleBlurFragmentProcessor(const SkRect& circle, float sigma,
float offset, GrTexture* blurProfile);
GrGLFragmentProcessor* onCreateGLInstance() const override;
void onGetGLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override;
bool onIsEqual(const GrFragmentProcessor& other) const override {
const GrCircleBlurFragmentProcessor& cbfp = other.cast<GrCircleBlurFragmentProcessor>();
// fOffset is computed from the circle width and the sigma
return this->circle().width() == cbfp.circle().width() && fSigma == cbfp.fSigma;
}
void onComputeInvariantOutput(GrInvariantOutput* inout) const override;
static GrTexture* CreateCircleBlurProfileTexture(GrTextureProvider*,
const SkRect& circle,
float sigma, float* offset);
SkRect fCircle;
float fSigma;
float fOffset;
GrTextureAccess fBlurProfileAccess;
GR_DECLARE_FRAGMENT_PROCESSOR_TEST;
typedef GrFragmentProcessor INHERITED;
};
#endif
#endif

Просмотреть файл

@ -678,7 +678,7 @@ static float gaussianIntegral(float x) {
memory returned in profile_out.
*/
void SkBlurMask::ComputeBlurProfile(SkScalar sigma, uint8_t **profile_out) {
uint8_t* SkBlurMask::ComputeBlurProfile(SkScalar sigma) {
int size = SkScalarCeilToInt(6*sigma);
int center = size >> 1;
@ -693,7 +693,7 @@ void SkBlurMask::ComputeBlurProfile(SkScalar sigma, uint8_t **profile_out) {
profile[x] = 255 - (uint8_t) (255.f * gi);
}
*profile_out = profile;
return profile;
}
// TODO MAYBE: Maintain a profile cache to avoid recomputing this for
@ -769,10 +769,8 @@ bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst,
}
return true;
}
uint8_t *profile = nullptr;
ComputeBlurProfile(sigma, &profile);
SkAutoTDeleteArray<uint8_t> ada(profile);
SkAutoTDeleteArray<uint8_t> profile(ComputeBlurProfile(sigma));
size_t dstSize = dst->computeImageSize();
if (0 == dstSize) {
@ -791,8 +789,8 @@ bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst,
SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth);
SkAutoTMalloc<uint8_t> verticalScanline(dstHeight);
ComputeBlurredScanline(horizontalScanline, profile, dstWidth, sigma);
ComputeBlurredScanline(verticalScanline, profile, dstHeight, sigma);
ComputeBlurredScanline(horizontalScanline, profile.get(), dstWidth, sigma);
ComputeBlurredScanline(verticalScanline, profile.get(), dstHeight, sigma);
for (int y = 0 ; y < dstHeight ; ++y) {
for (int x = 0 ; x < dstWidth ; x++) {

Просмотреть файл

@ -54,15 +54,14 @@ public:
@param blurred_width The width of the final, blurred rectangle
@param sharp_width The width of the original, unblurred rectangle.
*/
static uint8_t ProfileLookup(const uint8_t* profile, int loc, int blurred_width, int sharp_width);
static uint8_t ProfileLookup(const uint8_t* profile, int loc, int blurredWidth, int sharpWidth);
/** Allocate memory for and populate the profile of a 1D blurred halfplane. The caller
must free the memory. The amount of memory allocated will be exactly 6*sigma bytes.
@param sigma The standard deviation of the gaussian blur kernel
@param profile_out The location to store the allocated profile curve
*/
static void ComputeBlurProfile(SkScalar sigma, uint8_t** profile_out);
static uint8_t* ComputeBlurProfile(SkScalar sigma);
/** Compute an entire scanline of a blurred step function. This is a 1D helper that
will produce both the horizontal and vertical profiles of the blurry rectangle.

Просмотреть файл

@ -18,6 +18,7 @@
#include "SkStrokeRec.h"
#if SK_SUPPORT_GPU
#include "GrCircleBlurFragmentProcessor.h"
#include "GrContext.h"
#include "GrDrawContext.h"
#include "GrTexture.h"
@ -44,7 +45,7 @@ public:
SkIPoint* margin) const override;
#if SK_SUPPORT_GPU
bool canFilterMaskGPU(const SkRect& devBounds,
bool canFilterMaskGPU(const SkRRect& devRRect,
const SkIRect& clipBounds,
const SkMatrix& ctm,
SkRect* maskRect) const override;
@ -166,27 +167,25 @@ bool SkBlurMaskFilterImpl::asABlur(BlurRec* rec) const {
bool SkBlurMaskFilterImpl::filterMask(SkMask* dst, const SkMask& src,
const SkMatrix& matrix,
SkIPoint* margin) const{
SkIPoint* margin) const {
SkScalar sigma = this->computeXformedSigma(matrix);
return SkBlurMask::BoxBlur(dst, src, sigma, fBlurStyle, this->getQuality(), margin);
}
bool SkBlurMaskFilterImpl::filterRectMask(SkMask* dst, const SkRect& r,
const SkMatrix& matrix,
SkIPoint* margin, SkMask::CreateMode createMode) const{
SkIPoint* margin, SkMask::CreateMode createMode) const {
SkScalar sigma = computeXformedSigma(matrix);
return SkBlurMask::BlurRect(sigma, dst, r, fBlurStyle,
margin, createMode);
return SkBlurMask::BlurRect(sigma, dst, r, fBlurStyle, margin, createMode);
}
bool SkBlurMaskFilterImpl::filterRRectMask(SkMask* dst, const SkRRect& r,
const SkMatrix& matrix,
SkIPoint* margin, SkMask::CreateMode createMode) const{
SkIPoint* margin, SkMask::CreateMode createMode) const {
SkScalar sigma = computeXformedSigma(matrix);
return SkBlurMask::BlurRRect(sigma, dst, r, fBlurStyle,
margin, createMode);
return SkBlurMask::BlurRRect(sigma, dst, r, fBlurStyle, margin, createMode);
}
#include "SkCanvas.h"
@ -607,51 +606,42 @@ class GrGLRectBlurEffect;
class GrRectBlurEffect : public GrFragmentProcessor {
public:
virtual ~GrRectBlurEffect();
~GrRectBlurEffect() override { }
const char* name() const override { return "RectBlur"; }
/**
* Create a simple filter effect with custom bicubic coefficients.
*/
static GrFragmentProcessor* Create(GrTextureProvider *textureProvider, const SkRect& rect,
float sigma) {
GrTexture *blurProfileTexture = nullptr;
static GrFragmentProcessor* Create(GrTextureProvider *textureProvider,
const SkRect& rect, float sigma) {
int doubleProfileSize = SkScalarCeilToInt(12*sigma);
if (doubleProfileSize >= rect.width() || doubleProfileSize >= rect.height()) {
// if the blur sigma is too large so the gaussian overlaps the whole
// rect in either direction, fall back to CPU path for now.
return nullptr;
}
bool createdBlurProfileTexture = CreateBlurProfileTexture(
textureProvider, sigma, &blurProfileTexture);
SkAutoTUnref<GrTexture> hunref(blurProfileTexture);
if (!createdBlurProfileTexture) {
SkAutoTUnref<GrTexture> blurProfile(CreateBlurProfileTexture(textureProvider, sigma));
if (!blurProfile) {
return nullptr;
}
return new GrRectBlurEffect(rect, sigma, blurProfileTexture);
return new GrRectBlurEffect(rect, sigma, blurProfile);
}
const SkRect& getRect() const { return fRect; }
float getSigma() const { return fSigma; }
private:
GrRectBlurEffect(const SkRect& rect, float sigma, GrTexture *blurProfile);
GrGLFragmentProcessor* onCreateGLInstance() const override;
GrRectBlurEffect(const SkRect& rect, float sigma, GrTexture *blur_profile);
virtual void onGetGLProcessorKey(const GrGLSLCaps& caps,
GrProcessorKeyBuilder* b) const override;
void onGetGLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override;
bool onIsEqual(const GrFragmentProcessor&) const override;
void onComputeInvariantOutput(GrInvariantOutput* inout) const override;
static bool CreateBlurProfileTexture(GrTextureProvider*, float sigma,
GrTexture **blurProfileTexture);
static GrTexture* CreateBlurProfileTexture(GrTextureProvider*, float sigma);
SkRect fRect;
float fSigma;
@ -665,7 +655,7 @@ private:
class GrGLRectBlurEffect : public GrGLFragmentProcessor {
public:
GrGLRectBlurEffect(const GrProcessor&) {}
virtual void emitCode(EmitArgs&) override;
void emitCode(EmitArgs&) override;
protected:
void onSetData(const GrGLProgramDataManager&, const GrProcessor&) override;
@ -738,7 +728,7 @@ void GrGLRectBlurEffect::emitCode(EmitArgs& args) {
}
void GrGLRectBlurEffect::onSetData(const GrGLProgramDataManager& pdman,
const GrProcessor& proc) {
const GrProcessor& proc) {
const GrRectBlurEffect& rbe = proc.cast<GrRectBlurEffect>();
SkRect rect = rbe.getRect();
@ -746,8 +736,8 @@ void GrGLRectBlurEffect::onSetData(const GrGLProgramDataManager& pdman,
pdman.set1f(fProfileSizeUniform, SkScalarCeilToScalar(6*rbe.getSigma()));
}
bool GrRectBlurEffect::CreateBlurProfileTexture(GrTextureProvider* textureProvider, float sigma,
GrTexture **blurProfileTexture) {
GrTexture* GrRectBlurEffect::CreateBlurProfileTexture(GrTextureProvider* textureProvider,
float sigma) {
GrSurfaceDesc texDesc;
unsigned int profileSize = SkScalarCeilToInt(6*sigma);
@ -762,40 +752,29 @@ bool GrRectBlurEffect::CreateBlurProfileTexture(GrTextureProvider* textureProvid
builder[0] = profileSize;
builder.finish();
uint8_t *profile = nullptr;
SkAutoTDeleteArray<uint8_t> ada(nullptr);
GrTexture *blurProfile = textureProvider->findAndRefTextureByUniqueKey(key);
*blurProfileTexture = textureProvider->findAndRefTextureByUniqueKey(key);
if (!blurProfile) {
SkAutoTDeleteArray<uint8_t> profile(SkBlurMask::ComputeBlurProfile(sigma));
if (nullptr == *blurProfileTexture) {
SkBlurMask::ComputeBlurProfile(sigma, &profile);
ada.reset(profile);
*blurProfileTexture = textureProvider->createTexture(texDesc, true, profile, 0);
if (nullptr == *blurProfileTexture) {
return false;
blurProfile = textureProvider->createTexture(texDesc, true, profile.get(), 0);
if (blurProfile) {
textureProvider->assignUniqueKeyToTexture(key, blurProfile);
}
textureProvider->assignUniqueKeyToTexture(key, *blurProfileTexture);
}
return true;
return blurProfile;
}
GrRectBlurEffect::GrRectBlurEffect(const SkRect& rect, float sigma,
GrTexture *blur_profile)
: fRect(rect),
fSigma(sigma),
fBlurProfileAccess(blur_profile) {
GrRectBlurEffect::GrRectBlurEffect(const SkRect& rect, float sigma, GrTexture *blurProfile)
: fRect(rect)
, fSigma(sigma)
, fBlurProfileAccess(blurProfile) {
this->initClassID<GrRectBlurEffect>();
this->addTextureAccess(&fBlurProfileAccess);
this->setWillReadFragmentPosition();
}
GrRectBlurEffect::~GrRectBlurEffect() {
}
void GrRectBlurEffect::onGetGLProcessorKey(const GrGLSLCaps& caps,
GrProcessorKeyBuilder* b) const {
GrGLRectBlurEffect::GenKey(*this, caps, b);
@ -839,22 +818,31 @@ bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvider,
return false;
}
SkRect rect;
if (!path.isRect(&rect)) {
return false;
}
// TODO: we could handle blurred stroked circles
if (!strokeRec.isFillStyle()) {
return false;
}
SkScalar xformedSigma = this->computeXformedSigma(viewMatrix);
int pad = SkScalarCeilToInt(6*xformedSigma)/2;
rect.outset(SkIntToScalar(pad), SkIntToScalar(pad));
SkAutoTUnref<const GrFragmentProcessor> fp;
SkRect rect;
if (path.isRect(&rect)) {
int pad = SkScalarCeilToInt(6*xformedSigma)/2;
rect.outset(SkIntToScalar(pad), SkIntToScalar(pad));
fp.reset(GrRectBlurEffect::Create(texProvider, rect, xformedSigma));
} else if (path.isOval(&rect) && SkScalarNearlyEqual(rect.width(), rect.height())) {
fp.reset(GrCircleBlurFragmentProcessor::Create(texProvider, rect, xformedSigma));
// expand the rect for the coverage geometry
int pad = SkScalarCeilToInt(6*xformedSigma)/2;
rect.outset(SkIntToScalar(pad), SkIntToScalar(pad));
} else {
return false;
}
SkAutoTUnref<GrFragmentProcessor> fp(GrRectBlurEffect::Create(
texProvider, rect, xformedSigma));
if (!fp) {
return false;
}
@ -870,10 +858,12 @@ bool SkBlurMaskFilterImpl::directFilterMaskGPU(GrTextureProvider* texProvider,
return true;
}
//////////////////////////////////////////////////////////////////////////////
class GrRRectBlurEffect : public GrFragmentProcessor {
public:
static GrFragmentProcessor* Create(GrTextureProvider*, float sigma, const SkRRect&);
static const GrFragmentProcessor* Create(GrTextureProvider*, float sigma, const SkRRect&);
virtual ~GrRRectBlurEffect() {};
const char* name() const override { return "GrRRectBlur"; }
@ -903,8 +893,12 @@ private:
};
GrFragmentProcessor* GrRRectBlurEffect::Create(GrTextureProvider* texProvider, float sigma,
const SkRRect& rrect) {
const GrFragmentProcessor* GrRRectBlurEffect::Create(GrTextureProvider* texProvider, float sigma,
const SkRRect& rrect) {
if (rrect.isCircle()) {
return GrCircleBlurFragmentProcessor::Create(texProvider, rrect.rect(), sigma);
}
if (!rrect.isSimpleCircular()) {
return nullptr;
}
@ -1129,8 +1123,8 @@ bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrTextureProvider* texProvid
SkRect proxyRect = rrect.rect();
proxyRect.outset(extra, extra);
SkAutoTUnref<GrFragmentProcessor> fp(GrRRectBlurEffect::Create(texProvider,
xformedSigma, rrect));
SkAutoTUnref<const GrFragmentProcessor> fp(GrRRectBlurEffect::Create(texProvider,
xformedSigma, rrect));
if (!fp) {
return false;
}
@ -1146,7 +1140,7 @@ bool SkBlurMaskFilterImpl::directFilterRRectMaskGPU(GrTextureProvider* texProvid
return true;
}
bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds,
bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRRect& devRRect,
const SkIRect& clipBounds,
const SkMatrix& ctm,
SkRect* maskRect) const {
@ -1155,14 +1149,17 @@ bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds,
return false;
}
static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64);
static const SkScalar kMIN_GPU_BLUR_SIGMA = SkIntToScalar(32);
// We always do circles on the GPU
if (!devRRect.isCircle()) {
static const SkScalar kMIN_GPU_BLUR_SIZE = SkIntToScalar(64);
static const SkScalar kMIN_GPU_BLUR_SIGMA = SkIntToScalar(32);
if (srcBounds.width() <= kMIN_GPU_BLUR_SIZE &&
srcBounds.height() <= kMIN_GPU_BLUR_SIZE &&
xformedSigma <= kMIN_GPU_BLUR_SIGMA) {
// We prefer to blur small rect with small radius via CPU.
return false;
if (devRRect.width() <= kMIN_GPU_BLUR_SIZE &&
devRRect.height() <= kMIN_GPU_BLUR_SIZE &&
xformedSigma <= kMIN_GPU_BLUR_SIGMA) {
// We prefer to blur small rects with small radii on the CPU.
return false;
}
}
if (nullptr == maskRect) {
@ -1173,7 +1170,7 @@ bool SkBlurMaskFilterImpl::canFilterMaskGPU(const SkRect& srcBounds,
float sigma3 = 3 * SkScalarToFloat(xformedSigma);
SkRect clipRect = SkRect::Make(clipBounds);
SkRect srcRect(srcBounds);
SkRect srcRect(devRRect.rect());
// Outset srcRect and clipRect by 3 * sigma, to compute affected blur area.
srcRect.outset(sigma3, sigma3);

Просмотреть файл

@ -235,7 +235,7 @@ void GrBlurUtils::drawPathWithMaskFilter(GrContext* context,
pathPtr->transform(viewMatrix, devPathPtr);
SkRect maskRect;
if (paint.getMaskFilter()->canFilterMaskGPU(devPathPtr->getBounds(),
if (paint.getMaskFilter()->canFilterMaskGPU(SkRRect::MakeRect(devPathPtr->getBounds()),
clipBounds,
viewMatrix,
&maskRect)) {

Просмотреть файл

@ -50,7 +50,7 @@ GrProcessorTestFactory<GrGeometryProcessor>::GetFactories() {
* we verify the count is as expected. If a new factory is added, then these numbers must be
* manually adjusted.
*/
static const int kFPFactoryCount = 39;
static const int kFPFactoryCount = 40;
static const int kGPFactoryCount = 14;
static const int kXPFactoryCount = 5;

Просмотреть файл

@ -544,7 +544,7 @@ void SkGpuDevice::drawRRect(const SkDraw& draw, const SkRRect& rect,
if (rect.transform(*draw.fMatrix, &devRRect)) {
if (devRRect.allCornersCircular()) {
SkRect maskRect;
if (paint.getMaskFilter()->canFilterMaskGPU(devRRect.rect(),
if (paint.getMaskFilter()->canFilterMaskGPU(devRRect,
draw.fClip->getBounds(),
*draw.fMatrix,
&maskRect)) {
@ -637,7 +637,9 @@ void SkGpuDevice::drawOval(const SkDraw& draw, const SkRect& oval,
bool usePath = false;
// some basic reasons we might need to call drawPath...
if (paint.getMaskFilter()) {
usePath = true;
// The RRect path can handle special case blurring
SkRRect rr = SkRRect::MakeOval(oval);
return this->drawRRect(draw, rr, paint);
} else {
const SkPathEffect* pe = paint.getPathEffect();
if (pe && !strokeInfo.isDashed()) {