Line data Source code
1 : /*
2 : * Copyright 2013 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 "GrDistanceFieldGeoProc.h"
9 :
10 : #include "GrTexture.h"
11 : #include "SkDistanceFieldGen.h"
12 : #include "glsl/GrGLSLFragmentShaderBuilder.h"
13 : #include "glsl/GrGLSLGeometryProcessor.h"
14 : #include "glsl/GrGLSLProgramDataManager.h"
15 : #include "glsl/GrGLSLUniformHandler.h"
16 : #include "glsl/GrGLSLUtil.h"
17 : #include "glsl/GrGLSLVarying.h"
18 : #include "glsl/GrGLSLVertexShaderBuilder.h"
19 :
20 : // Assuming a radius of a little less than the diagonal of the fragment
21 : #define SK_DistanceFieldAAFactor "0.65"
22 :
23 0 : class GrGLDistanceFieldA8TextGeoProc : public GrGLSLGeometryProcessor {
24 : public:
25 0 : GrGLDistanceFieldA8TextGeoProc()
26 0 : : fViewMatrix(SkMatrix::InvalidMatrix())
27 : #ifdef SK_GAMMA_APPLY_TO_A8
28 : , fDistanceAdjust(-1.0f)
29 : #endif
30 0 : {}
31 :
32 0 : void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
33 : const GrDistanceFieldA8TextGeoProc& dfTexEffect =
34 0 : args.fGP.cast<GrDistanceFieldA8TextGeoProc>();
35 0 : GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
36 :
37 0 : GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
38 0 : GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
39 0 : GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
40 :
41 : // emit attributes
42 0 : varyingHandler->emitAttributes(dfTexEffect);
43 :
44 : #ifdef SK_GAMMA_APPLY_TO_A8
45 : // adjust based on gamma
46 : const char* distanceAdjustUniName = nullptr;
47 : // width, height, 1/(3*width)
48 : fDistanceAdjustUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
49 : kFloat_GrSLType, kDefault_GrSLPrecision,
50 : "DistanceAdjust", &distanceAdjustUniName);
51 : #endif
52 :
53 : // Setup pass through color
54 0 : varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
55 :
56 : // Setup position
57 0 : this->setupPosition(vertBuilder,
58 : uniformHandler,
59 : gpArgs,
60 0 : dfTexEffect.inPosition()->fName,
61 : dfTexEffect.viewMatrix(),
62 0 : &fViewMatrixUniform);
63 :
64 : // emit transforms
65 0 : this->emitTransforms(vertBuilder,
66 : varyingHandler,
67 : uniformHandler,
68 : gpArgs->fPositionVar,
69 0 : dfTexEffect.inPosition()->fName,
70 0 : args.fFPCoordTransformHandler);
71 :
72 : // add varyings
73 0 : GrGLSLVertToFrag recipScale(kFloat_GrSLType);
74 0 : GrGLSLVertToFrag uv(kVec2f_GrSLType);
75 0 : bool isUniformScale = (dfTexEffect.getFlags() & kUniformScale_DistanceFieldEffectMask) ==
76 0 : kUniformScale_DistanceFieldEffectMask;
77 0 : bool isSimilarity = SkToBool(dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag);
78 : bool isGammaCorrect =
79 0 : SkToBool(dfTexEffect.getFlags() & kGammaCorrect_DistanceFieldEffectFlag);
80 0 : varyingHandler->addVarying("TextureCoords", &uv, kHigh_GrSLPrecision);
81 0 : vertBuilder->codeAppendf("%s = %s;", uv.vsOut(), dfTexEffect.inTextureCoords()->fName);
82 :
83 : // compute numbers to be hardcoded to convert texture coordinates from float to int
84 0 : SkASSERT(dfTexEffect.numTextureSamplers() == 1);
85 0 : GrTexture* atlas = dfTexEffect.textureSampler(0).texture();
86 0 : SkASSERT(atlas && SkIsPow2(atlas->width()) && SkIsPow2(atlas->height()));
87 :
88 0 : GrGLSLVertToFrag st(kVec2f_GrSLType);
89 0 : varyingHandler->addVarying("IntTextureCoords", &st, kHigh_GrSLPrecision);
90 0 : vertBuilder->codeAppendf("%s = vec2(%d, %d) * %s;", st.vsOut(),
91 : atlas->width(), atlas->height(),
92 0 : dfTexEffect.inTextureCoords()->fName);
93 :
94 : // Use highp to work around aliasing issues
95 0 : fragBuilder->codeAppendf("highp vec2 uv = %s;\n", uv.fsIn());
96 :
97 0 : fragBuilder->codeAppend("\tfloat texColor = ");
98 0 : fragBuilder->appendTextureLookup(args.fTexSamplers[0],
99 : "uv",
100 0 : kVec2f_GrSLType);
101 0 : fragBuilder->codeAppend(".r;\n");
102 0 : fragBuilder->codeAppend("\tfloat distance = "
103 0 : SK_DistanceFieldMultiplier "*(texColor - " SK_DistanceFieldThreshold ");");
104 : #ifdef SK_GAMMA_APPLY_TO_A8
105 : // adjust width based on gamma
106 : fragBuilder->codeAppendf("distance -= %s;", distanceAdjustUniName);
107 : #endif
108 :
109 0 : fragBuilder->codeAppend("float afwidth;");
110 0 : if (isUniformScale) {
111 : // For uniform scale, we adjust for the effect of the transformation on the distance
112 : // by using the length of the gradient of the t coordinate in the y direction.
113 : // We use st coordinates to ensure we're mapping 1:1 from texel space to pixel space.
114 :
115 : // this gives us a smooth step across approximately one fragment
116 : #ifdef SK_VULKAN
117 : fragBuilder->codeAppendf("afwidth = abs(" SK_DistanceFieldAAFactor "*dFdx(%s.x));",
118 : st.fsIn());
119 : #else
120 : // We use the y gradient because there is a bug in the Mali 400 in the x direction.
121 0 : fragBuilder->codeAppendf("afwidth = abs(" SK_DistanceFieldAAFactor "*dFdy(%s.y));",
122 0 : st.fsIn());
123 : #endif
124 0 : } else if (isSimilarity) {
125 : // For similarity transform, we adjust the effect of the transformation on the distance
126 : // by using the length of the gradient of the texture coordinates. We use st coordinates
127 : // to ensure we're mapping 1:1 from texel space to pixel space.
128 : // We use the y gradient because there is a bug in the Mali 400 in the x direction.
129 :
130 : // this gives us a smooth step across approximately one fragment
131 : #ifdef SK_VULKAN
132 : fragBuilder->codeAppendf("float st_grad_len = length(dFdx(%s));", st.fsIn());
133 : #else
134 : // We use the y gradient because there is a bug in the Mali 400 in the x direction.
135 0 : fragBuilder->codeAppendf("float st_grad_len = length(dFdy(%s));", st.fsIn());
136 : #endif
137 0 : fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*st_grad_len);");
138 : } else {
139 : // For general transforms, to determine the amount of correction we multiply a unit
140 : // vector pointing along the SDF gradient direction by the Jacobian of the st coords
141 : // (which is the inverse transform for this fragment) and take the length of the result.
142 0 : fragBuilder->codeAppend("vec2 dist_grad = vec2(dFdx(distance), dFdy(distance));");
143 : // the length of the gradient may be 0, so we need to check for this
144 : // this also compensates for the Adreno, which likes to drop tiles on division by 0
145 0 : fragBuilder->codeAppend("float dg_len2 = dot(dist_grad, dist_grad);");
146 0 : fragBuilder->codeAppend("if (dg_len2 < 0.0001) {");
147 0 : fragBuilder->codeAppend("dist_grad = vec2(0.7071, 0.7071);");
148 0 : fragBuilder->codeAppend("} else {");
149 0 : fragBuilder->codeAppend("dist_grad = dist_grad*inversesqrt(dg_len2);");
150 0 : fragBuilder->codeAppend("}");
151 :
152 0 : fragBuilder->codeAppendf("vec2 Jdx = dFdx(%s);", st.fsIn());
153 0 : fragBuilder->codeAppendf("vec2 Jdy = dFdy(%s);", st.fsIn());
154 0 : fragBuilder->codeAppend("vec2 grad = vec2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,");
155 0 : fragBuilder->codeAppend(" dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);");
156 :
157 : // this gives us a smooth step across approximately one fragment
158 0 : fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
159 : }
160 :
161 : // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
162 : // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
163 : // mapped linearly to coverage, so use a linear step:
164 0 : if (isGammaCorrect) {
165 0 : fragBuilder->codeAppend(
166 0 : "float val = clamp(distance + afwidth / (2.0 * afwidth), 0.0, 1.0);");
167 : } else {
168 0 : fragBuilder->codeAppend("float val = smoothstep(-afwidth, afwidth, distance);");
169 : }
170 :
171 0 : fragBuilder->codeAppendf("%s = vec4(val);", args.fOutputCoverage);
172 0 : }
173 :
174 0 : void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
175 : FPCoordTransformIter&& transformIter) override {
176 : #ifdef SK_GAMMA_APPLY_TO_A8
177 : const GrDistanceFieldA8TextGeoProc& dfTexEffect = proc.cast<GrDistanceFieldA8TextGeoProc>();
178 : float distanceAdjust = dfTexEffect.getDistanceAdjust();
179 : if (distanceAdjust != fDistanceAdjust) {
180 : pdman.set1f(fDistanceAdjustUni, distanceAdjust);
181 : fDistanceAdjust = distanceAdjust;
182 : }
183 : #endif
184 0 : const GrDistanceFieldA8TextGeoProc& dfa8gp = proc.cast<GrDistanceFieldA8TextGeoProc>();
185 :
186 0 : if (!dfa8gp.viewMatrix().isIdentity() && !fViewMatrix.cheapEqualTo(dfa8gp.viewMatrix())) {
187 0 : fViewMatrix = dfa8gp.viewMatrix();
188 : float viewMatrix[3 * 3];
189 0 : GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix);
190 0 : pdman.setMatrix3f(fViewMatrixUniform, viewMatrix);
191 : }
192 0 : this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
193 0 : }
194 :
195 0 : static inline void GenKey(const GrGeometryProcessor& gp,
196 : const GrShaderCaps&,
197 : GrProcessorKeyBuilder* b) {
198 0 : const GrDistanceFieldA8TextGeoProc& dfTexEffect = gp.cast<GrDistanceFieldA8TextGeoProc>();
199 0 : uint32_t key = dfTexEffect.getFlags();
200 0 : key |= ComputePosKey(dfTexEffect.viewMatrix()) << 16;
201 0 : b->add32(key);
202 :
203 : // Currently we hardcode numbers to convert atlas coordinates to normalized floating point
204 0 : SkASSERT(gp.numTextureSamplers() == 1);
205 0 : GrTexture* atlas = gp.textureSampler(0).texture();
206 0 : SkASSERT(atlas);
207 0 : b->add32(atlas->width());
208 0 : b->add32(atlas->height());
209 0 : }
210 :
211 : private:
212 : SkMatrix fViewMatrix;
213 : UniformHandle fViewMatrixUniform;
214 : #ifdef SK_GAMMA_APPLY_TO_A8
215 : float fDistanceAdjust;
216 : UniformHandle fDistanceAdjustUni;
217 : #endif
218 :
219 : typedef GrGLSLGeometryProcessor INHERITED;
220 : };
221 :
222 : ///////////////////////////////////////////////////////////////////////////////
223 :
224 0 : GrDistanceFieldA8TextGeoProc::GrDistanceFieldA8TextGeoProc(GrResourceProvider* resourceProvider,
225 : GrColor color,
226 : const SkMatrix& viewMatrix,
227 : sk_sp<GrTextureProxy> proxy,
228 : const GrSamplerParams& params,
229 : #ifdef SK_GAMMA_APPLY_TO_A8
230 : float distanceAdjust,
231 : #endif
232 : uint32_t flags,
233 0 : bool usesLocalCoords)
234 : : fColor(color)
235 : , fViewMatrix(viewMatrix)
236 0 : , fTextureSampler(resourceProvider, std::move(proxy), params)
237 : #ifdef SK_GAMMA_APPLY_TO_A8
238 : , fDistanceAdjust(distanceAdjust)
239 : #endif
240 0 : , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
241 : , fInColor(nullptr)
242 0 : , fUsesLocalCoords(usesLocalCoords) {
243 0 : SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
244 0 : this->initClassID<GrDistanceFieldA8TextGeoProc>();
245 0 : fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
246 0 : kHigh_GrSLPrecision);
247 0 : fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
248 0 : fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kVec2us_GrVertexAttribType,
249 0 : kHigh_GrSLPrecision);
250 0 : this->addTextureSampler(&fTextureSampler);
251 0 : }
252 :
253 0 : void GrDistanceFieldA8TextGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps,
254 : GrProcessorKeyBuilder* b) const {
255 0 : GrGLDistanceFieldA8TextGeoProc::GenKey(*this, caps, b);
256 0 : }
257 :
258 : GrGLSLPrimitiveProcessor*
259 0 : GrDistanceFieldA8TextGeoProc::createGLSLInstance(const GrShaderCaps&) const {
260 0 : return new GrGLDistanceFieldA8TextGeoProc();
261 : }
262 :
263 : ///////////////////////////////////////////////////////////////////////////////
264 :
265 : GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldA8TextGeoProc);
266 :
267 : #if GR_TEST_UTILS
268 0 : sk_sp<GrGeometryProcessor> GrDistanceFieldA8TextGeoProc::TestCreate(GrProcessorTestData* d) {
269 0 : int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
270 0 : : GrProcessorUnitTest::kAlphaTextureIdx;
271 0 : sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
272 :
273 : static const SkShader::TileMode kTileModes[] = {
274 : SkShader::kClamp_TileMode,
275 : SkShader::kRepeat_TileMode,
276 : SkShader::kMirror_TileMode,
277 : };
278 : SkShader::TileMode tileModes[] = {
279 0 : kTileModes[d->fRandom->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
280 0 : kTileModes[d->fRandom->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
281 0 : };
282 0 : GrSamplerParams params(tileModes, d->fRandom->nextBool() ? GrSamplerParams::kBilerp_FilterMode
283 0 : : GrSamplerParams::kNone_FilterMode);
284 :
285 0 : uint32_t flags = 0;
286 0 : flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
287 0 : if (flags & kSimilarity_DistanceFieldEffectFlag) {
288 0 : flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
289 : }
290 :
291 : return GrDistanceFieldA8TextGeoProc::Make(d->resourceProvider(),
292 : GrRandomColor(d->fRandom),
293 : GrTest::TestMatrix(d->fRandom),
294 0 : std::move(proxy), params,
295 : #ifdef SK_GAMMA_APPLY_TO_A8
296 : d->fRandom->nextF(),
297 : #endif
298 : flags,
299 0 : d->fRandom->nextBool());
300 : }
301 : #endif
302 :
303 : ///////////////////////////////////////////////////////////////////////////////
304 :
305 0 : class GrGLDistanceFieldPathGeoProc : public GrGLSLGeometryProcessor {
306 : public:
307 0 : GrGLDistanceFieldPathGeoProc()
308 0 : : fViewMatrix(SkMatrix::InvalidMatrix())
309 0 : , fTextureSize(SkISize::Make(-1, -1)) {}
310 :
311 0 : void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
312 0 : const GrDistanceFieldPathGeoProc& dfTexEffect = args.fGP.cast<GrDistanceFieldPathGeoProc>();
313 :
314 0 : GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
315 :
316 0 : GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
317 0 : GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
318 0 : GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
319 :
320 : // emit attributes
321 0 : varyingHandler->emitAttributes(dfTexEffect);
322 :
323 0 : GrGLSLVertToFrag v(kVec2f_GrSLType);
324 0 : varyingHandler->addVarying("TextureCoords", &v, kHigh_GrSLPrecision);
325 :
326 : // setup pass through color
327 0 : varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
328 0 : vertBuilder->codeAppendf("%s = %s;", v.vsOut(), dfTexEffect.inTextureCoords()->fName);
329 :
330 : // Setup position
331 0 : this->setupPosition(vertBuilder,
332 : uniformHandler,
333 : gpArgs,
334 0 : dfTexEffect.inPosition()->fName,
335 : dfTexEffect.viewMatrix(),
336 0 : &fViewMatrixUniform);
337 :
338 : // emit transforms
339 0 : this->emitTransforms(vertBuilder,
340 : varyingHandler,
341 : uniformHandler,
342 : gpArgs->fPositionVar,
343 0 : dfTexEffect.inPosition()->fName,
344 0 : args.fFPCoordTransformHandler);
345 :
346 0 : const char* textureSizeUniName = nullptr;
347 : fTextureSizeUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
348 : kVec2f_GrSLType, kDefault_GrSLPrecision,
349 0 : "TextureSize", &textureSizeUniName);
350 :
351 : // Use highp to work around aliasing issues
352 0 : fragBuilder->codeAppendf("highp vec2 uv = %s;", v.fsIn());
353 :
354 0 : fragBuilder->codeAppend("float texColor = ");
355 0 : fragBuilder->appendTextureLookup(args.fTexSamplers[0],
356 : "uv",
357 0 : kVec2f_GrSLType);
358 0 : fragBuilder->codeAppend(".r;");
359 0 : fragBuilder->codeAppend("float distance = "
360 0 : SK_DistanceFieldMultiplier "*(texColor - " SK_DistanceFieldThreshold ");");
361 :
362 0 : fragBuilder->codeAppendf("highp vec2 st = uv*%s;", textureSizeUniName);
363 0 : fragBuilder->codeAppend("float afwidth;");
364 0 : bool isUniformScale = (dfTexEffect.getFlags() & kUniformScale_DistanceFieldEffectMask) ==
365 0 : kUniformScale_DistanceFieldEffectMask;
366 0 : bool isSimilarity = SkToBool(dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag);
367 : bool isGammaCorrect =
368 0 : SkToBool(dfTexEffect.getFlags() & kGammaCorrect_DistanceFieldEffectFlag);
369 0 : if (isUniformScale) {
370 : // For uniform scale, we adjust for the effect of the transformation on the distance
371 : // by using the length of the gradient of the t coordinate in the y direction.
372 : // We use st coordinates to ensure we're mapping 1:1 from texel space to pixel space.
373 :
374 : // this gives us a smooth step across approximately one fragment
375 : #ifdef SK_VULKAN
376 : fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*dFdx(st.x));");
377 : #else
378 : // We use the y gradient because there is a bug in the Mali 400 in the x direction.
379 0 : fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*dFdy(st.y));");
380 : #endif
381 0 : } else if (isSimilarity) {
382 : // For similarity transform, we adjust the effect of the transformation on the distance
383 : // by using the length of the gradient of the texture coordinates. We use st coordinates
384 : // to ensure we're mapping 1:1 from texel space to pixel space.
385 :
386 : // this gives us a smooth step across approximately one fragment
387 : #ifdef SK_VULKAN
388 : fragBuilder->codeAppend("float st_grad_len = length(dFdx(st));");
389 : #else
390 : // We use the y gradient because there is a bug in the Mali 400 in the x direction.
391 0 : fragBuilder->codeAppend("float st_grad_len = length(dFdy(st));");
392 : #endif
393 0 : fragBuilder->codeAppend("afwidth = abs(" SK_DistanceFieldAAFactor "*st_grad_len);");
394 : } else {
395 : // For general transforms, to determine the amount of correction we multiply a unit
396 : // vector pointing along the SDF gradient direction by the Jacobian of the st coords
397 : // (which is the inverse transform for this fragment) and take the length of the result.
398 0 : fragBuilder->codeAppend("vec2 dist_grad = vec2(dFdx(distance), dFdy(distance));");
399 : // the length of the gradient may be 0, so we need to check for this
400 : // this also compensates for the Adreno, which likes to drop tiles on division by 0
401 0 : fragBuilder->codeAppend("float dg_len2 = dot(dist_grad, dist_grad);");
402 0 : fragBuilder->codeAppend("if (dg_len2 < 0.0001) {");
403 0 : fragBuilder->codeAppend("dist_grad = vec2(0.7071, 0.7071);");
404 0 : fragBuilder->codeAppend("} else {");
405 0 : fragBuilder->codeAppend("dist_grad = dist_grad*inversesqrt(dg_len2);");
406 0 : fragBuilder->codeAppend("}");
407 :
408 0 : fragBuilder->codeAppend("vec2 Jdx = dFdx(st);");
409 0 : fragBuilder->codeAppend("vec2 Jdy = dFdy(st);");
410 0 : fragBuilder->codeAppend("vec2 grad = vec2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,");
411 0 : fragBuilder->codeAppend(" dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);");
412 :
413 : // this gives us a smooth step across approximately one fragment
414 0 : fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
415 : }
416 : // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
417 : // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
418 : // mapped linearly to coverage, so use a linear step:
419 0 : if (isGammaCorrect) {
420 0 : fragBuilder->codeAppend(
421 0 : "float val = clamp(distance + afwidth / (2.0 * afwidth), 0.0, 1.0);");
422 : } else {
423 0 : fragBuilder->codeAppend("float val = smoothstep(-afwidth, afwidth, distance);");
424 : }
425 :
426 0 : fragBuilder->codeAppendf("%s = vec4(val);", args.fOutputCoverage);
427 0 : }
428 :
429 0 : void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
430 : FPCoordTransformIter&& transformIter) override {
431 0 : SkASSERT(fTextureSizeUni.isValid());
432 :
433 0 : GrTexture* texture = proc.textureSampler(0).texture();
434 0 : if (texture->width() != fTextureSize.width() ||
435 0 : texture->height() != fTextureSize.height()) {
436 0 : fTextureSize = SkISize::Make(texture->width(), texture->height());
437 0 : pdman.set2f(fTextureSizeUni,
438 0 : SkIntToScalar(fTextureSize.width()),
439 0 : SkIntToScalar(fTextureSize.height()));
440 : }
441 :
442 0 : const GrDistanceFieldPathGeoProc& dfpgp = proc.cast<GrDistanceFieldPathGeoProc>();
443 :
444 0 : if (!dfpgp.viewMatrix().isIdentity() && !fViewMatrix.cheapEqualTo(dfpgp.viewMatrix())) {
445 0 : fViewMatrix = dfpgp.viewMatrix();
446 : float viewMatrix[3 * 3];
447 0 : GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix);
448 0 : pdman.setMatrix3f(fViewMatrixUniform, viewMatrix);
449 : }
450 0 : this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
451 0 : }
452 :
453 0 : static inline void GenKey(const GrGeometryProcessor& gp,
454 : const GrShaderCaps&,
455 : GrProcessorKeyBuilder* b) {
456 0 : const GrDistanceFieldPathGeoProc& dfTexEffect = gp.cast<GrDistanceFieldPathGeoProc>();
457 :
458 0 : uint32_t key = dfTexEffect.getFlags();
459 0 : key |= ComputePosKey(dfTexEffect.viewMatrix()) << 16;
460 0 : b->add32(key);
461 0 : }
462 :
463 : private:
464 : UniformHandle fTextureSizeUni;
465 : UniformHandle fViewMatrixUniform;
466 : SkMatrix fViewMatrix;
467 : SkISize fTextureSize;
468 :
469 : typedef GrGLSLGeometryProcessor INHERITED;
470 : };
471 :
472 : ///////////////////////////////////////////////////////////////////////////////
473 0 : GrDistanceFieldPathGeoProc::GrDistanceFieldPathGeoProc(
474 : GrResourceProvider* resourceProvider,
475 : GrColor color,
476 : const SkMatrix& viewMatrix,
477 : sk_sp<GrTextureProxy> proxy,
478 : const GrSamplerParams& params,
479 : uint32_t flags,
480 0 : bool usesLocalCoords)
481 : : fColor(color)
482 : , fViewMatrix(viewMatrix)
483 0 : , fTextureSampler(resourceProvider, std::move(proxy), params)
484 0 : , fFlags(flags & kNonLCD_DistanceFieldEffectMask)
485 : , fInColor(nullptr)
486 0 : , fUsesLocalCoords(usesLocalCoords) {
487 0 : SkASSERT(!(flags & ~kNonLCD_DistanceFieldEffectMask));
488 0 : this->initClassID<GrDistanceFieldPathGeoProc>();
489 0 : fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
490 0 : kHigh_GrSLPrecision);
491 0 : fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
492 0 : fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kVec2us_GrVertexAttribType);
493 0 : this->addTextureSampler(&fTextureSampler);
494 0 : }
495 :
496 0 : void GrDistanceFieldPathGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps,
497 : GrProcessorKeyBuilder* b) const {
498 0 : GrGLDistanceFieldPathGeoProc::GenKey(*this, caps, b);
499 0 : }
500 :
501 : GrGLSLPrimitiveProcessor*
502 0 : GrDistanceFieldPathGeoProc::createGLSLInstance(const GrShaderCaps&) const {
503 0 : return new GrGLDistanceFieldPathGeoProc();
504 : }
505 :
506 : ///////////////////////////////////////////////////////////////////////////////
507 :
508 : GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldPathGeoProc);
509 :
510 : #if GR_TEST_UTILS
511 0 : sk_sp<GrGeometryProcessor> GrDistanceFieldPathGeoProc::TestCreate(GrProcessorTestData* d) {
512 0 : int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx
513 0 : : GrProcessorUnitTest::kAlphaTextureIdx;
514 0 : sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
515 :
516 : static const SkShader::TileMode kTileModes[] = {
517 : SkShader::kClamp_TileMode,
518 : SkShader::kRepeat_TileMode,
519 : SkShader::kMirror_TileMode,
520 : };
521 : SkShader::TileMode tileModes[] = {
522 0 : kTileModes[d->fRandom->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
523 0 : kTileModes[d->fRandom->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
524 0 : };
525 0 : GrSamplerParams params(tileModes, d->fRandom->nextBool() ? GrSamplerParams::kBilerp_FilterMode
526 0 : : GrSamplerParams::kNone_FilterMode);
527 :
528 0 : uint32_t flags = 0;
529 0 : flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
530 0 : if (flags & kSimilarity_DistanceFieldEffectFlag) {
531 0 : flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
532 : }
533 :
534 : return GrDistanceFieldPathGeoProc::Make(d->resourceProvider(),
535 : GrRandomColor(d->fRandom),
536 : GrTest::TestMatrix(d->fRandom),
537 0 : std::move(proxy),
538 : params,
539 : flags,
540 0 : d->fRandom->nextBool());
541 : }
542 : #endif
543 :
544 : ///////////////////////////////////////////////////////////////////////////////
545 :
546 0 : class GrGLDistanceFieldLCDTextGeoProc : public GrGLSLGeometryProcessor {
547 : public:
548 0 : GrGLDistanceFieldLCDTextGeoProc()
549 0 : : fViewMatrix(SkMatrix::InvalidMatrix()) {
550 0 : fDistanceAdjust = GrDistanceFieldLCDTextGeoProc::DistanceAdjust::Make(1.0f, 1.0f, 1.0f);
551 0 : }
552 :
553 0 : void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
554 : const GrDistanceFieldLCDTextGeoProc& dfTexEffect =
555 0 : args.fGP.cast<GrDistanceFieldLCDTextGeoProc>();
556 :
557 0 : GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
558 0 : GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
559 0 : GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
560 :
561 : // emit attributes
562 0 : varyingHandler->emitAttributes(dfTexEffect);
563 :
564 0 : GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
565 :
566 : // setup pass through color
567 0 : varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
568 :
569 : // Setup position
570 0 : this->setupPosition(vertBuilder,
571 : uniformHandler,
572 : gpArgs,
573 0 : dfTexEffect.inPosition()->fName,
574 : dfTexEffect.viewMatrix(),
575 0 : &fViewMatrixUniform);
576 :
577 : // emit transforms
578 0 : this->emitTransforms(vertBuilder,
579 : varyingHandler,
580 : uniformHandler,
581 : gpArgs->fPositionVar,
582 0 : dfTexEffect.inPosition()->fName,
583 0 : args.fFPCoordTransformHandler);
584 :
585 : // set up varyings
586 0 : bool isUniformScale = (dfTexEffect.getFlags() & kUniformScale_DistanceFieldEffectMask) ==
587 0 : kUniformScale_DistanceFieldEffectMask;
588 0 : bool isSimilarity = SkToBool(dfTexEffect.getFlags() & kSimilarity_DistanceFieldEffectFlag);
589 : bool isGammaCorrect =
590 0 : SkToBool(dfTexEffect.getFlags() & kGammaCorrect_DistanceFieldEffectFlag);
591 0 : GrGLSLVertToFrag recipScale(kFloat_GrSLType);
592 0 : GrGLSLVertToFrag uv(kVec2f_GrSLType);
593 0 : varyingHandler->addVarying("TextureCoords", &uv, kHigh_GrSLPrecision);
594 0 : vertBuilder->codeAppendf("%s = %s;", uv.vsOut(), dfTexEffect.inTextureCoords()->fName);
595 :
596 : // compute numbers to be hardcoded to convert texture coordinates from float to int
597 0 : SkASSERT(dfTexEffect.numTextureSamplers() == 1);
598 0 : GrTexture* atlas = dfTexEffect.textureSampler(0).texture();
599 0 : SkASSERT(atlas && SkIsPow2(atlas->width()) && SkIsPow2(atlas->height()));
600 :
601 0 : GrGLSLVertToFrag st(kVec2f_GrSLType);
602 0 : varyingHandler->addVarying("IntTextureCoords", &st, kHigh_GrSLPrecision);
603 0 : vertBuilder->codeAppendf("%s = vec2(%d, %d) * %s;", st.vsOut(),
604 : atlas->width(), atlas->height(),
605 0 : dfTexEffect.inTextureCoords()->fName);
606 :
607 : // add frag shader code
608 :
609 : // create LCD offset adjusted by inverse of transform
610 : // Use highp to work around aliasing issues
611 0 : fragBuilder->codeAppendf("highp vec2 uv = %s;\n", uv.fsIn());
612 :
613 0 : SkScalar lcdDelta = 1.0f / (3.0f * atlas->width());
614 0 : if (dfTexEffect.getFlags() & kBGR_DistanceFieldEffectFlag) {
615 0 : fragBuilder->codeAppendf("highp float delta = -%.*f;\n", SK_FLT_DECIMAL_DIG, lcdDelta);
616 : } else {
617 0 : fragBuilder->codeAppendf("highp float delta = %.*f;\n", SK_FLT_DECIMAL_DIG, lcdDelta);
618 : }
619 0 : if (isUniformScale) {
620 : #ifdef SK_VULKAN
621 : fragBuilder->codeAppendf("float st_grad_len = abs(dFdx(%s.x));", st.fsIn());
622 : #else
623 : // We use the y gradient because there is a bug in the Mali 400 in the x direction.
624 0 : fragBuilder->codeAppendf("float st_grad_len = abs(dFdy(%s.y));", st.fsIn());
625 : #endif
626 0 : fragBuilder->codeAppend("vec2 offset = vec2(st_grad_len*delta, 0.0);");
627 0 : } else if (isSimilarity) {
628 : // For a similarity matrix with rotation, the gradient will not be aligned
629 : // with the texel coordinate axes, so we need to calculate it.
630 : #ifdef SK_VULKAN
631 : fragBuilder->codeAppendf("vec2 st_grad = dFdx(%s);", st.fsIn());
632 : fragBuilder->codeAppend("vec2 offset = delta*st_grad;");
633 : #else
634 : // We use dFdy because of a Mali 400 bug, and rotate -90 degrees to
635 : // get the gradient in the x direction.
636 0 : fragBuilder->codeAppendf("vec2 st_grad = dFdy(%s);", st.fsIn());
637 0 : fragBuilder->codeAppend("vec2 offset = delta*vec2(st_grad.y, -st_grad.x);");
638 : #endif
639 0 : fragBuilder->codeAppend("float st_grad_len = length(st_grad);");
640 : } else {
641 0 : fragBuilder->codeAppendf("vec2 st = %s;\n", st.fsIn());
642 :
643 0 : fragBuilder->codeAppend("vec2 Jdx = dFdx(st);");
644 0 : fragBuilder->codeAppend("vec2 Jdy = dFdy(st);");
645 0 : fragBuilder->codeAppend("vec2 offset = delta*Jdx;");
646 : }
647 :
648 : // green is distance to uv center
649 0 : fragBuilder->codeAppend("\tvec4 texColor = ");
650 0 : fragBuilder->appendTextureLookup(args.fTexSamplers[0], "uv", kVec2f_GrSLType);
651 0 : fragBuilder->codeAppend(";\n");
652 0 : fragBuilder->codeAppend("\tvec3 distance;\n");
653 0 : fragBuilder->codeAppend("\tdistance.y = texColor.r;\n");
654 : // red is distance to left offset
655 0 : fragBuilder->codeAppend("\tvec2 uv_adjusted = uv - offset;\n");
656 0 : fragBuilder->codeAppend("\ttexColor = ");
657 0 : fragBuilder->appendTextureLookup(args.fTexSamplers[0], "uv_adjusted", kVec2f_GrSLType);
658 0 : fragBuilder->codeAppend(";\n");
659 0 : fragBuilder->codeAppend("\tdistance.x = texColor.r;\n");
660 : // blue is distance to right offset
661 0 : fragBuilder->codeAppend("\tuv_adjusted = uv + offset;\n");
662 0 : fragBuilder->codeAppend("\ttexColor = ");
663 0 : fragBuilder->appendTextureLookup(args.fTexSamplers[0], "uv_adjusted", kVec2f_GrSLType);
664 0 : fragBuilder->codeAppend(";\n");
665 0 : fragBuilder->codeAppend("\tdistance.z = texColor.r;\n");
666 :
667 0 : fragBuilder->codeAppend("\tdistance = "
668 0 : "vec3(" SK_DistanceFieldMultiplier ")*(distance - vec3(" SK_DistanceFieldThreshold"));");
669 :
670 : // adjust width based on gamma
671 0 : const char* distanceAdjustUniName = nullptr;
672 : fDistanceAdjustUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
673 : kVec3f_GrSLType, kDefault_GrSLPrecision,
674 0 : "DistanceAdjust", &distanceAdjustUniName);
675 0 : fragBuilder->codeAppendf("distance -= %s;", distanceAdjustUniName);
676 :
677 : // To be strictly correct, we should compute the anti-aliasing factor separately
678 : // for each color component. However, this is only important when using perspective
679 : // transformations, and even then using a single factor seems like a reasonable
680 : // trade-off between quality and speed.
681 0 : fragBuilder->codeAppend("float afwidth;");
682 0 : if (isSimilarity) {
683 : // For similarity transform (uniform scale-only is a subset of this), we adjust for the
684 : // effect of the transformation on the distance by using the length of the gradient of
685 : // the texture coordinates. We use st coordinates to ensure we're mapping 1:1 from texel
686 : // space to pixel space.
687 :
688 : // this gives us a smooth step across approximately one fragment
689 0 : fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*st_grad_len;");
690 : } else {
691 : // For general transforms, to determine the amount of correction we multiply a unit
692 : // vector pointing along the SDF gradient direction by the Jacobian of the st coords
693 : // (which is the inverse transform for this fragment) and take the length of the result.
694 0 : fragBuilder->codeAppend("vec2 dist_grad = vec2(dFdx(distance.r), dFdy(distance.r));");
695 : // the length of the gradient may be 0, so we need to check for this
696 : // this also compensates for the Adreno, which likes to drop tiles on division by 0
697 0 : fragBuilder->codeAppend("float dg_len2 = dot(dist_grad, dist_grad);");
698 0 : fragBuilder->codeAppend("if (dg_len2 < 0.0001) {");
699 0 : fragBuilder->codeAppend("dist_grad = vec2(0.7071, 0.7071);");
700 0 : fragBuilder->codeAppend("} else {");
701 0 : fragBuilder->codeAppend("dist_grad = dist_grad*inversesqrt(dg_len2);");
702 0 : fragBuilder->codeAppend("}");
703 0 : fragBuilder->codeAppend("vec2 grad = vec2(dist_grad.x*Jdx.x + dist_grad.y*Jdy.x,");
704 0 : fragBuilder->codeAppend(" dist_grad.x*Jdx.y + dist_grad.y*Jdy.y);");
705 :
706 : // this gives us a smooth step across approximately one fragment
707 0 : fragBuilder->codeAppend("afwidth = " SK_DistanceFieldAAFactor "*length(grad);");
708 : }
709 :
710 : // The smoothstep falloff compensates for the non-linear sRGB response curve. If we are
711 : // doing gamma-correct rendering (to an sRGB or F16 buffer), then we actually want distance
712 : // mapped linearly to coverage, so use a linear step:
713 0 : if (isGammaCorrect) {
714 0 : fragBuilder->codeAppend("vec4 val = "
715 0 : "vec4(clamp(distance + vec3(afwidth) / vec3(2.0 * afwidth), 0.0, 1.0), 1.0);");
716 : } else {
717 0 : fragBuilder->codeAppend(
718 0 : "vec4 val = vec4(smoothstep(vec3(-afwidth), vec3(afwidth), distance), 1.0);");
719 : }
720 :
721 : // set alpha to be max of rgb coverage
722 0 : fragBuilder->codeAppend("val.a = max(max(val.r, val.g), val.b);");
723 :
724 0 : fragBuilder->codeAppendf("%s = val;", args.fOutputCoverage);
725 0 : }
726 :
727 0 : void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& processor,
728 : FPCoordTransformIter&& transformIter) override {
729 0 : SkASSERT(fDistanceAdjustUni.isValid());
730 :
731 0 : const GrDistanceFieldLCDTextGeoProc& dflcd = processor.cast<GrDistanceFieldLCDTextGeoProc>();
732 0 : GrDistanceFieldLCDTextGeoProc::DistanceAdjust wa = dflcd.getDistanceAdjust();
733 0 : if (wa != fDistanceAdjust) {
734 0 : pdman.set3f(fDistanceAdjustUni,
735 : wa.fR,
736 : wa.fG,
737 0 : wa.fB);
738 0 : fDistanceAdjust = wa;
739 : }
740 :
741 0 : if (!dflcd.viewMatrix().isIdentity() && !fViewMatrix.cheapEqualTo(dflcd.viewMatrix())) {
742 0 : fViewMatrix = dflcd.viewMatrix();
743 : float viewMatrix[3 * 3];
744 0 : GrGLSLGetMatrix<3>(viewMatrix, fViewMatrix);
745 0 : pdman.setMatrix3f(fViewMatrixUniform, viewMatrix);
746 : }
747 0 : this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
748 0 : }
749 :
750 0 : static inline void GenKey(const GrGeometryProcessor& gp,
751 : const GrShaderCaps&,
752 : GrProcessorKeyBuilder* b) {
753 0 : const GrDistanceFieldLCDTextGeoProc& dfTexEffect = gp.cast<GrDistanceFieldLCDTextGeoProc>();
754 :
755 0 : uint32_t key = dfTexEffect.getFlags();
756 0 : key |= ComputePosKey(dfTexEffect.viewMatrix()) << 16;
757 0 : b->add32(key);
758 :
759 : // Currently we hardcode numbers to convert atlas coordinates to normalized floating point
760 0 : SkASSERT(gp.numTextureSamplers() == 1);
761 0 : GrTexture* atlas = gp.textureSampler(0).texture();
762 0 : SkASSERT(atlas);
763 0 : b->add32(atlas->width());
764 0 : b->add32(atlas->height());
765 0 : }
766 :
767 : private:
768 : SkMatrix fViewMatrix;
769 : UniformHandle fViewMatrixUniform;
770 : UniformHandle fColorUniform;
771 : GrDistanceFieldLCDTextGeoProc::DistanceAdjust fDistanceAdjust;
772 : UniformHandle fDistanceAdjustUni;
773 :
774 : typedef GrGLSLGeometryProcessor INHERITED;
775 : };
776 :
777 : ///////////////////////////////////////////////////////////////////////////////
778 0 : GrDistanceFieldLCDTextGeoProc::GrDistanceFieldLCDTextGeoProc(
779 : GrResourceProvider* resourceProvider,
780 : GrColor color, const SkMatrix& viewMatrix,
781 : sk_sp<GrTextureProxy> proxy,
782 : const GrSamplerParams& params,
783 : DistanceAdjust distanceAdjust,
784 0 : uint32_t flags, bool usesLocalCoords)
785 : : fColor(color)
786 : , fViewMatrix(viewMatrix)
787 0 : , fTextureSampler(resourceProvider, std::move(proxy), params)
788 : , fDistanceAdjust(distanceAdjust)
789 0 : , fFlags(flags & kLCD_DistanceFieldEffectMask)
790 0 : , fUsesLocalCoords(usesLocalCoords) {
791 0 : SkASSERT(!(flags & ~kLCD_DistanceFieldEffectMask) && (flags & kUseLCD_DistanceFieldEffectFlag));
792 0 : this->initClassID<GrDistanceFieldLCDTextGeoProc>();
793 0 : fInPosition = &this->addVertexAttrib("inPosition", kVec2f_GrVertexAttribType,
794 0 : kHigh_GrSLPrecision);
795 0 : fInColor = &this->addVertexAttrib("inColor", kVec4ub_GrVertexAttribType);
796 0 : fInTextureCoords = &this->addVertexAttrib("inTextureCoords", kVec2us_GrVertexAttribType,
797 0 : kHigh_GrSLPrecision);
798 0 : this->addTextureSampler(&fTextureSampler);
799 0 : }
800 :
801 0 : void GrDistanceFieldLCDTextGeoProc::getGLSLProcessorKey(const GrShaderCaps& caps,
802 : GrProcessorKeyBuilder* b) const {
803 0 : GrGLDistanceFieldLCDTextGeoProc::GenKey(*this, caps, b);
804 0 : }
805 :
806 0 : GrGLSLPrimitiveProcessor* GrDistanceFieldLCDTextGeoProc::createGLSLInstance(const GrShaderCaps&) const {
807 0 : return new GrGLDistanceFieldLCDTextGeoProc();
808 : }
809 :
810 : ///////////////////////////////////////////////////////////////////////////////
811 :
812 : GR_DEFINE_GEOMETRY_PROCESSOR_TEST(GrDistanceFieldLCDTextGeoProc);
813 :
814 : #if GR_TEST_UTILS
815 0 : sk_sp<GrGeometryProcessor> GrDistanceFieldLCDTextGeoProc::TestCreate(GrProcessorTestData* d) {
816 0 : int texIdx = d->fRandom->nextBool() ? GrProcessorUnitTest::kSkiaPMTextureIdx :
817 0 : GrProcessorUnitTest::kAlphaTextureIdx;
818 0 : sk_sp<GrTextureProxy> proxy = d->textureProxy(texIdx);
819 :
820 : static const SkShader::TileMode kTileModes[] = {
821 : SkShader::kClamp_TileMode,
822 : SkShader::kRepeat_TileMode,
823 : SkShader::kMirror_TileMode,
824 : };
825 : SkShader::TileMode tileModes[] = {
826 0 : kTileModes[d->fRandom->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
827 0 : kTileModes[d->fRandom->nextULessThan(SK_ARRAY_COUNT(kTileModes))],
828 0 : };
829 0 : GrSamplerParams params(tileModes, d->fRandom->nextBool() ? GrSamplerParams::kBilerp_FilterMode
830 0 : : GrSamplerParams::kNone_FilterMode);
831 0 : DistanceAdjust wa = { 0.0f, 0.1f, -0.1f };
832 0 : uint32_t flags = kUseLCD_DistanceFieldEffectFlag;
833 0 : flags |= d->fRandom->nextBool() ? kSimilarity_DistanceFieldEffectFlag : 0;
834 0 : if (flags & kSimilarity_DistanceFieldEffectFlag) {
835 0 : flags |= d->fRandom->nextBool() ? kScaleOnly_DistanceFieldEffectFlag : 0;
836 : }
837 0 : flags |= d->fRandom->nextBool() ? kBGR_DistanceFieldEffectFlag : 0;
838 : return GrDistanceFieldLCDTextGeoProc::Make(d->resourceProvider(),
839 : GrRandomColor(d->fRandom),
840 : GrTest::TestMatrix(d->fRandom),
841 0 : std::move(proxy), params,
842 : wa,
843 : flags,
844 0 : d->fRandom->nextBool());
845 : }
846 : #endif
|