1/*
2 * Copyright 2012 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#ifndef GrTextureDomain_DEFINED
9#define GrTextureDomain_DEFINED
10
11#include "src/gpu/GrCoordTransform.h"
12#include "src/gpu/GrFragmentProcessor.h"
13#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
14#include "src/gpu/glsl/GrGLSLProgramDataManager.h"
15
16class GrGLProgramBuilder;
17class GrGLSLShaderBuilder;
18class GrInvariantOutput;
19class GrGLSLUniformHandler;
20struct SkRect;
21
22/**
23 * Limits a texture's lookup coordinates to a domain. Samples outside the domain are either clamped
24 * the edge of the domain or result in a half4 of zeros (decal mode). The domain is clipped to
25 * normalized texture coords ([0,1]x[0,1] square). Bilinear filtering can cause texels outside the
26 * domain to affect the read value unless the caller considers this when calculating the domain.
27 */
28class GrTextureDomain {
29public:
30 enum Mode {
31 // Ignore the texture domain rectangle.
32 kIgnore_Mode,
33 // Clamp texture coords to the domain rectangle.
34 kClamp_Mode,
35 // Treat the area outside the domain rectangle as fully transparent.
36 kDecal_Mode,
37 // Wrap texture coordinates. NOTE: filtering may not work as expected because Bilerp will
38 // read texels outside of the domain. We could perform additional texture reads and filter
39 // in the shader, but are not currently doing this for performance reasons
40 kRepeat_Mode,
41 // Mirror wrap texture coordinates. NOTE: suffers the same filtering limitation as kRepeat.
42 kMirrorRepeat_Mode,
43
44 kLastMode = kMirrorRepeat_Mode
45 };
46 static const int kModeCount = kLastMode + 1;
47
48 static const GrTextureDomain& IgnoredDomain() {
49 static const GrTextureDomain gDomain((GrTextureProxy*)nullptr,
50 SkRect::MakeEmpty(), kIgnore_Mode, kIgnore_Mode);
51 return gDomain;
52 }
53
54 /**
55 * Construct a domain used to sample a GrFragmentProcessor.
56 *
57 * @param index Pass a value >= 0 if using multiple texture domains in the same effect.
58 * It is used to keep inserted variables from causing name collisions.
59 */
60 GrTextureDomain(const SkRect& domain, Mode modeX, Mode modeY, int index = -1);
61
62 /**
63 * Construct a domain used to directly sampler a texture.
64 *
65 * @param index Pass a value >= 0 if using multiple texture domains in the same effect.
66 * It is used to keep inserted variables from causing name collisions.
67 */
68 GrTextureDomain(GrSurfaceProxy*, const SkRect& domain, Mode modeX, Mode modeY, int index = -1);
69
70 GrTextureDomain(const GrTextureDomain&) = default;
71
72 const SkRect& domain() const { return fDomain; }
73 Mode modeX() const { return fModeX; }
74 Mode modeY() const { return fModeY; }
75
76 /*
77 * Computes a domain that bounds all the texels in texelRect, possibly insetting by half a pixel
78 * depending on the mode. The mode is used for both axes.
79 */
80 static const SkRect MakeTexelDomain(const SkIRect& texelRect, Mode mode) {
81 return MakeTexelDomain(texelRect, mode, mode);
82 }
83
84 static const SkRect MakeTexelDomain(const SkIRect& texelRect, Mode modeX, Mode modeY) {
85 // For Clamp and decal modes, inset by half a texel
86 SkScalar insetX = ((modeX == kClamp_Mode || modeX == kDecal_Mode) && texelRect.width() > 0)
87 ? SK_ScalarHalf : 0;
88 SkScalar insetY = ((modeY == kClamp_Mode || modeY == kDecal_Mode) && texelRect.height() > 0)
89 ? SK_ScalarHalf : 0;
90 return SkRect::MakeLTRB(texelRect.fLeft + insetX, texelRect.fTop + insetY,
91 texelRect.fRight - insetX, texelRect.fBottom - insetY);
92 }
93
94 // Convenience to determine if any axis of a texture uses an explicit decal mode or the hardware
95 // clamp to border decal mode.
96 static bool IsDecalSampled(GrSamplerState::WrapMode wrapX, GrSamplerState::WrapMode wrapY,
97 Mode modeX, Mode modeY) {
98 return wrapX == GrSamplerState::WrapMode::kClampToBorder ||
99 wrapY == GrSamplerState::WrapMode::kClampToBorder ||
100 modeX == kDecal_Mode ||
101 modeY == kDecal_Mode;
102 }
103
104 static bool IsDecalSampled(const GrSamplerState::WrapMode wraps[2], Mode modeX, Mode modeY) {
105 return IsDecalSampled(wraps[0], wraps[1], modeX, modeY);
106 }
107
108 static bool IsDecalSampled(GrSamplerState sampler, Mode modeX, Mode modeY) {
109 return IsDecalSampled(sampler.wrapModeX(), sampler.wrapModeY(), modeX, modeY);
110 }
111
112 bool operator==(const GrTextureDomain& that) const {
113 return fModeX == that.fModeX && fModeY == that.fModeY &&
114 (kIgnore_Mode == fModeX || (fDomain.fLeft == that.fDomain.fLeft &&
115 fDomain.fRight == that.fDomain.fRight)) &&
116 (kIgnore_Mode == fModeY || (fDomain.fTop == that.fDomain.fTop &&
117 fDomain.fBottom == that.fDomain.fBottom));
118 }
119 bool operator!=(const GrTextureDomain& that) const { return !(*this == that); }
120
121 /**
122 * A GrGLSLFragmentProcessor subclass that corresponds to a GrProcessor subclass that uses
123 * GrTextureDomain should include this helper. It generates the texture domain GLSL, produces
124 * the part of the effect key that reflects the texture domain code, and performs the uniform
125 * uploads necessary for texture domains.
126 */
127 class GLDomain {
128 public:
129 GLDomain() = default;
130
131 /**
132 * Call this from GrGLSLFragmentProcessor::emitCode() to sample a child processor WRT the
133 * domain and mode.
134 *
135 * @param outcolor name of half4 variable to hold the sampled color.
136 * @param inCoords name of float2 variable containing the coords to be used with the
137 * domain.
138 * @param inColor color passed to the child processor.
139 */
140 void sampleProcessor(const GrFragmentProcessor* owner,
141 const GrTextureDomain& textureDomain,
142 const char* inColor,
143 const char* outColor,
144 const SkString& inCoords,
145 GrGLSLFragmentProcessor* parent,
146 GrGLSLFragmentProcessor::EmitArgs& args,
147 int childIndex);
148
149 /**
150 * Call this from GrGLSLFragmentProcessor::emitCode() to sample the texture WRT the domain
151 * and mode.
152 *
153 * @param outcolor name of half4 variable to hold the sampled color.
154 * @param inCoords name of float2 variable containing the coords to be used with the
155 * domain.
156 * @param inModulateColor if non-nullptr the sampled color will be modulated with this
157 * expression before being written to outColor.
158 */
159 void sampleTexture(const GrFragmentProcessor* owner,
160 GrGLSLShaderBuilder* builder,
161 GrGLSLUniformHandler* uniformHandler,
162 const GrShaderCaps* shaderCaps,
163 const GrTextureDomain& textureDomain,
164 const char* outColor,
165 const SkString& inCoords,
166 GrGLSLFragmentProcessor::SamplerHandle sampler,
167 const char* inModulateColor = nullptr);
168
169 /**
170 * Call this from GrGLSLFragmentProcessor::setData() to upload uniforms necessary for the
171 * domain. 'filterIfDecal' determines whether the transition to transparent black at the
172 * edge of domain is linearly interpolated over a unit interval or is "hard" when
173 * kDecal_Mode is used.
174 */
175 void setData(const GrGLSLProgramDataManager&, const GrTextureDomain&, bool filterIfDecal);
176
177 /**
178 * Call this from GrGLSLFragmentProcessor::setData() to upload uniforms necessary for the
179 * texture domain used with a texture proxy. The rectangle is automatically adjusted to
180 * account for the texture's origin. Filtering at the edge of the domain is inferred from
181 * the GrSamplerState's filter mode.
182 */
183 void setData(const GrGLSLProgramDataManager&, const GrTextureDomain&,
184 const GrSurfaceProxyView&, GrSamplerState state);
185 /** Same as above but with direct control over decal filtering. */
186 void setData(const GrGLSLProgramDataManager&, const GrTextureDomain&, const GrSurfaceProxy*,
187 GrSurfaceOrigin, bool filterIfDecal);
188
189 enum {
190 kModeBits = 3, // See DomainKey().
191 kDomainKeyBits = 4
192 };
193
194 /**
195 * GrGLSLFragmentProcessor::GenKey() must call this and include the returned value in it's
196 * computed key. The returned will be limited to the lower kDomainKeyBits bits.
197 */
198 static uint32_t DomainKey(const GrTextureDomain& domain) {
199 static_assert(kModeCount <= (1 << kModeBits));
200 return domain.modeX() | (domain.modeY() << kModeBits);
201 }
202
203 private:
204 // Takes a builder and a coord and appends to the builder a string that is an expression
205 // the evaluates to a half4 color.
206 using AppendSample = SkString(const char* coord);
207
208 void sample(const GrFragmentProcessor* owner,
209 GrGLSLShaderBuilder* builder,
210 GrGLSLUniformHandler* uniformHandler,
211 const GrTextureDomain& textureDomain,
212 const char* outColor,
213 const SkString& inCoords,
214 const std::function<AppendSample>& color);
215
216 SkDEBUGCODE(Mode fModeX;)
217 SkDEBUGCODE(Mode fModeY;)
218 SkDEBUGCODE(bool fHasMode = false;)
219 GrGLSLProgramDataManager::UniformHandle fDomainUni;
220 SkString fDomainName;
221
222 // Only initialized if the domain has at least one decal axis
223 GrGLSLProgramDataManager::UniformHandle fDecalUni;
224 SkString fDecalName;
225
226 float fPrevDomain[4] = {SK_FloatNaN};
227 float fPrevDeclFilterWeights[3] = {SK_FloatNaN};
228 };
229
230protected:
231 SkRect fDomain;
232 Mode fModeX;
233 Mode fModeY;
234 int fIndex;
235};
236
237#endif
238