xref: /aosp_15_r20/external/skia/src/gpu/ganesh/effects/GrTextureEffect.h (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2017 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 GrTextureEffect_DEFINED
9 #define GrTextureEffect_DEFINED
10 
11 #include "include/core/SkMatrix.h"
12 #include "include/core/SkPoint.h"
13 #include "include/core/SkRect.h"
14 #include "include/core/SkSamplingOptions.h"
15 #include "src/gpu/ganesh/GrCaps.h"
16 #include "src/gpu/ganesh/GrFragmentProcessor.h"
17 #include "src/gpu/ganesh/GrProcessorUnitTest.h"
18 #include "src/gpu/ganesh/GrSamplerState.h"
19 #include "src/gpu/ganesh/GrSurfaceProxyView.h"
20 #include "src/gpu/ganesh/GrTextureProxy.h"
21 #include "src/gpu/ganesh/glsl/GrGLSLShaderBuilder.h"
22 
23 #include <cstdint>
24 #include <memory>
25 
26 class GrGLSLProgramDataManager;
27 class GrTexture;
28 enum SkAlphaType : int;
29 namespace skgpu { class KeyBuilder; }
30 struct GrShaderCaps;
31 
32 class GrTextureEffect : public GrFragmentProcessor {
33 public:
34     inline static constexpr float kDefaultBorder[4] = {0};
35 
36     // An extra inset amount to apply to subset clamp boundaries to ensure clamped coordinates will
37     // access texel (i-1) to i instead of i to (i+1), even if the weights should mean the clamped
38     // color is still equal to texel i's value.
39 #if defined(SK_USE_SAFE_INSET_FOR_TEXTURE_SAMPLING)
40     inline static constexpr float kInsetEpsilon = 0.0001f;
41 #else
42     // A value of 0 means that coordinates for linear filtering will be clamped to exactly 1/2px
43     // inset from the defined data boundary (for approx-fit textures that don't fill to the HW
44     // boundaries). For bottom/right edges this can then access the row or column outside of the
45     // defined data. The filtering weight should be 0, which should be safe for non-floating-point
46     // color types, and not have any impact on the clamped sample. In practice, devices with lower
47     // precision texture coordinates may not actually evaluate with a weight of 0, and it is
48     // caught by Swiftshader+MSAN.
49     inline static constexpr float kInsetEpsilon = 0.f;
50 #endif
51 
52     inline static constexpr float kLinearInset = 0.5f + kInsetEpsilon;
53 
54     /** Make from a filter. The sampler will be configured with clamp mode. */
55     static std::unique_ptr<GrFragmentProcessor> Make(
56             GrSurfaceProxyView,
57             SkAlphaType,
58             const SkMatrix& = SkMatrix::I(),
59             GrSamplerState::Filter = GrSamplerState::Filter::kNearest,
60             GrSamplerState::MipmapMode mipmapMode = GrSamplerState::MipmapMode::kNone);
61 
62     /**
63      * Make from a full GrSamplerState. Caps are required to determine support for kClampToBorder.
64      * This will be emulated in the shader if there is no hardware support.
65      */
66     static std::unique_ptr<GrFragmentProcessor> Make(GrSurfaceProxyView, SkAlphaType,
67                                                      const SkMatrix&, GrSamplerState,
68                                                      const GrCaps& caps,
69                                                      const float border[4] = kDefaultBorder);
70 
71     /**
72      * Makes a texture effect that samples a subset of a texture. The wrap modes of the
73      * GrSampleState are applied to the subset in the shader rather than using HW samplers.
74      * The 'subset' parameter specifies the texels in the base level. The shader code will
75      * avoid allowing linear filtering to read outside the texel window. However, if MIP
76      * filtering is used and a shader invocation reads from a level other than the base
77      * then it may read texel values that were computed from in part from base level texels
78      * outside the window. More specifically, we treat the MIP map case exactly like the
79      * linear case in terms of how the final texture coords are computed. If
80      * alwaysUseShaderTileMode is true then MakeSubset won't attempt to use HW wrap modes if the
81      * subset contains the entire texture.
82      */
83     static std::unique_ptr<GrFragmentProcessor> MakeSubset(GrSurfaceProxyView,
84                                                            SkAlphaType,
85                                                            const SkMatrix&,
86                                                            GrSamplerState,
87                                                            const SkRect& subset,
88                                                            const GrCaps& caps,
89                                                            const float border[4] = kDefaultBorder,
90                                                            bool alwaysUseShaderTileMode = false);
91 
92     /**
93      * The same as above but also takes a 'domain' that specifies any known limit on the post-
94      * matrix texture coords that will be used to sample the texture. Specifying this requires
95      * knowledge of how this effect will be nested into a paint, the local coords used with the
96      * draw, etc. It is only used to attempt to optimize away the shader subset calculations.
97      */
98     static std::unique_ptr<GrFragmentProcessor> MakeSubset(GrSurfaceProxyView,
99                                                            SkAlphaType,
100                                                            const SkMatrix&,
101                                                            GrSamplerState,
102                                                            const SkRect& subset,
103                                                            const SkRect& domain,
104                                                            const GrCaps& caps,
105                                                            const float border[4] = kDefaultBorder);
106 
107     /**
108      * Like MakeSubset() but always uses kLinear filtering. MakeSubset() uses the subset rect
109      * dimensions to determine the period of the wrap mode (for repeat and mirror). Once it computes
110      * the wrapped texture coordinate inside subset rect it further clamps it to a 0.5 inset rect of
111      * subset. When subset is an integer rectangle this clamping avoids the hw linear filtering from
112      * reading texels just outside the subset rect. This factory allows a custom inset clamping
113      * distance rather than 0.5, allowing those neighboring texels to influence the linear filtering
114      * sample result. If there is a known restriction on the post-matrix texture coords it can be
115      * specified using domain.
116      */
117     static std::unique_ptr<GrFragmentProcessor> MakeCustomLinearFilterInset(
118             GrSurfaceProxyView,
119             SkAlphaType,
120             const SkMatrix&,
121             GrSamplerState::WrapMode wx,
122             GrSamplerState::WrapMode wy,
123             const SkRect& subset,
124             const SkRect* domain,
125             SkVector inset,
126             const GrCaps& caps,
127             const float border[4] = kDefaultBorder);
128 
129     std::unique_ptr<GrFragmentProcessor> clone() const override;
130 
name()131     const char* name() const override { return "TextureEffect"; }
132 
samplerState()133     GrSamplerState samplerState() const { return fSamplerState; }
134 
texture()135     GrTexture* texture() const { return fView.asTextureProxy()->peekTexture(); }
136 
view()137     const GrSurfaceProxyView& view() const { return fView; }
138 
139     // Gets a matrix that is concat'ed by wrapping GrMatrixEffect that handles y-flip and coord
140     // normalization if required. This matrix is not always known when we make the GrTextureEffect
141     // because of fully-lazy proxies. Hence, this method  exists to allow this concat to happen
142     // after proxy instantiation with coordination from GrMatrixEffect.
143     SkMatrix coordAdjustmentMatrix() const;
144 
145     class Impl : public ProgramImpl {
146     public:
147         void emitCode(EmitArgs&) override;
148 
setSamplerHandle(GrGLSLShaderBuilder::SamplerHandle handle)149         void setSamplerHandle(GrGLSLShaderBuilder::SamplerHandle handle) {
150             fSamplerHandle = handle;
151         }
152 
153     private:
154         void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
155 
156         UniformHandle fSubsetUni;
157         UniformHandle fClampUni;
158         UniformHandle fIDimsUni;
159         UniformHandle fBorderUni;
160         GrGLSLShaderBuilder::SamplerHandle fSamplerHandle;
161     };
162 
163 private:
164     struct Sampling;
165 
166     /**
167      * Possible implementation of wrap mode in shader code. Some modes are specialized by
168      * filter.
169      */
170     enum class ShaderMode : uint16_t {
171         kNone,                   // Using HW mode
172         kClamp,                  // Shader based clamp, no filter specialization
173         kRepeat_Nearest_None,    // Simple repeat for nearest sampling, no mipmapping
174         kRepeat_Linear_None,     // Filter the subset boundary for kRepeat mode, no mip mapping
175         kRepeat_Linear_Mipmap,   // Logic for linear filtering and LOD selection with kRepeat mode.
176         kRepeat_Nearest_Mipmap,  // Logic for nearest filtering and LOD selection with kRepeat mode.
177         kMirrorRepeat,           // Mirror repeat (doesn't depend on filter))
178         kClampToBorder_Nearest,  // Logic for hard transition to border color when not filtering.
179         kClampToBorder_Filter,   // Logic for fading to border color when filtering.
180     };
181     static ShaderMode GetShaderMode(GrSamplerState::WrapMode,
182                                     GrSamplerState::Filter,
183                                     GrSamplerState::MipmapMode);
184     static bool ShaderModeIsClampToBorder(ShaderMode);
185     // To keep things a little simpler, when we have filtering logic in the shader we
186     // operate on unnormalized texture coordinates. We will add a uniform that stores
187     // {1/w, 1/h} in a float2 and normalizes after the mode is handled if the texture
188     // is not rectangle.
189     static bool ShaderModeRequiresUnormCoord(ShaderMode);
190 
191     GrSurfaceProxyView fView;
192     GrSamplerState fSamplerState;
193     float fBorder[4];
194     SkRect fSubset;
195     SkRect fClamp;
196     ShaderMode fShaderModes[2];
197 
198     inline GrTextureEffect(GrSurfaceProxyView, SkAlphaType, const Sampling&);
199 
200     explicit GrTextureEffect(const GrTextureEffect& src);
201 
202     std::unique_ptr<ProgramImpl> onMakeProgramImpl() const override;
203 
204     void onAddToKey(const GrShaderCaps&, skgpu::KeyBuilder*) const override;
205 
206     bool onIsEqual(const GrFragmentProcessor&) const override;
207 
208     bool matrixEffectShouldNormalize() const;
209 
hasClampToBorderShaderMode()210     bool hasClampToBorderShaderMode() const {
211         return ShaderModeIsClampToBorder(fShaderModes[0]) ||
212                ShaderModeIsClampToBorder(fShaderModes[1]);
213     }
214 
215     GR_DECLARE_FRAGMENT_PROCESSOR_TEST
216 
217     using INHERITED = GrFragmentProcessor;
218 };
219 #endif
220