aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/gpu/GrTextureProducer.cpp
blob: 8d3549d5d18c8acc8ea9bfc8e1b5f23bb1416705 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "GrTextureProducer.h"
#include "GrClip.h"
#include "GrProxyProvider.h"
#include "GrRenderTargetContext.h"
#include "GrTextureProxy.h"
#include "SkGr.h"
#include "SkMipMap.h"
#include "SkRectPriv.h"
#include "effects/GrBicubicEffect.h"
#include "effects/GrSimpleTextureEffect.h"
#include "effects/GrTextureDomain.h"

sk_sp<GrTextureProxy> GrTextureProducer::CopyOnGpu(GrContext* context,
                                                   sk_sp<GrTextureProxy> inputProxy,
                                                   const CopyParams& copyParams,
                                                   bool dstWillRequireMipMaps) {
    SkASSERT(context);

    const SkRect dstRect = SkRect::MakeIWH(copyParams.fWidth, copyParams.fHeight);
    GrMipMapped mipMapped = dstWillRequireMipMaps ? GrMipMapped::kYes : GrMipMapped::kNo;

    SkRect localRect = SkRect::MakeWH(inputProxy->width(), inputProxy->height());

    bool needsDomain = false;
    bool resizing = false;
    if (copyParams.fFilter != GrSamplerState::Filter::kNearest) {
        bool resizing = localRect.width()  != dstRect.width() ||
                        localRect.height() != dstRect.height();
        needsDomain = resizing && !GrProxyProvider::IsFunctionallyExact(inputProxy.get());
    }

    if (copyParams.fFilter == GrSamplerState::Filter::kNearest && !needsDomain && !resizing &&
        dstWillRequireMipMaps) {
        sk_sp<GrTextureProxy> proxy = GrCopyBaseMipMapToTextureProxy(context, inputProxy.get());
        if (proxy) {
            return proxy;
        }
    }

    sk_sp<GrRenderTargetContext> copyRTC =
        context->contextPriv().makeDeferredRenderTargetContextWithFallback(
            SkBackingFit::kExact, dstRect.width(), dstRect.height(), inputProxy->config(),
            nullptr, 1, mipMapped, inputProxy->origin());
    if (!copyRTC) {
        return nullptr;
    }

    GrPaint paint;

    if (needsDomain) {
        const SkRect domain = localRect.makeInset(0.5f, 0.5f);
        // This would cause us to read values from outside the subset. Surely, the caller knows
        // better!
        SkASSERT(copyParams.fFilter != GrSamplerState::Filter::kMipMap);
        paint.addColorFragmentProcessor(
            GrTextureDomainEffect::Make(std::move(inputProxy), SkMatrix::I(), domain,
                                        GrTextureDomain::kClamp_Mode, copyParams.fFilter));
    } else {
        GrSamplerState samplerState(GrSamplerState::WrapMode::kClamp, copyParams.fFilter);
        paint.addColorTextureProcessor(std::move(inputProxy), SkMatrix::I(), samplerState);
    }
    paint.setPorterDuffXPFactory(SkBlendMode::kSrc);

    copyRTC->fillRectToRect(GrNoClip(), std::move(paint), GrAA::kNo, SkMatrix::I(), dstRect,
                            localRect);
    return copyRTC->asTextureProxyRef();
}

/** Determines whether a texture domain is necessary and if so what domain to use. There are two
 *  rectangles to consider:
 *  - The first is the content area specified by the texture adjuster (i.e., textureContentArea).
 *    We can *never* allow filtering to cause bleed of pixels outside this rectangle.
 *  - The second rectangle is the constraint rectangle (i.e., constraintRect), which is known to
 *    be contained by the content area. The filterConstraint specifies whether we are allowed to
 *    bleed across this rect.
 *
 *  We want to avoid using a domain if possible. We consider the above rectangles, the filter type,
 *  and whether the coords generated by the draw would all fall within the constraint rect. If the
 *  latter is true we only need to consider whether the filter would extend beyond the rects.
 */
