1 /*
2  * Copyright 2016 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 "GrTextureProducer.h"
9 #include "GrClip.h"
10 #include "GrRenderTargetContext.h"
11 #include "GrResourceProvider.h"
12 #include "GrSurfaceProxy.h"
13 #include "GrSurfaceProxyPriv.h"
14 #include "GrTexture.h"
15 #include "effects/GrBicubicEffect.h"
16 #include "effects/GrSimpleTextureEffect.h"
17 #include "effects/GrTextureDomain.h"
18 
CopyOnGpu(GrContext * context,sk_sp<GrTextureProxy> inputProxy,const SkIRect * subset,const CopyParams & copyParams)19 sk_sp<GrTextureProxy> GrTextureProducer::CopyOnGpu(GrContext* context,
20                                                    sk_sp<GrTextureProxy> inputProxy,
21                                                    const SkIRect* subset,
22                                                    const CopyParams& copyParams) {
23     SkASSERT(!subset || !subset->isEmpty());
24     SkASSERT(context);
25 
26     GrPixelConfig config = GrMakePixelConfigUncompressed(inputProxy->config());
27 
28     const SkRect dstRect = SkRect::MakeIWH(copyParams.fWidth, copyParams.fHeight);
29 
30     sk_sp<GrRenderTargetContext> copyRTC = context->makeRenderTargetContextWithFallback(
31         SkBackingFit::kExact, dstRect.width(), dstRect.height(), config, nullptr);
32     if (!copyRTC) {
33         return nullptr;
34     }
35 
36     GrPaint paint;
37     paint.setGammaCorrect(true);
38 
39     SkRect localRect;
40     if (subset) {
41         localRect = SkRect::Make(*subset);
42     } else {
43         localRect = SkRect::MakeWH(inputProxy->width(), inputProxy->height());
44     }
45 
46     bool needsDomain = false;
47     if (copyParams.fFilter != GrSamplerParams::kNone_FilterMode) {
48         bool resizing = localRect.width()  != dstRect.width() ||
49                         localRect.height() != dstRect.height();
50 
51         if (GrResourceProvider::IsFunctionallyExact(inputProxy.get())) {
52             needsDomain = subset && resizing;
53         } else {
54             needsDomain = resizing;
55         }
56     }
57 
58     if (needsDomain) {
59         const SkRect domain = localRect.makeInset(0.5f, 0.5f);
60         // This would cause us to read values from outside the subset. Surely, the caller knows
61         // better!
62         SkASSERT(copyParams.fFilter != GrSamplerParams::kMipMap_FilterMode);
63         paint.addColorFragmentProcessor(
64             GrTextureDomainEffect::Make(context->resourceProvider(), std::move(inputProxy),
65                                         nullptr, SkMatrix::I(),
66                                         domain, GrTextureDomain::kClamp_Mode, copyParams.fFilter));
67     } else {
68         GrSamplerParams params(SkShader::kClamp_TileMode, copyParams.fFilter);
69         paint.addColorTextureProcessor(context->resourceProvider(), std::move(inputProxy),
70                                        nullptr, SkMatrix::I(), params);
71     }
72     paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
73 
74     copyRTC->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect,
75                             localRect);
76     return copyRTC->asTextureProxyRef();
77 }
78 
79 /** Determines whether a texture domain is necessary and if so what domain to use. There are two
80  *  rectangles to consider:
81  *  - The first is the content area specified by the texture adjuster (i.e., textureContentArea).
82  *    We can *never* allow filtering to cause bleed of pixels outside this rectangle.
83  *  - The second rectangle is the constraint rectangle (i.e., constraintRect), which is known to
84  *    be contained by the content area. The filterConstraint specifies whether we are allowed to
85  *    bleed across this rect.
86  *
87  *  We want to avoid using a domain if possible. We consider the above rectangles, the filter type,
88  *  and whether the coords generated by the draw would all fall within the constraint rect. If the
89  *  latter is true we only need to consider whether the filter would extend beyond the rects.
90  */
DetermineDomainMode(const SkRect & constraintRect,FilterConstraint filterConstraint,bool coordsLimitedToConstraintRect,GrTextureProxy * proxy,const SkIRect * contentRect,const GrSamplerParams::FilterMode * filterModeOrNullForBicubic,SkRect * domainRect)91 GrTextureProducer::DomainMode GrTextureProducer::DetermineDomainMode(
92                                 const SkRect& constraintRect,
93                                 FilterConstraint filterConstraint,
94                                 bool coordsLimitedToConstraintRect,
95                                 GrTextureProxy* proxy,
96                                 const SkIRect* contentRect,
97                                 const GrSamplerParams::FilterMode* filterModeOrNullForBicubic,
98                                 SkRect* domainRect) {
99     const SkIRect proxyBounds = SkIRect::MakeWH(proxy->width(), proxy->height());
100 
101     SkASSERT(proxyBounds.contains(constraintRect));
102     // We only expect a content area rect if there is some non-content area.
103     SkASSERT(!contentRect ||
104              (!contentRect->contains(proxyBounds) &&
105               proxyBounds.contains(*contentRect) &&
106               contentRect->contains(constraintRect)));
107 
108     const bool proxyIsExact = GrResourceProvider::IsFunctionallyExact(proxy);
109 
110     // If the constraint rectangle contains the whole proxy then no need for a domain.
111     if (constraintRect.contains(proxyBounds) && proxyIsExact) {
112         return kNoDomain_DomainMode;
113     }
114 
115     if (!contentRect && !proxyIsExact) {
116         contentRect = &proxyBounds;
117     }
118 
119     bool restrictFilterToRect = (filterConstraint == GrTextureProducer::kYes_FilterConstraint);
120 
121     // If we can filter outside the constraint rect, and there is no non-content area of the
122     // proxy, and we aren't going to generate sample coords outside the constraint rect then we
123     // don't need a domain.
124     if (!restrictFilterToRect && !contentRect && coordsLimitedToConstraintRect) {
125         return kNoDomain_DomainMode;
126     }
127 
128     // Get the domain inset based on sampling mode (or bail if mipped)
129     SkScalar filterHalfWidth = 0.f;
130     if (filterModeOrNullForBicubic) {
131         switch (*filterModeOrNullForBicubic) {
132             case GrSamplerParams::kNone_FilterMode:
133                 if (coordsLimitedToConstraintRect) {
134                     return kNoDomain_DomainMode;
135                 } else {
136                     filterHalfWidth = 0.f;
137                 }
138                 break;
139             case GrSamplerParams::kBilerp_FilterMode:
140                 filterHalfWidth = .5f;
141                 break;
142             case GrSamplerParams::kMipMap_FilterMode:
143                 if (restrictFilterToRect || contentRect) {
144                     // No domain can save us here.
145                     return kTightCopy_DomainMode;
146                 }
147                 return kNoDomain_DomainMode;
148         }
149     } else {
150         // bicubic does nearest filtering internally.
151         filterHalfWidth = 1.5f;
152     }
153 
154     // Both bilerp and bicubic use bilinear filtering and so need to be clamped to the center
155     // of the edge texel. Pinning to the texel center has no impact on nearest mode and MIP-maps
156 
157     static const SkScalar kDomainInset = 0.5f;
158     // Figure out the limits of pixels we're allowed to sample from.
159     // Unless we know the amount of outset and the texture matrix we have to conservatively enforce
160     // the domain.
161     if (restrictFilterToRect) {
162         *domainRect = constraintRect.makeInset(kDomainInset, kDomainInset);
163     } else if (contentRect) {
164         // If we got here then: there is a contentRect, the coords are limited to the
165         // constraint rect, and we're allowed to filter across the constraint rect boundary. So
166         // we check whether the filter would reach across the edge of the content area.
167         // We will only set the sides that are required.
168 
169         domainRect->setLargest();
170         if (coordsLimitedToConstraintRect) {
171             // We may be able to use the fact that the texture coords are limited to the constraint
172             // rect in order to avoid having to add a domain.
173             bool needContentAreaConstraint = false;
174             if (contentRect->fLeft > 0 &&
175                 contentRect->fLeft + filterHalfWidth > constraintRect.fLeft) {
176                 domainRect->fLeft = contentRect->fLeft + kDomainInset;
177                 needContentAreaConstraint = true;
178             }
179             if (contentRect->fTop > 0 &&
180                 contentRect->fTop + filterHalfWidth > constraintRect.fTop) {
181                 domainRect->fTop = contentRect->fTop + kDomainInset;
182                 needContentAreaConstraint = true;
183             }
184             if ((!proxyIsExact || contentRect->fRight < proxy->width()) &&
185                 contentRect->fRight - filterHalfWidth < constraintRect.fRight) {
186                 domainRect->fRight = contentRect->fRight - kDomainInset;
187                 needContentAreaConstraint = true;
188             }
189             if ((!proxyIsExact || contentRect->fBottom < proxy->height()) &&
190                 contentRect->fBottom - filterHalfWidth < constraintRect.fBottom) {
191                 domainRect->fBottom = contentRect->fBottom - kDomainInset;
192                 needContentAreaConstraint = true;
193             }
194             if (!needContentAreaConstraint) {
195                 return kNoDomain_DomainMode;
196             }
197         } else {
198             // Our sample coords for the texture are allowed to be outside the constraintRect so we
199             // don't consider it when computing the domain.
200             if (contentRect->fLeft > 0) {
201                 domainRect->fLeft = contentRect->fLeft + kDomainInset;
202             }
203             if (contentRect->fTop > 0) {
204                 domainRect->fTop = contentRect->fTop + kDomainInset;
205             }
206             if (!proxyIsExact || contentRect->fRight < proxy->width()) {
207                 domainRect->fRight = contentRect->fRight - kDomainInset;
208             }
209             if (!proxyIsExact || contentRect->fBottom < proxy->height()) {
210                 domainRect->fBottom = contentRect->fBottom - kDomainInset;
211             }
212         }
213     } else {
214         return kNoDomain_DomainMode;
215     }
216 
217     if (domainRect->fLeft > domainRect->fRight) {
218         domainRect->fLeft = domainRect->fRight = SkScalarAve(domainRect->fLeft, domainRect->fRight);
219     }
220     if (domainRect->fTop > domainRect->fBottom) {
221         domainRect->fTop = domainRect->fBottom = SkScalarAve(domainRect->fTop, domainRect->fBottom);
222     }
223     return kDomain_DomainMode;
224 }
225 
CreateFragmentProcessorForDomainAndFilter(GrResourceProvider * resourceProvider,sk_sp<GrTextureProxy> proxy,sk_sp<GrColorSpaceXform> colorSpaceXform,const SkMatrix & textureMatrix,DomainMode domainMode,const SkRect & domain,const GrSamplerParams::FilterMode * filterOrNullForBicubic)226 sk_sp<GrFragmentProcessor> GrTextureProducer::CreateFragmentProcessorForDomainAndFilter(
227                                         GrResourceProvider* resourceProvider,
228                                         sk_sp<GrTextureProxy> proxy,
229                                         sk_sp<GrColorSpaceXform> colorSpaceXform,
230                                         const SkMatrix& textureMatrix,
231                                         DomainMode domainMode,
232                                         const SkRect& domain,
233                                         const GrSamplerParams::FilterMode* filterOrNullForBicubic) {
234     SkASSERT(kTightCopy_DomainMode != domainMode);
235     if (filterOrNullForBicubic) {
236         if (kDomain_DomainMode == domainMode) {
237             return GrTextureDomainEffect::Make(resourceProvider, std::move(proxy),
238                                                std::move(colorSpaceXform), textureMatrix,
239                                                domain, GrTextureDomain::kClamp_Mode,
240                                                *filterOrNullForBicubic);
241         } else {
242             GrSamplerParams params(SkShader::kClamp_TileMode, *filterOrNullForBicubic);
243             return GrSimpleTextureEffect::Make(resourceProvider, std::move(proxy),
244                                                std::move(colorSpaceXform), textureMatrix,
245                                                params);
246         }
247     } else {
248         if (kDomain_DomainMode == domainMode) {
249             return GrBicubicEffect::Make(resourceProvider, std::move(proxy),
250                                          std::move(colorSpaceXform),
251                                          textureMatrix, domain);
252         } else {
253             static const SkShader::TileMode kClampClamp[] =
254                 { SkShader::kClamp_TileMode, SkShader::kClamp_TileMode };
255             return GrBicubicEffect::Make(resourceProvider, std::move(proxy),
256                                          std::move(colorSpaceXform),
257                                          textureMatrix, kClampClamp);
258         }
259     }
260 }
261