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