GrTextureProducer::DomainMode GrTextureProducer::DetermineDomainMode(
        const SkRect& constraintRect,
        FilterConstraint filterConstraint,
        bool coordsLimitedToConstraintRect,
        GrTextureProxy* proxy,
        const GrSamplerState::Filter* filterModeOrNullForBicubic,
        SkRect* domainRect) {
    const SkIRect proxyBounds = SkIRect::MakeWH(proxy->width(), proxy->height());

    SkASSERT(proxyBounds.contains(constraintRect));

    const bool proxyIsExact = GrProxyProvider::IsFunctionallyExact(proxy);

    // If the constraint rectangle contains the whole proxy then no need for a domain.
    if (constraintRect.contains(proxyBounds) && proxyIsExact) {
        return kNoDomain_DomainMode;
    }

    bool restrictFilterToRect = (filterConstraint == GrTextureProducer::kYes_FilterConstraint);

    // If we can filter outside the constraint rect, and there is no non-content area of the
    // proxy, and we aren't going to generate sample coords outside the constraint rect then we
    // don't need a domain.
    if (!restrictFilterToRect && proxyIsExact && coordsLimitedToConstraintRect) {
        return kNoDomain_DomainMode;
    }

    // Get the domain inset based on sampling mode (or bail if mipped)
    SkScalar filterHalfWidth = 0.f;
    if (filterModeOrNullForBicubic) {
        switch (*filterModeOrNullForBicubic) {
            case GrSamplerState::Filter::kNearest:
                if (coordsLimitedToConstraintRect) {
                    return kNoDomain_DomainMode;
                } else {
                    filterHalfWidth = 0.f;
                }
                break;
            case GrSamplerState::Filter::kBilerp:
                filterHalfWidth = .5f;
                break;
            case GrSamplerState::Filter::kMipMap:
                if (restrictFilterToRect || !proxyIsExact) {
                    // No domain can save us here.
                    return kTightCopy_DomainMode;
                }
                return kNoDomain_DomainMode;
        }
    } else {
        // bicubic does nearest filtering internally.
        filterHalfWidth = 1.5f;
    }

    // Both bilerp and bicubic use bilinear filtering and so need to be clamped to the center
    // of the edge texel. Pinning to the texel center has no impact on nearest mode and MIP-maps

    static const SkScalar kDomainInset = 0.5f;
    // Figure out the limits of pixels we're allowed to sample from.
    // Unless we know the amount of outset and the texture matrix we have to conservatively enforce
    // the domain.
    if (restrictFilterToRect) {
        *domainRect = constraintRect.makeInset(kDomainInset, kDomainInset);
    } else if (!proxyIsExact) {
        // If we got here then: proxy is not exact, the coords are limited to the
        // constraint rect, and we're allowed to filter across the constraint rect boundary. So
        // we check whether the filter would reach across the edge of the proxy.
        // We will only set the sides that are required.

        *domainRect = SkRectPriv::MakeLargest();
        if (coordsLimitedToConstraintRect) {
            // We may be able to use the fact that the texture coords are limited to the constraint
            // rect in order to avoid having to add a domain.
            bool needContentAreaConstraint = false;
            if (proxyBounds.fRight - filterHalfWidth < constraintRect.fRight) {
                domainRect->fRight = proxyBounds.fRight - kDomainInset;
                needContentAreaConstraint = true;
            }
            if (proxyBounds.fBottom - filterHalfWidth < constraintRect.fBottom) {
                domainRect->fBottom = proxyBounds.fBottom - kDomainInset;
                needContentAreaConstraint = true;
            }
            if (!needContentAreaConstraint) {
                return kNoDomain_DomainMode;
            }
        } else {
            // Our sample coords for the texture are allowed to be outside the constraintRect so we
            // don't consider it when computing the domain.
            domainRect->fRight = proxyBounds.fRight - kDomainInset;
            domainRect->fBottom = proxyBounds.fBottom - kDomainInset;
        }
    } else {
        return kNoDomain_DomainMode;
    }

    if (domainRect->fLeft > domainRect->fRight) {
        domainRect->fLeft = domainRect->fRight = SkScalarAve(domainRect->fLeft, domainRect->fRight);
    }
    if (domainRect->fTop > domainRect->fBottom) {
        domainRect->fTop = domainRect->fBottom = SkScalarAve(domainRect->fTop, domainRect->fBottom);
    }
    return kDomain_DomainMode;
}

