OLD | NEW |
---|---|
1 | 1 |
2 /* | 2 /* |
3 * Copyright 2006 The Android Open Source Project | 3 * Copyright 2006 The Android Open Source Project |
4 * | 4 * |
5 * Use of this source code is governed by a BSD-style license that can be | 5 * Use of this source code is governed by a BSD-style license that can be |
6 * found in the LICENSE file. | 6 * found in the LICENSE file. |
7 */ | 7 */ |
8 | 8 |
9 | 9 |
10 #include "SkXfermode.h" | 10 #include "SkXfermode.h" |
(...skipping 937 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
948 #include "GrEffectUnitTest.h" | 948 #include "GrEffectUnitTest.h" |
949 #include "GrTBackendEffectFactory.h" | 949 #include "GrTBackendEffectFactory.h" |
950 #include "gl/GrGLEffect.h" | 950 #include "gl/GrGLEffect.h" |
951 | 951 |
952 /** | 952 /** |
953 * GrEffect that implements the all the separable xfer modes that cannot be expr essed as Coeffs. | 953 * GrEffect that implements the all the separable xfer modes that cannot be expr essed as Coeffs. |
954 */ | 954 */ |
955 class XferEffect : public GrEffect { | 955 class XferEffect : public GrEffect { |
956 public: | 956 public: |
957 static bool IsSupportedMode(SkXfermode::Mode mode) { | 957 static bool IsSupportedMode(SkXfermode::Mode mode) { |
958 return mode > SkXfermode::kLastCoeffMode && mode <= SkXfermode::kLastSep arableMode; | 958 return mode > SkXfermode::kLastCoeffMode && mode <= SkXfermode::kLastMod e; |
959 } | 959 } |
960 | 960 |
961 static GrEffectRef* Create(SkXfermode::Mode mode) { | 961 static GrEffectRef* Create(SkXfermode::Mode mode) { |
962 if (!IsSupportedMode(mode)) { | 962 if (!IsSupportedMode(mode)) { |
963 return NULL; | 963 return NULL; |
964 } else { | 964 } else { |
965 AutoEffectUnref effect(SkNEW_ARGS(XferEffect, (mode))); | 965 AutoEffectUnref effect(SkNEW_ARGS(XferEffect, (mode))); |
966 return CreateEffectRef(effect); | 966 return CreateEffectRef(effect); |
967 } | 967 } |
968 } | 968 } |
(...skipping 24 matching lines...) Expand all Loading... | |
993 const TextureSamplerArray& samplers) SK_OVERRIDE { | 993 const TextureSamplerArray& samplers) SK_OVERRIDE { |
994 const char* dstColor = builder->dstColor(); | 994 const char* dstColor = builder->dstColor(); |
995 GrAssert(NULL != dstColor); | 995 GrAssert(NULL != dstColor); |
996 | 996 |
997 // We don't try to optimize for this case at all | 997 // We don't try to optimize for this case at all |
998 if (NULL == inputColor) { | 998 if (NULL == inputColor) { |
999 builder->fsCodeAppendf("\tconst vec4 ones = %s;\n", GrGLSLOnesVe cf(4)); | 999 builder->fsCodeAppendf("\tconst vec4 ones = %s;\n", GrGLSLOnesVe cf(4)); |
1000 inputColor = "ones"; | 1000 inputColor = "ones"; |
1001 } | 1001 } |
1002 | 1002 |
1003 SkXfermode::Mode mode = drawEffect.castEffect<XferEffect>().mode(); | |
1004 builder->fsCodeAppendf("\t\t// SkXfermode::Mode: %s\n", SkXfermode:: ModeName(mode)); | |
1005 | |
1003 // These all perform src-over on the alpha channel. | 1006 // These all perform src-over on the alpha channel. |
1004 builder->fsCodeAppendf("\t\t%s.a = %s.a + (1.0 - %s.a) * %s.a;\n", | 1007 builder->fsCodeAppendf("\t\t%s.a = %s.a + (1.0 - %s.a) * %s.a;\n", |
1005 outputColor, inputColor, inputColor, dstColo r); | 1008 outputColor, inputColor, inputColor, dstColo r); |
1006 | 1009 |
1007 switch (drawEffect.castEffect<XferEffect>().mode()) { | 1010 switch (mode) { |
1008 case SkXfermode::kOverlay_Mode: | 1011 case SkXfermode::kOverlay_Mode: |
1009 // Overlay is Hard-Light with the src and dst reversed | 1012 // Overlay is Hard-Light with the src and dst reversed |
1010 HardLight(builder, outputColor, dstColor, inputColor); | 1013 HardLight(builder, outputColor, dstColor, inputColor); |
1011 break; | 1014 break; |
1012 case SkXfermode::kDarken_Mode: | 1015 case SkXfermode::kDarken_Mode: |
1013 builder->fsCodeAppendf("\t\t%s.rgb = min((1.0 - %s.a) * %s.r gb + %s.rgb, " | 1016 builder->fsCodeAppendf("\t\t%s.rgb = min((1.0 - %s.a) * %s.r gb + %s.rgb, " |
1014 "(1.0 - %s.a) * %s.r gb + %s.rgb);\n", | 1017 "(1.0 - %s.a) * %s.r gb + %s.rgb);\n", |
1015 outputColor, | 1018 outputColor, |
1016 inputColor, dstColor, inputColor, | 1019 inputColor, dstColor, inputColor, |
1017 dstColor, inputColor, dstColor); | 1020 dstColor, inputColor, dstColor); |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1056 "2.0 * %s.rgb * %s.rgb;\ n", | 1059 "2.0 * %s.rgb * %s.rgb;\ n", |
1057 outputColor, dstColor, inputColor, ds tColor, inputColor); | 1060 outputColor, dstColor, inputColor, ds tColor, inputColor); |
1058 break; | 1061 break; |
1059 case SkXfermode::kMultiply_Mode: | 1062 case SkXfermode::kMultiply_Mode: |
1060 builder->fsCodeAppendf("\t\t%s.rgb = (1.0 - %s.a) * %s.rgb + " | 1063 builder->fsCodeAppendf("\t\t%s.rgb = (1.0 - %s.a) * %s.rgb + " |
1061 "(1.0 - %s.a) * %s.rgb + " | 1064 "(1.0 - %s.a) * %s.rgb + " |
1062 "%s.rgb * %s.rgb;\n", | 1065 "%s.rgb * %s.rgb;\n", |
1063 outputColor, inputColor, dstColor, ds tColor, inputColor, | 1066 outputColor, inputColor, dstColor, ds tColor, inputColor, |
1064 inputColor, dstColor); | 1067 inputColor, dstColor); |
1065 break; | 1068 break; |
1066 case SkXfermode::kHue_Mode: | 1069 case SkXfermode::kHue_Mode: { |
1067 case SkXfermode::kSaturation_Mode: | 1070 // SetLum(SetSat(S * Da, Sat(D * Sa)), Sa*Da, D*Sa) + (1 - Sa) * D + (1 - Da) * S |
1068 case SkXfermode::kColor_Mode: | 1071 SkString setSat, setLum; |
1069 case SkXfermode::kLuminosity_Mode: | 1072 AddSatFunction(builder, &setSat); |
1070 GrCrash("Unimplemented XferEffect mode."); | 1073 AddLumFunction(builder, &setLum); |
robertphillips
2013/04/22 15:46:31
\n before dstColor?
bsalomon
2013/04/22 17:36:17
Done.
| |
1074 builder->fsCodeAppendf("\t\tvec4 dstSrcAlpha = %s * %s.a;\n" , dstColor, inputColor); | |
1075 builder->fsCodeAppendf("\t\t%s.rgb = %s(%s(%s.rgb * %s.a, ds tSrcAlpha.rgb), dstSrcAlpha.a, dstSrcAlpha.rgb);\n", | |
robertphillips
2013/04/22 15:46:31
\n before inputColor?
bsalomon
2013/04/22 17:36:17
Done.
| |
1076 outputColor, setLum.c_str(), setSat.c _str(), inputColor, dstColor); | |
1077 builder->fsCodeAppendf("\t\t%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;\n", | |
1078 outputColor, inputColor, dstColor, ds tColor, inputColor); | |
1071 break; | 1079 break; |
1080 } | |
1081 case SkXfermode::kSaturation_Mode: { | |
1082 // SetLum(SetSat(D * Sa, Sat(S * Da)), Sa*Da, D*Sa)) + (1 - Sa) * D + (1 - Da) * S | |
1083 SkString setSat, setLum; | |
1084 AddSatFunction(builder, &setSat); | |
1085 AddLumFunction(builder, &setLum); | |
robertphillips
2013/04/22 15:46:31
\n
bsalomon
2013/04/22 17:36:17
Done.
| |
1086 builder->fsCodeAppendf("\t\tvec4 dstSrcAlpha = %s * %s.a;\n" , dstColor, inputColor); | |
1087 builder->fsCodeAppendf("\t\t%s.rgb = %s(%s(dstSrcAlpha.rgb, %s.rgb * %s.a), dstSrcAlpha.a, dstSrcAlpha.rgb);\n", | |
robertphillips
2013/04/22 15:46:31
/n
bsalomon
2013/04/22 17:36:17
Done.
| |
1088 outputColor, setLum.c_str(), setSat.c _str(), inputColor, dstColor); | |
1089 builder->fsCodeAppendf("\t\t%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;\n", | |
1090 outputColor, inputColor, dstColor, ds tColor, inputColor); | |
1091 break; | |
1092 } | |
1093 case SkXfermode::kColor_Mode: { | |
1094 // SetLum(S * Da, Sa* Da, D * Sa) + (1 - Sa) * D + (1 - Da) * S | |
1095 SkString setLum; | |
1096 AddLumFunction(builder, &setLum); | |
robertphillips
2013/04/22 15:46:31
\n
bsalomon
2013/04/22 17:36:17
Done.
| |
1097 builder->fsCodeAppendf("\t\tvec4 srcDstAlpha = %s * %s.a;\n" , inputColor, dstColor); | |
1098 builder->fsCodeAppendf("\t\t%s.rgb = %s(srcDstAlpha.rgb, src DstAlpha.a, %s.rgb * %s.a);\n", | |
1099 outputColor, setLum.c_str(), dstColor , inputColor); | |
1100 builder->fsCodeAppendf("\t\t%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;\n", | |
1101 outputColor, inputColor, dstColor, ds tColor, inputColor); | |
1102 break; | |
1103 } | |
1104 case SkXfermode::kLuminosity_Mode: { | |
1105 // SetLum(D * Sa, Sa* Da, S * Da) + (1 - Sa) * D + (1 - Da) * S | |
1106 SkString setLum; | |
1107 AddLumFunction(builder, &setLum); | |
robertphillips
2013/04/22 15:46:31
\n
bsalomon
2013/04/22 17:36:17
Done.
| |
1108 builder->fsCodeAppendf("\t\tvec4 srcDstAlpha = %s * %s.a;\n" , inputColor, dstColor); | |
1109 builder->fsCodeAppendf("\t\t%s.rgb = %s(%s.rgb * %s.a, srcDs tAlpha.a, srcDstAlpha.rgb);\n", | |
1110 outputColor, setLum.c_str(), dstColor , inputColor); | |
1111 builder->fsCodeAppendf("\t\t%s.rgb += (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb;\n", | |
1112 outputColor, inputColor, dstColor, ds tColor, inputColor); | |
1113 break; | |
1114 } | |
1072 default: | 1115 default: |
1073 GrCrash("Unknown XferEffect mode."); | 1116 GrCrash("Unknown XferEffect mode."); |
1074 break; | 1117 break; |
1075 } | 1118 } |
1076 } | 1119 } |
1077 | 1120 |
1078 static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrG LCaps&) { | 1121 static inline EffectKey GenKey(const GrDrawEffect& drawEffect, const GrG LCaps&) { |
1079 return drawEffect.castEffect<XferEffect>().mode(); | 1122 return drawEffect.castEffect<XferEffect>().mode(); |
1080 } | 1123 } |
1081 | 1124 |
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1170 src, src, component, dst, src, src, component , src, src, | 1213 src, src, component, dst, src, src, component , src, src, |
1171 component); | 1214 component); |
1172 builder->fsCodeAppendf("\t\t\t} else {\n"); | 1215 builder->fsCodeAppendf("\t\t\t} else {\n"); |
1173 // -sqrt(Da * D) (Sa-2 S)-Da S+D (Sa-2 S+1)+S | 1216 // -sqrt(Da * D) (Sa-2 S)-Da S+D (Sa-2 S+1)+S |
1174 builder->fsCodeAppendf("\t\t\t\t%s.%c = -sqrt(%s.a*%s.%c)*(%s.a - 2. 0*%s.%c) - %s.a*%s.%c + %s.%c*(%s.a - 2.0*%s.%c + 1.0) + %s.%c;\n", | 1217 builder->fsCodeAppendf("\t\t\t\t%s.%c = -sqrt(%s.a*%s.%c)*(%s.a - 2. 0*%s.%c) - %s.a*%s.%c + %s.%c*(%s.a - 2.0*%s.%c + 1.0) + %s.%c;\n", |
1175 final, component, dst, dst, component, src, src, component, dst, | 1218 final, component, dst, dst, component, src, src, component, dst, |
1176 src, component, dst, component, src, src, co mponent, src, | 1219 src, component, dst, component, src, src, co mponent, src, |
1177 component); | 1220 component); |
1178 builder->fsCodeAppendf("\t\t\t}\n"); | 1221 builder->fsCodeAppendf("\t\t\t}\n"); |
1179 } | 1222 } |
1180 | 1223 |
robertphillips
2013/04/22 15:46:31
It also emits "luminance" method
bsalomon
2013/04/22 17:36:17
But it doesn't return the name of it, so it isn't
| |
1224 // Adds a function that takes two colors and an alpha as input. It produ ces a color with the | |
1225 // hue and saturation of the first color, the luminosity of the second c olor, and the input | |
1226 // alpha. It has this signature: | |
1227 // vec3 set_luminance(vec3 hueSatColor, float alpha, vec3 lumColor) . | |
1228 static void AddLumFunction(GrGLShaderBuilder* builder, SkString* setLumF unction) { | |
1229 SkString getFunction; | |
robertphillips
2013/04/22 15:46:31
Is the indent right after this {?
bsalomon
2013/04/22 17:36:17
I removed the blocks.
| |
1230 { | |
robertphillips
2013/04/22 15:46:31
one line?
bsalomon
2013/04/22 17:36:17
Done.
| |
1231 GrGLShaderVar args[] = { | |
1232 GrGLShaderVar("color", kVec3f_GrSLType), | |
1233 }; | |
1234 SkString body("\treturn dot(vec3(0.3, 0.59, 0.11), color);\n"); | |
1235 builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, | |
1236 kFloat_GrSLType, | |
1237 "luminance", | |
1238 SK_ARRAY_COUNT(args), args, | |
1239 body.c_str(), | |
1240 &getFunction); | |
1241 } | |
1242 | |
1243 { | |
1244 GrGLShaderVar args[] = { | |
1245 GrGLShaderVar("hueSat", kVec3f_GrSLType), | |
1246 GrGLShaderVar("alpha", kFloat_GrSLType), | |
1247 GrGLShaderVar("lumColor", kVec3f_GrSLType), | |
1248 }; | |
1249 SkString body; | |
1250 body.printf("\tfloat diff = %s(lumColor - hueSat);\n", getFunction.c _str()); | |
1251 body.append("\tvec3 outColor = hueSat + diff;\n"); | |
1252 body.appendf("\tfloat outLum = %s(outColor);\n", getFunction.c_str() ); | |
robertphillips
2013/04/22 15:46:31
Is this code happy with black?
bsalomon
2013/04/22 17:36:17
yup
| |
1253 body.append("\tfloat minComp = min(min(outColor.r, outColor.g), outC olor.b);\n" | |
1254 "\tfloat maxComp = max(max(outColor.r, outColor.g), outC olor.b);\n" | |
1255 "\tif (minComp < 0.0) {\n" | |
1256 "\t\toutColor = outLum + ((outColor - vec3(outLum, outLu m, outLum)) * outLum) / (outLum - minComp);\n" | |
1257 "\t}\n" | |
1258 "\tif (maxComp > alpha) {\n" | |
1259 "\t\toutColor = outLum + ((outColor - vec3(outLum, outLu m, outLum)) * (alpha - outLum)) / (maxComp - outLum);\n" | |
1260 "\t}\n" | |
1261 "\treturn outColor;\n"); | |
1262 builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, | |
1263 kVec3f_GrSLType, | |
1264 "set_luminance", | |
1265 SK_ARRAY_COUNT(args), args, | |
1266 body.c_str(), | |
1267 setLumFunction); | |
1268 } | |
1269 } | |
1270 | |
robertphillips
2013/04/22 15:46:31
functions that create?
bsalomon
2013/04/22 17:36:17
Changed to "Adds a function that creates"
| |
1271 // Adds functions that creates a color with the hue and luminosity of on e input color and | |
1272 // the saturation of another color. It will have this signature: | |
1273 // float set_saturation(vec3 hueLumColor, vec3 satColor) | |
1274 static void AddSatFunction(GrGLShaderBuilder* builder, SkString* setSatF unction) { | |
1275 SkString getFunction; | |
robertphillips
2013/04/22 15:46:31
// emit 'saturation' function?
bsalomon
2013/04/22 17:36:17
Done.
| |
1276 { | |
1277 GrGLShaderVar args[] = { | |
1278 GrGLShaderVar("color", kVec3f_GrSLType), | |
1279 }; | |
1280 SkString body; | |
1281 body.appendf("\treturn max(max(color.r, color.g), color.b) - " | |
1282 "min(min(color.r, color.g), color.b);\n"); | |
1283 builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, | |
1284 kFloat_GrSLType, | |
1285 "saturation", | |
1286 SK_ARRAY_COUNT(args), args, | |
1287 body.c_str(), | |
1288 &getFunction); | |
1289 } | |
1290 | |
1291 SkString helper; | |
robertphillips
2013/04/22 15:46:31
emit "set_saturation_helper" method?
bsalomon
2013/04/22 17:36:17
Done.
| |
1292 { | |
1293 GrGLShaderVar args[] = { | |
1294 GrGLShaderVar("minComp", kFloat_GrSLType), | |
1295 GrGLShaderVar("midComp", kFloat_GrSLType), | |
1296 GrGLShaderVar("maxComp", kFloat_GrSLType), | |
1297 GrGLShaderVar("sat", kFloat_GrSLType), | |
1298 }; | |
1299 args[0].setTypeModifier(GrGLShaderVar::kInOut_TypeModifier); | |
1300 args[1].setTypeModifier(GrGLShaderVar::kInOut_TypeModifier); | |
1301 args[2].setTypeModifier(GrGLShaderVar::kInOut_TypeModifier); | |
1302 SkString body; | |
1303 body.append("\tif (minComp < maxComp) {\n" | |
1304 "\t\tmidComp = sat * (midComp - minComp) / (maxComp - mi nComp);\n" | |
1305 "\t\tmaxComp = sat;\n" | |
1306 "\t} else {\n" | |
1307 "\t\tmidComp = midComp = 0.0;\n" | |
1308 "\t}\n" | |
1309 "\tminComp = 0.0;\n"); | |
1310 builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, | |
1311 kVoid_GrSLType, | |
1312 "set_saturation_helper", | |
1313 SK_ARRAY_COUNT(args), args, | |
1314 body.c_str(), | |
1315 &helper); | |
1316 } | |
1317 { | |
1318 GrGLShaderVar args[] = { | |
1319 GrGLShaderVar("hueLumColor", kVec3f_GrSLType), | |
1320 GrGLShaderVar("satColor", kVec3f_GrSLType), | |
1321 }; | |
1322 SkString body; | |
1323 const char* helpFunc = helper.c_str(); | |
1324 body.appendf("\tfloat sat = %s(satColor);\n" | |
1325 "\tif (hueLumColor.r <= hueLumColor.g) {\n" | |
1326 "\t\tif (hueLumColor.g <= hueLumColor.b) {\n" | |
1327 "\t\t\t%s(hueLumColor.r, hueLumColor.g, hueLumColor.b, sat);\n" | |
1328 "\t\t} else {\n" | |
1329 "\t\t\t%s(hueLumColor.r, hueLumColor.b, hueLumColor.g, sat);\n" | |
1330 "\t\t}\n" | |
1331 "\t} else if (hueLumColor.r <= hueLumColor.b) {\n" | |
1332 "\t\t%s(hueLumColor.g, hueLumColor.r, hueLumColor.b, sa t);\n" | |
1333 "\t} else if (hueLumColor.g <= hueLumColor.b) {\n" | |
1334 "\t\t%s(hueLumColor.g, hueLumColor.b, hueLumColor.r, sa t);\n" | |
1335 "\t} else {\n" | |
1336 "\t\t%s(hueLumColor.b, hueLumColor.g, hueLumColor.r, sa t);\n" | |
1337 "\t}\n" | |
1338 "\treturn hueLumColor;", | |
1339 getFunction.c_str(), helpFunc, helpFunc, helpFunc, help Func, helpFunc); | |
1340 builder->emitFunction(GrGLShaderBuilder::kFragment_ShaderType, | |
1341 kVec3f_GrSLType, | |
1342 "set_saturation", | |
1343 SK_ARRAY_COUNT(args), args, | |
1344 body.c_str(), | |
1345 setSatFunction); | |
1346 } | |
1347 | |
1348 } | |
1349 | |
1181 typedef GrGLEffect INHERITED; | 1350 typedef GrGLEffect INHERITED; |
1182 }; | 1351 }; |
1183 | 1352 |
1184 GR_DECLARE_EFFECT_TEST; | 1353 GR_DECLARE_EFFECT_TEST; |
1185 | 1354 |
1186 private: | 1355 private: |
1187 XferEffect(SkXfermode::Mode mode) : fMode(mode) { this->setWillReadDst(); } | 1356 XferEffect(SkXfermode::Mode mode) : fMode(mode) { this->setWillReadDst(); } |
1188 virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE { return tru e; } | 1357 virtual bool onIsEqual(const GrEffect& other) const SK_OVERRIDE { return tru e; } |
1189 | 1358 |
1190 SkXfermode::Mode fMode; | 1359 SkXfermode::Mode fMode; |
(...skipping 604 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1795 return proc16; | 1964 return proc16; |
1796 } | 1965 } |
1797 | 1966 |
1798 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkXfermode) | 1967 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkXfermode) |
1799 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkProcCoeffXfermode) | 1968 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkProcCoeffXfermode) |
1800 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkClearXfermode) | 1969 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkClearXfermode) |
1801 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSrcXfermode) | 1970 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkSrcXfermode) |
1802 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDstInXfermode) | 1971 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDstInXfermode) |
1803 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDstOutXfermode) | 1972 SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkDstOutXfermode) |
1804 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END | 1973 SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END |
OLD | NEW |