1 /*
2  * Copyright 2015 Google Inc.
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 "SkGpuDevice.h"
9 
10 #include "GrBlurUtils.h"
11 #include "GrCaps.h"
12 #include "GrDrawContext.h"
13 #include "GrStyle.h"
14 #include "GrTextureParamsAdjuster.h"
15 #include "SkDraw.h"
16 #include "SkGrPriv.h"
17 #include "SkMaskFilter.h"
18 #include "effects/GrBicubicEffect.h"
19 #include "effects/GrSimpleTextureEffect.h"
20 #include "effects/GrTextureDomain.h"
21 
use_shader(bool textureIsAlphaOnly,const SkPaint & paint)22 static inline bool use_shader(bool textureIsAlphaOnly, const SkPaint& paint) {
23     return textureIsAlphaOnly && paint.getShader();
24 }
25 
26 //////////////////////////////////////////////////////////////////////////////
27 //  Helper functions for dropping src rect constraint in bilerp mode.
28 
29 static const SkScalar kColorBleedTolerance = 0.001f;
30 
has_aligned_samples(const SkRect & srcRect,const SkRect & transformedRect)31 static bool has_aligned_samples(const SkRect& srcRect, const SkRect& transformedRect) {
32     // detect pixel disalignment
33     if (SkScalarAbs(SkScalarRoundToScalar(transformedRect.left()) - transformedRect.left()) < kColorBleedTolerance &&
34         SkScalarAbs(SkScalarRoundToScalar(transformedRect.top())  - transformedRect.top())  < kColorBleedTolerance &&
35         SkScalarAbs(transformedRect.width()  - srcRect.width())  < kColorBleedTolerance &&
36         SkScalarAbs(transformedRect.height() - srcRect.height()) < kColorBleedTolerance) {
37         return true;
38     }
39     return false;
40 }
41 
may_color_bleed(const SkRect & srcRect,const SkRect & transformedRect,const SkMatrix & m,bool isMSAA)42 static bool may_color_bleed(const SkRect& srcRect,
43                             const SkRect& transformedRect,
44                             const SkMatrix& m,
45                             bool isMSAA) {
46     // Only gets called if has_aligned_samples returned false.
47     // So we can assume that sampling is axis aligned but not texel aligned.
48     SkASSERT(!has_aligned_samples(srcRect, transformedRect));
49     SkRect innerSrcRect(srcRect), innerTransformedRect, outerTransformedRect(transformedRect);
50     if (isMSAA) {
51         innerSrcRect.inset(SK_Scalar1, SK_Scalar1);
52     } else {
53         innerSrcRect.inset(SK_ScalarHalf, SK_ScalarHalf);
54     }
55     m.mapRect(&innerTransformedRect, innerSrcRect);
56 
57     // The gap between outerTransformedRect and innerTransformedRect
58     // represents the projection of the source border area, which is
59     // problematic for color bleeding.  We must check whether any
60     // destination pixels sample the border area.
61     outerTransformedRect.inset(kColorBleedTolerance, kColorBleedTolerance);
62     innerTransformedRect.outset(kColorBleedTolerance, kColorBleedTolerance);
63     SkIRect outer, inner;
64     outerTransformedRect.round(&outer);
65     innerTransformedRect.round(&inner);
66     // If the inner and outer rects round to the same result, it means the
67     // border does not overlap any pixel centers. Yay!
68     return inner != outer;
69 }
70 
can_ignore_bilerp_constraint(const GrTextureProducer & producer,const SkRect & srcRect,const SkMatrix & srcRectToDeviceSpace,bool isMSAA)71 static bool can_ignore_bilerp_constraint(const GrTextureProducer& producer,
72                                          const SkRect& srcRect,
73                                          const SkMatrix& srcRectToDeviceSpace,
74                                          bool isMSAA) {
75     if (srcRectToDeviceSpace.rectStaysRect()) {
76         // sampling is axis-aligned
77         SkRect transformedRect;
78         srcRectToDeviceSpace.mapRect(&transformedRect, srcRect);
79 
80         if (has_aligned_samples(srcRect, transformedRect) ||
81             !may_color_bleed(srcRect, transformedRect, srcRectToDeviceSpace, isMSAA)) {
82             return true;
83         }
84     }
85     return false;
86 }
87 
88 //////////////////////////////////////////////////////////////////////////////
89 
drawTextureProducer(GrTextureProducer * producer,const SkRect * srcRect,const SkRect * dstRect,SkCanvas::SrcRectConstraint constraint,const SkMatrix & viewMatrix,const GrClip & clip,const SkPaint & paint)90 void SkGpuDevice::drawTextureProducer(GrTextureProducer* producer,
91                                       const SkRect* srcRect,
92                                       const SkRect* dstRect,
93                                       SkCanvas::SrcRectConstraint constraint,
94                                       const SkMatrix& viewMatrix,
95                                       const GrClip& clip,
96                                       const SkPaint& paint) {
97     // This is the funnel for all non-tiled bitmap/image draw calls. Log a histogram entry.
98     SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
99 
100     // Figure out the actual dst and src rect by clipping the src rect to the bounds of the
101     // adjuster. If the src rect is clipped then the dst rect must be recomputed. Also determine
102     // the matrix that maps the src rect to the dst rect.
103     SkRect clippedSrcRect;
104     SkRect clippedDstRect;
105     const SkRect srcBounds = SkRect::MakeIWH(producer->width(), producer->height());
106     SkMatrix srcToDstMatrix;
107     if (srcRect) {
108         if (!dstRect) {
109             dstRect = &srcBounds;
110         }
111         if (!srcBounds.contains(*srcRect)) {
112             clippedSrcRect = *srcRect;
113             if (!clippedSrcRect.intersect(srcBounds)) {
114                 return;
115             }
116             if (!srcToDstMatrix.setRectToRect(*srcRect, *dstRect, SkMatrix::kFill_ScaleToFit)) {
117                 return;
118             }
119             srcToDstMatrix.mapRect(&clippedDstRect, clippedSrcRect);
120         } else {
121             clippedSrcRect = *srcRect;
122             clippedDstRect = *dstRect;
123             if (!srcToDstMatrix.setRectToRect(*srcRect, *dstRect, SkMatrix::kFill_ScaleToFit)) {
124                 return;
125             }
126         }
127     } else {
128         clippedSrcRect = srcBounds;
129         if (dstRect) {
130             clippedDstRect = *dstRect;
131             if (!srcToDstMatrix.setRectToRect(srcBounds, *dstRect, SkMatrix::kFill_ScaleToFit)) {
132                 return;
133             }
134         } else {
135             clippedDstRect = srcBounds;
136             srcToDstMatrix.reset();
137         }
138     }
139 
140     // Now that we have both the view and srcToDst matrices, log our scale factor.
141     LogDrawScaleFactor(SkMatrix::Concat(viewMatrix, srcToDstMatrix), paint.getFilterQuality());
142 
143     this->drawTextureProducerImpl(producer, clippedSrcRect, clippedDstRect, constraint, viewMatrix,
144                                   srcToDstMatrix, clip, paint);
145 }
146 
drawTextureProducerImpl(GrTextureProducer * producer,const SkRect & clippedSrcRect,const SkRect & clippedDstRect,SkCanvas::SrcRectConstraint constraint,const SkMatrix & viewMatrix,const SkMatrix & srcToDstMatrix,const GrClip & clip,const SkPaint & paint)147 void SkGpuDevice::drawTextureProducerImpl(GrTextureProducer* producer,
148                                           const SkRect& clippedSrcRect,
149                                           const SkRect& clippedDstRect,
150                                           SkCanvas::SrcRectConstraint constraint,
151                                           const SkMatrix& viewMatrix,
152                                           const SkMatrix& srcToDstMatrix,
153                                           const GrClip& clip,
154                                           const SkPaint& paint) {
155     // Specifying the texture coords as local coordinates is an attempt to enable more batching
156     // by not baking anything about the srcRect, dstRect, or viewMatrix, into the texture FP. In
157     // the future this should be an opaque optimization enabled by the combination of batch/GP and
158     // FP.
159     const SkMaskFilter* mf = paint.getMaskFilter();
160     // The shader expects proper local coords, so we can't replace local coords with texture coords
161     // if the shader will be used. If we have a mask filter we will change the underlying geometry
162     // that is rendered.
163     bool canUseTextureCoordsAsLocalCoords = !use_shader(producer->isAlphaOnly(), paint) && !mf;
164 
165     bool doBicubic;
166     GrTextureParams::FilterMode fm =
167         GrSkFilterQualityToGrFilterMode(paint.getFilterQuality(), viewMatrix, srcToDstMatrix,
168                                         &doBicubic);
169     const GrTextureParams::FilterMode* filterMode = doBicubic ? nullptr : &fm;
170 
171     GrTextureAdjuster::FilterConstraint constraintMode;
172     if (SkCanvas::kFast_SrcRectConstraint == constraint) {
173         constraintMode = GrTextureAdjuster::kNo_FilterConstraint;
174     } else {
175         constraintMode = GrTextureAdjuster::kYes_FilterConstraint;
176     }
177 
178     // If we have to outset for AA then we will generate texture coords outside the src rect. The
179     // same happens for any mask filter that extends the bounds rendered in the dst.
180     // This is conservative as a mask filter does not have to expand the bounds rendered.
181     bool coordsAllInsideSrcRect = !paint.isAntiAlias() && !mf;
182 
183     // Check for optimization to drop the src rect constraint when on bilerp.
184     if (filterMode && GrTextureParams::kBilerp_FilterMode == *filterMode &&
185         GrTextureAdjuster::kYes_FilterConstraint == constraintMode && coordsAllInsideSrcRect) {
186         SkMatrix combinedMatrix;
187         combinedMatrix.setConcat(viewMatrix, srcToDstMatrix);
188         if (can_ignore_bilerp_constraint(*producer, clippedSrcRect, combinedMatrix,
189                                          fDrawContext->isUnifiedMultisampled())) {
190             constraintMode = GrTextureAdjuster::kNo_FilterConstraint;
191         }
192     }
193 
194     const SkMatrix* textureMatrix;
195     SkMatrix tempMatrix;
196     if (canUseTextureCoordsAsLocalCoords) {
197         textureMatrix = &SkMatrix::I();
198     } else {
199         if (!srcToDstMatrix.invert(&tempMatrix)) {
200             return;
201         }
202         textureMatrix = &tempMatrix;
203     }
204     sk_sp<GrFragmentProcessor> fp(producer->createFragmentProcessor(
205         *textureMatrix, clippedSrcRect, constraintMode, coordsAllInsideSrcRect, filterMode,
206         fDrawContext->getColorSpace(), fDrawContext->sourceGammaTreatment()));
207     if (!fp) {
208         return;
209     }
210 
211     GrPaint grPaint;
212     if (!SkPaintToGrPaintWithTexture(fContext, fDrawContext.get(), paint, viewMatrix, fp,
213                                      producer->isAlphaOnly(), &grPaint)) {
214         return;
215     }
216 
217     if (canUseTextureCoordsAsLocalCoords) {
218         fDrawContext->fillRectToRect(clip, grPaint, viewMatrix, clippedDstRect, clippedSrcRect);
219         return;
220     }
221 
222     if (!mf) {
223         fDrawContext->drawRect(clip, grPaint, viewMatrix, clippedDstRect);
224         return;
225     }
226 
227     // First see if we can do the draw + mask filter direct to the dst.
228     if (viewMatrix.isScaleTranslate()) {
229         SkRect devClippedDstRect;
230         viewMatrix.mapRectScaleTranslate(&devClippedDstRect, clippedDstRect);
231 
232         SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
233         if (mf->directFilterRRectMaskGPU(fContext,
234                                           fDrawContext.get(),
235                                           &grPaint,
236                                           clip,
237                                           viewMatrix,
238                                           rec,
239                                           SkRRect::MakeRect(clippedDstRect),
240                                           SkRRect::MakeRect(devClippedDstRect))) {
241             return;
242         }
243     }
244 
245     SkPath rectPath;
246     rectPath.addRect(clippedDstRect);
247     rectPath.setIsVolatile(true);
248     GrBlurUtils::drawPathWithMaskFilter(this->context(), fDrawContext.get(), fClip,
249                                         rectPath, &grPaint, viewMatrix, mf, GrStyle::SimpleFill(),
250                                         true);
251 }
252