std::unique_ptr<GrFragmentProcessor> GrTextureProducer::CreateFragmentProcessorForDomainAndFilter(
        sk_sp<GrTextureProxy> proxy,
        const SkMatrix& textureMatrix,
        DomainMode domainMode,
        const SkRect& domain,
        const GrSamplerState::Filter* filterOrNullForBicubic) {
    SkASSERT(kTightCopy_DomainMode != domainMode);
    if (filterOrNullForBicubic) {
        if (kDomain_DomainMode == domainMode) {
            return GrTextureDomainEffect::Make(std::move(proxy), textureMatrix, domain,
                                               GrTextureDomain::kClamp_Mode,
                                               *filterOrNullForBicubic);
        } else {
            GrSamplerState samplerState(GrSamplerState::WrapMode::kClamp, *filterOrNullForBicubic);
            return GrSimpleTextureEffect::Make(std::move(proxy), textureMatrix, samplerState);
        }
    } else {
        if (kDomain_DomainMode == domainMode) {
            return GrBicubicEffect::Make(std::move(proxy), textureMatrix, domain);
        } else {
            static const GrSamplerState::WrapMode kClampClamp[] = {
                    GrSamplerState::WrapMode::kClamp, GrSamplerState::WrapMode::kClamp};
            return GrBicubicEffect::Make(std::move(proxy), textureMatrix, kClampClamp);
        }
    }
}

sk_sp<GrTextureProxy> GrTextureProducer::refTextureProxyForParams(
        const GrSamplerState& sampler,
        SkColorSpace* dstColorSpace,
        sk_sp<SkColorSpace>* proxyColorSpace,
        SkScalar scaleAdjust[2]) {
    // Check that the caller pre-initialized scaleAdjust
    SkASSERT(!scaleAdjust || (scaleAdjust[0] == 1 && scaleAdjust[1] == 1));
    // Check that if the caller passed nullptr for scaleAdjust that we're in the case where there
    // can be no scaling.
    SkDEBUGCODE(bool expectNoScale = (sampler.filter() != GrSamplerState::Filter::kMipMap &&
                                      !sampler.isRepeated()));
    SkASSERT(scaleAdjust || expectNoScale);

    int mipCount = SkMipMap::ComputeLevelCount(this->width(), this->height());
    bool willBeMipped = GrSamplerState::Filter::kMipMap == sampler.filter() && mipCount &&
                        fContext->contextPriv().caps()->mipMapSupport();

    auto result =
            this->onRefTextureProxyForParams(sampler, dstColorSpace, proxyColorSpace, willBeMipped,
                                             scaleAdjust);

    // Check to make sure that if we say the texture willBeMipped that the returned texture has mip
    // maps, unless the config is not copyable.
    SkASSERT(!result || !willBeMipped || result->mipMapped() == GrMipMapped::kYes ||
             !fContext->contextPriv().caps()->isConfigCopyable(result->config()));

    // Check that the "no scaling expected" case always returns a proxy of the same size as the
    // producer.
    SkASSERT(!result || !expectNoScale ||
             (result->width() == this->width() && result->height() == this->height()));
    return result;
}

sk_sp<GrTextureProxy> GrTextureProducer::refTextureProxy(GrMipMapped willNeedMips,
                                                         SkColorSpace* dstColorSpace,
                                                         sk_sp<SkColorSpace>* proxyColorSpace) {
    GrSamplerState::Filter filter =
            GrMipMapped::kNo == willNeedMips ? GrSamplerState::Filter::kNearest
                                             : GrSamplerState::Filter::kMipMap;
    GrSamplerState sampler(GrSamplerState::WrapMode::kClamp, filter);

    int mipCount = SkMipMap::ComputeLevelCount(this->width(), this->height());
    bool willBeMipped = GrSamplerState::Filter::kMipMap == sampler.filter() && mipCount &&
                        fContext->contextPriv().caps()->mipMapSupport();

    auto result =
            this->onRefTextureProxyForParams(sampler, dstColorSpace, proxyColorSpace,
                                             willBeMipped, nullptr);

    // Check to make sure that if we say the texture willBeMipped that the returned texture has mip
    // maps, unless the config is not copyable.
    SkASSERT(!result || !willBeMipped || result->mipMapped() == GrMipMapped::kYes ||
             !fContext->contextPriv().caps()->isConfigCopyable(result->config()));

    // Check that no scaling occured and we returned a proxy of the same size as the producer.
    SkASSERT(!result || (result->width() == this->width() && result->height() == this->height()));
    return result;
}