GrTextureProducer.cpp revision 274218ef0173ff6046f2258c703c1c83ea37c02f
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 "SkRectPriv.h"
14#include "effects/GrBicubicEffect.h"
15#include "effects/GrSimpleTextureEffect.h"
16#include "effects/GrTextureDomain.h"
17
18sk_sp<GrTextureProxy> GrTextureProducer::CopyOnGpu(GrContext* context,
19                                                   sk_sp<GrTextureProxy> inputProxy,
20                                                   const CopyParams& copyParams,
21                                                   bool dstWillRequireMipMaps) {
22    SkASSERT(context);
23
24    const SkRect dstRect = SkRect::MakeIWH(copyParams.fWidth, copyParams.fHeight);
25    GrMipMapped mipMapped = dstWillRequireMipMaps ? GrMipMapped::kYes : GrMipMapped::kNo;
26
27    sk_sp<GrRenderTargetContext> copyRTC = context->makeDeferredRenderTargetContextWithFallback(
28        SkBackingFit::kExact, dstRect.width(), dstRect.height(), inputProxy->config(), nullptr,
29        0, mipMapped, inputProxy->origin());
30    if (!copyRTC) {
31        return nullptr;
32    }
33
34    GrPaint paint;
35    paint.setGammaCorrect(true);
36
37    SkRect localRect = SkRect::MakeWH(inputProxy->width(), inputProxy->height());
38
39    bool needsDomain = false;
40    if (copyParams.fFilter != GrSamplerState::Filter::kNearest) {
41        bool resizing = localRect.width()  != dstRect.width() ||
42                        localRect.height() != dstRect.height();
43        needsDomain = resizing && !GrProxyProvider::IsFunctionallyExact(inputProxy.get());
44    }
45
46    if (needsDomain) {
47        const SkRect domain = localRect.makeInset(0.5f, 0.5f);
48        // This would cause us to read values from outside the subset. Surely, the caller knows
49        // better!
50        SkASSERT(copyParams.fFilter != GrSamplerState::Filter::kMipMap);
51        paint.addColorFragmentProcessor(
52            GrTextureDomainEffect::Make(std::move(inputProxy), SkMatrix::I(), domain,
53                                        GrTextureDomain::kClamp_Mode, copyParams.fFilter));
54    } else {
55        GrSamplerState samplerState(GrSamplerState::WrapMode::kClamp, copyParams.fFilter);
56        paint.addColorTextureProcessor(std::move(inputProxy), SkMatrix::I(), samplerState);
57    }
58    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);
59
60    copyRTC->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect,
61                            localRect);
62    return copyRTC->asTextureProxyRef();
63}
64
65/** Determines whether a texture domain is necessary and if so what domain to use. There are two
66 *  rectangles to consider:
67 *  - The first is the content area specified by the texture adjuster (i.e., textureContentArea).
68 *    We can *never* allow filtering to cause bleed of pixels outside this rectangle.
69 *  - The second rectangle is the constraint rectangle (i.e., constraintRect), which is known to
70 *    be contained by the content area. The filterConstraint specifies whether we are allowed to
71 *    bleed across this rect.
72 *
73 *  We want to avoid using a domain if possible. We consider the above rectangles, the filter type,
74 *  and whether the coords generated by the draw would all fall within the constraint rect. If the
75 *  latter is true we only need to consider whether the filter would extend beyond the rects.
76 */
77GrTextureProducer::DomainMode GrTextureProducer::DetermineDomainMode(
78        const SkRect& constraintRect,
79        FilterConstraint filterConstraint,
80        bool coordsLimitedToConstraintRect,
81        GrTextureProxy* proxy,
82        const GrSamplerState::Filter* filterModeOrNullForBicubic,
83        SkRect* domainRect) {
84    const SkIRect proxyBounds = SkIRect::MakeWH(proxy->width(), proxy->height());
85
86    SkASSERT(proxyBounds.contains(constraintRect));
87
88    const bool proxyIsExact = GrProxyProvider::IsFunctionallyExact(proxy);
89
90    // If the constraint rectangle contains the whole proxy then no need for a domain.
91    if (constraintRect.contains(proxyBounds) && proxyIsExact) {
92        return kNoDomain_DomainMode;
93    }
94
95    bool restrictFilterToRect = (filterConstraint == GrTextureProducer::kYes_FilterConstraint);
96
97    // If we can filter outside the constraint rect, and there is no non-content area of the
98    // proxy, and we aren't going to generate sample coords outside the constraint rect then we
99    // don't need a domain.
100    if (!restrictFilterToRect && proxyIsExact && coordsLimitedToConstraintRect) {
101        return kNoDomain_DomainMode;
102    }
103
104    // Get the domain inset based on sampling mode (or bail if mipped)
105    SkScalar filterHalfWidth = 0.f;
106    if (filterModeOrNullForBicubic) {
107        switch (*filterModeOrNullForBicubic) {
108            case GrSamplerState::Filter::kNearest:
109                if (coordsLimitedToConstraintRect) {
110                    return kNoDomain_DomainMode;
111                } else {
112                    filterHalfWidth = 0.f;
113                }
114                break;
115            case GrSamplerState::Filter::kBilerp:
116                filterHalfWidth = .5f;
117                break;
118            case GrSamplerState::Filter::kMipMap:
119                if (restrictFilterToRect || !proxyIsExact) {
120                    // No domain can save us here.
121                    return kTightCopy_DomainMode;
122                }
123                return kNoDomain_DomainMode;
124        }
125    } else {
126        // bicubic does nearest filtering internally.
127        filterHalfWidth = 1.5f;
128    }
129
130    // Both bilerp and bicubic use bilinear filtering and so need to be clamped to the center
131    // of the edge texel. Pinning to the texel center has no impact on nearest mode and MIP-maps
132
133    static const SkScalar kDomainInset = 0.5f;
134    // Figure out the limits of pixels we're allowed to sample from.
135    // Unless we know the amount of outset and the texture matrix we have to conservatively enforce
136    // the domain.
137    if (restrictFilterToRect) {
138        *domainRect = constraintRect.makeInset(kDomainInset, kDomainInset);
139    } else if (!proxyIsExact) {
140        // If we got here then: proxy is not exact, the coords are limited to the
141        // constraint rect, and we're allowed to filter across the constraint rect boundary. So
142        // we check whether the filter would reach across the edge of the proxy.
143        // We will only set the sides that are required.
144
145        *domainRect = SkRectPriv::MakeLargest();
146        if (coordsLimitedToConstraintRect) {
147            // We may be able to use the fact that the texture coords are limited to the constraint
148            // rect in order to avoid having to add a domain.
149            bool needContentAreaConstraint = false;
150            if (proxyBounds.fRight - filterHalfWidth < constraintRect.fRight) {
151                domainRect->fRight = proxyBounds.fRight - kDomainInset;
152                needContentAreaConstraint = true;
153            }
154            if (proxyBounds.fBottom - filterHalfWidth < constraintRect.fBottom) {
155                domainRect->fBottom = proxyBounds.fBottom - kDomainInset;
156                needContentAreaConstraint = true;
157            }
158            if (!needContentAreaConstraint) {
159                return kNoDomain_DomainMode;
160            }
161        } else {
162            // Our sample coords for the texture are allowed to be outside the constraintRect so we
163            // don't consider it when computing the domain.
164            domainRect->fRight = proxyBounds.fRight - kDomainInset;
165            domainRect->fBottom = proxyBounds.fBottom - kDomainInset;
166        }
167    } else {
168        return kNoDomain_DomainMode;
169    }
170
171    if (domainRect->fLeft > domainRect->fRight) {
172        domainRect->fLeft = domainRect->fRight = SkScalarAve(domainRect->fLeft, domainRect->fRight);
173    }
174    if (domainRect->fTop > domainRect->fBottom) {
175        domainRect->fTop = domainRect->fBottom = SkScalarAve(domainRect->fTop, domainRect->fBottom);
176    }
177    return kDomain_DomainMode;
178}
179
180std::unique_ptr<GrFragmentProcessor> GrTextureProducer::CreateFragmentProcessorForDomainAndFilter(
181        sk_sp<GrTextureProxy> proxy,
182        const SkMatrix& textureMatrix,
183        DomainMode domainMode,
184        const SkRect& domain,
185        const GrSamplerState::Filter* filterOrNullForBicubic) {
186    SkASSERT(kTightCopy_DomainMode != domainMode);
187    if (filterOrNullForBicubic) {
188        if (kDomain_DomainMode == domainMode) {
189            return GrTextureDomainEffect::Make(std::move(proxy), textureMatrix, domain,
190                                               GrTextureDomain::kClamp_Mode,
191                                               *filterOrNullForBicubic);
192        } else {
193            GrSamplerState samplerState(GrSamplerState::WrapMode::kClamp, *filterOrNullForBicubic);
194            return GrSimpleTextureEffect::Make(std::move(proxy), textureMatrix, samplerState);
195        }
196    } else {
197        if (kDomain_DomainMode == domainMode) {
198            return GrBicubicEffect::Make(std::move(proxy), textureMatrix, domain);
199        } else {
200            static const GrSamplerState::WrapMode kClampClamp[] = {
201                    GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp};
202            return GrBicubicEffect::Make(std::move(proxy), textureMatrix, kClampClamp);
203        }
204    }
205}
206