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