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
CopyOnGpu(GrContext * context,sk_sp<GrTextureProxy> inputProxy,const CopyParams & copyParams,bool dstWillRequireMipMaps)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 */
DetermineDomainMode(const SkRect & constraintRect,FilterConstraint filterConstraint,bool coordsLimitedToConstraintRect,GrTextureProxy * proxy,const GrSamplerState::Filter * filterModeOrNullForBicubic,SkRect * domainRect)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
CreateFragmentProcessorForDomainAndFilter(sk_sp<GrTextureProxy> proxy,const SkMatrix & textureMatrix,DomainMode domainMode,const SkRect & domain,const GrSamplerState::Filter * filterOrNullForBicubic)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
refTextureProxyForParams(const GrSamplerState & sampler,SkScalar scaleAdjust[2])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
refTextureProxy(GrMipMapped willNeedMips)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