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