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 #include "GrBlurUtils.h"
10 #include "GrCaps.h"
11 #include "GrColorSpaceXform.h"
12 #include "GrRenderTargetContext.h"
13 #include "GrShape.h"
14 #include "GrStyle.h"
15 #include "GrTextureAdjuster.h"
16 #include "GrTextureMaker.h"
17 #include "SkDraw.h"
18 #include "SkGr.h"
19 #include "SkMaskFilterBase.h"
20 #include "effects/GrBicubicEffect.h"
21 #include "effects/GrSimpleTextureEffect.h"
22 #include "effects/GrTextureDomain.h"
23 
24 static inline bool use_shader(bool textureIsAlphaOnly, const SkPaint& paint) {
25     return textureIsAlphaOnly && paint.getShader();
26 }
27 
28 //////////////////////////////////////////////////////////////////////////////
29 //  Helper functions for dropping src rect constraint in bilerp mode.
30 
31 static const SkScalar kColorBleedTolerance = 0.001f;
32 
33 static bool has_aligned_samples(const SkRect& srcRect, const SkRect& transformedRect) {
34     // detect pixel disalignment
35     if (SkScalarAbs(SkScalarRoundToScalar(transformedRect.left()) - transformedRect.left()) < kColorBleedTolerance &&
36         SkScalarAbs(SkScalarRoundToScalar(transformedRect.top())  - transformedRect.top())  < kColorBleedTolerance &&
37         SkScalarAbs(transformedRect.width()  - srcRect.width())  < kColorBleedTolerance &&
38         SkScalarAbs(transformedRect.height() - srcRect.height()) < kColorBleedTolerance) {
39         return true;
40     }
41     return false;
42 }
43 
44 static bool may_color_bleed(const SkRect& srcRect,
45                             const SkRect& transformedRect,
46                             const SkMatrix& m,
47                             GrFSAAType fsaaType) {
48     // Only gets called if has_aligned_samples returned false.
49     // So we can assume that sampling is axis aligned but not texel aligned.
50     SkASSERT(!has_aligned_samples(srcRect, transformedRect));
51     SkRect innerSrcRect(srcRect), innerTransformedRect, outerTransformedRect(transformedRect);
52     if (GrFSAAType::kUnifiedMSAA == fsaaType) {
53         innerSrcRect.inset(SK_Scalar1, SK_Scalar1);
54     } else {
55         innerSrcRect.inset(SK_ScalarHalf, SK_ScalarHalf);
56     }
57     m.mapRect(&innerTransformedRect, innerSrcRect);
58 
59     // The gap between outerTransformedRect and innerTransformedRect
60     // represents the projection of the source border area, which is
61     // problematic for color bleeding.  We must check whether any
62     // destination pixels sample the border area.
63     outerTransformedRect.inset(kColorBleedTolerance, kColorBleedTolerance);
64     innerTransformedRect.outset(kColorBleedTolerance, kColorBleedTolerance);
65     SkIRect outer, inner;
66     outerTransformedRect.round(&outer);
67     innerTransformedRect.round(&inner);
68     // If the inner and outer rects round to the same result, it means the
69     // border does not overlap any pixel centers. Yay!
70     return inner != outer;
71 }
72 
73 static bool can_ignore_bilerp_constraint(const GrTextureProducer& producer,
74                                          const SkRect& srcRect,
75                                          const SkMatrix& srcRectToDeviceSpace,
76                                          GrFSAAType fsaaType) {
77     if (srcRectToDeviceSpace.rectStaysRect()) {
78         // sampling is axis-aligned
79         SkRect transformedRect;
80         srcRectToDeviceSpace.mapRect(&transformedRect, srcRect);
81 
82         if (has_aligned_samples(srcRect, transformedRect) ||
83             !may_color_bleed(srcRect, transformedRect, srcRectToDeviceSpace, fsaaType)) {
84             return true;
85         }
86     }
87     return false;
88 }
89 
90 /**
91  * Checks whether the paint is compatible with using GrRenderTargetContext::drawTexture. It is more
92  * efficient than the GrTextureProducer general case.
93  */
94 static bool can_use_draw_texture(const SkPaint& paint) {
95     return (!paint.getColorFilter() && !paint.getShader() && !paint.getMaskFilter() &&
96             !paint.getImageFilter() && paint.getFilterQuality() < kMedium_SkFilterQuality &&
97             paint.getBlendMode() == SkBlendMode::kSrcOver);
98 }
99 
100 static void draw_texture(const SkPaint& paint, const SkMatrix& ctm, const SkRect* src,
101                          const SkRect* dst, GrAA aa, SkCanvas::SrcRectConstraint constraint,
102                          sk_sp<GrTextureProxy> proxy, SkAlphaType alphaType,
103                          SkColorSpace* colorSpace, const GrClip& clip, GrRenderTargetContext* rtc) {
104     SkASSERT(!(SkToBool(src) && !SkToBool(dst)));
105     SkRect srcRect = src ? *src : SkRect::MakeWH(proxy->width(), proxy->height());
106     SkRect dstRect = dst ? *dst : srcRect;
107     if (src && !SkRect::MakeIWH(proxy->width(), proxy->height()).contains(srcRect)) {
108         // Shrink the src rect to be within bounds and proportionately shrink the dst rect.
109         SkMatrix srcToDst;
110         srcToDst.setRectToRect(srcRect, dstRect, SkMatrix::kFill_ScaleToFit);
111         SkAssertResult(srcRect.intersect(SkRect::MakeIWH(proxy->width(), proxy->height())));
112         srcToDst.mapRect(&dstRect, srcRect);
113     }
114     const GrColorSpaceInfo& dstInfo(rtc->colorSpaceInfo());
115     auto textureXform =
116         GrColorSpaceXform::Make(colorSpace          , alphaType,
117                                 dstInfo.colorSpace(), kPremul_SkAlphaType);
118     GrSamplerState::Filter filter;
119     switch (paint.getFilterQuality()) {
120         case kNone_SkFilterQuality:
121             filter = GrSamplerState::Filter::kNearest;
122             break;
123         case kLow_SkFilterQuality:
124             filter = GrSamplerState::Filter::kBilerp;
125             break;
126         case kMedium_SkFilterQuality:
127         case kHigh_SkFilterQuality:
128             SK_ABORT("Quality level not allowed.");
129     }
130     SkPMColor4f color;
131     if (GrPixelConfigIsAlphaOnly(proxy->config())) {
132         color = SkColor4fPrepForDst(paint.getColor4f(), dstInfo, *rtc->caps()).premul();
133     } else {
134         float paintAlpha = paint.getColor4f().fA;
135         color = { paintAlpha, paintAlpha, paintAlpha, paintAlpha };
136     }
137     GrQuadAAFlags aaFlags = aa == GrAA::kYes ? GrQuadAAFlags::kAll : GrQuadAAFlags::kNone;
138     rtc->drawTexture(clip, std::move(proxy), filter, color, srcRect, dstRect, aaFlags, constraint,
139                      ctm, std::move(textureXform));
140 }
141 
142 //////////////////////////////////////////////////////////////////////////////
143 
144 void SkGpuDevice::drawPinnedTextureProxy(sk_sp<GrTextureProxy> proxy, uint32_t pinnedUniqueID,
145                                          SkColorSpace* colorSpace, SkAlphaType alphaType,
146                                          const SkRect* srcRect, const SkRect* dstRect,
147                                          SkCanvas::SrcRectConstraint constraint,
148                                          const SkMatrix& viewMatrix, const SkPaint& paint) {
149     GrAA aa = GrAA(paint.isAntiAlias());
150     if (can_use_draw_texture(paint)) {
151         draw_texture(paint, viewMatrix, srcRect, dstRect, aa, constraint, std::move(proxy),
152                      alphaType, colorSpace, this->clip(), fRenderTargetContext.get());
153         return;
154     }
155     GrTextureAdjuster adjuster(this->context(), std::move(proxy), alphaType, pinnedUniqueID,
156                                colorSpace);
157     this->drawTextureProducer(&adjuster, srcRect, dstRect, constraint, viewMatrix, paint, false);
158 }
159 
160 void SkGpuDevice::drawTextureProducer(GrTextureProducer* producer,
161                                       const SkRect* srcRect,
162                                       const SkRect* dstRect,
163                                       SkCanvas::SrcRectConstraint constraint,
164                                       const SkMatrix& viewMatrix,
165                                       const SkPaint& paint,
166                                       bool attemptDrawTexture) {
167     if (attemptDrawTexture && can_use_draw_texture(paint)) {
168         GrAA aa = GrAA(paint.isAntiAlias());
169         // We've done enough checks above to allow us to pass ClampNearest() and not check for
170         // scaling adjustments.
171         auto proxy = producer->refTextureProxyForParams(GrSamplerState::ClampNearest(), nullptr);
172         if (!proxy) {
173             return;
174         }
175         draw_texture(paint, viewMatrix, srcRect, dstRect, aa, constraint, std::move(proxy),
176                      producer->alphaType(), producer->colorSpace(), this->clip(),
177                      fRenderTargetContext.get());
178         return;
179     }
180 
181     // This is the funnel for all non-tiled bitmap/image draw calls. Log a histogram entry.
182     SK_HISTOGRAM_BOOLEAN("DrawTiled", false);
183 
184     // Figure out the actual dst and src rect by clipping the src rect to the bounds of the
185     // adjuster. If the src rect is clipped then the dst rect must be recomputed. Also determine
186     // the matrix that maps the src rect to the dst rect.
187     SkRect clippedSrcRect;
188     SkRect clippedDstRect;
189     const SkRect srcBounds = SkRect::MakeIWH(producer->width(), producer->height());
190     SkMatrix srcToDstMatrix;
191     if (srcRect) {
192         if (!dstRect) {
193             dstRect = &srcBounds;
194         }
195         if (!srcBounds.contains(*srcRect)) {
196             clippedSrcRect = *srcRect;
197             if (!clippedSrcRect.intersect(srcBounds)) {
198                 return;
199             }
200             if (!srcToDstMatrix.setRectToRect(*srcRect, *dstRect, SkMatrix::kFill_ScaleToFit)) {
201                 return;
202             }
203             srcToDstMatrix.mapRect(&clippedDstRect, clippedSrcRect);
204         } else {
205             clippedSrcRect = *srcRect;
206             clippedDstRect = *dstRect;
207             if (!srcToDstMatrix.setRectToRect(*srcRect, *dstRect, SkMatrix::kFill_ScaleToFit)) {
208                 return;
209             }
210         }
211     } else {
212         clippedSrcRect = srcBounds;
213         if (dstRect) {
214             clippedDstRect = *dstRect;
215             if (!srcToDstMatrix.setRectToRect(srcBounds, *dstRect, SkMatrix::kFill_ScaleToFit)) {
216                 return;
217             }
218         } else {
219             clippedDstRect = srcBounds;
220             srcToDstMatrix.reset();
221         }
222     }
223 
224     // Now that we have both the view and srcToDst matrices, log our scale factor.
225     LogDrawScaleFactor(SkMatrix::Concat(viewMatrix, srcToDstMatrix), paint.getFilterQuality());
226 
227     this->drawTextureProducerImpl(producer, clippedSrcRect, clippedDstRect, constraint, viewMatrix,
228                                   srcToDstMatrix, paint);
229 }
230 
231 void SkGpuDevice::drawTextureProducerImpl(GrTextureProducer* producer,
232                                           const SkRect& clippedSrcRect,
233                                           const SkRect& clippedDstRect,
234                                           SkCanvas::SrcRectConstraint constraint,
235                                           const SkMatrix& viewMatrix,
236                                           const SkMatrix& srcToDstMatrix,
237                                           const SkPaint& paint) {
238     // Specifying the texture coords as local coordinates is an attempt to enable more GrDrawOp
239     // combining by not baking anything about the srcRect, dstRect, or viewMatrix, into the texture
240     // FP. In the future this should be an opaque optimization enabled by the combination of
241     // GrDrawOp/GP and FP.
242     const SkMaskFilter* mf = paint.getMaskFilter();
243     if (mf && as_MFB(mf)->hasFragmentProcessor()) {
244         mf = nullptr;
245     }
246     // The shader expects proper local coords, so we can't replace local coords with texture coords
247     // if the shader will be used. If we have a mask filter we will change the underlying geometry
248     // that is rendered.
249     bool canUseTextureCoordsAsLocalCoords = !use_shader(producer->isAlphaOnly(), paint) && !mf;
250 
251     bool doBicubic;
252     GrSamplerState::Filter fm = GrSkFilterQualityToGrFilterMode(
253             paint.getFilterQuality(), viewMatrix, srcToDstMatrix,
254             fContext->contextPriv().sharpenMipmappedTextures(), &doBicubic);
255     const GrSamplerState::Filter* filterMode = doBicubic ? nullptr : &fm;
256 
257     GrTextureProducer::FilterConstraint constraintMode;
258     if (SkCanvas::kFast_SrcRectConstraint == constraint) {
259         constraintMode = GrTextureAdjuster::kNo_FilterConstraint;
260     } else {
261         constraintMode = GrTextureAdjuster::kYes_FilterConstraint;
262     }
263 
264     // If we have to outset for AA then we will generate texture coords outside the src rect. The
265     // same happens for any mask filter that extends the bounds rendered in the dst.
266     // This is conservative as a mask filter does not have to expand the bounds rendered.
267     bool coordsAllInsideSrcRect = !paint.isAntiAlias() && !mf;
268 
269     // Check for optimization to drop the src rect constraint when on bilerp.
270     if (filterMode && GrSamplerState::Filter::kBilerp == *filterMode &&
271         GrTextureAdjuster::kYes_FilterConstraint == constraintMode && coordsAllInsideSrcRect) {
272         SkMatrix combinedMatrix;
273         combinedMatrix.setConcat(viewMatrix, srcToDstMatrix);
274         if (can_ignore_bilerp_constraint(*producer, clippedSrcRect, combinedMatrix,
275                                          fRenderTargetContext->fsaaType())) {
276             constraintMode = GrTextureAdjuster::kNo_FilterConstraint;
277         }
278     }
279 
280     const SkMatrix* textureMatrix;
281     SkMatrix tempMatrix;
282     if (canUseTextureCoordsAsLocalCoords) {
283         textureMatrix = &SkMatrix::I();
284     } else {
285         if (!srcToDstMatrix.invert(&tempMatrix)) {
286             return;
287         }
288         textureMatrix = &tempMatrix;
289     }
290     auto fp = producer->createFragmentProcessor(*textureMatrix, clippedSrcRect, constraintMode,
291                                                 coordsAllInsideSrcRect, filterMode);
292     SkColorSpace* rtColorSpace = fRenderTargetContext->colorSpaceInfo().colorSpace();
293     SkColorSpace* targetColorSpace = producer->targetColorSpace();
294     SkColorSpace* dstColorSpace = SkToBool(rtColorSpace) ? rtColorSpace : targetColorSpace;
295     fp = GrColorSpaceXformEffect::Make(std::move(fp), producer->colorSpace(), producer->alphaType(),
296                                        dstColorSpace);
297     if (!fp) {
298         return;
299     }
300 
301     GrPaint grPaint;
302     if (!SkPaintToGrPaintWithTexture(fContext.get(), fRenderTargetContext->colorSpaceInfo(), paint,
303                                      viewMatrix, std::move(fp), producer->isAlphaOnly(),
304                                      &grPaint)) {
305         return;
306     }
307     GrAA aa = GrAA(paint.isAntiAlias());
308     if (canUseTextureCoordsAsLocalCoords) {
309         fRenderTargetContext->fillRectToRect(this->clip(), std::move(grPaint), aa, viewMatrix,
310                                              clippedDstRect, clippedSrcRect);
311         return;
312     }
313 
314     if (!mf) {
315         fRenderTargetContext->drawRect(this->clip(), std::move(grPaint), aa, viewMatrix,
316                                        clippedDstRect);
317         return;
318     }
319 
320     GrShape shape(clippedDstRect, GrStyle::SimpleFill());
321 
322     GrBlurUtils::drawShapeWithMaskFilter(this->context(), fRenderTargetContext.get(), this->clip(),
323                                          shape, std::move(grPaint), viewMatrix, mf);
324 }
325