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