Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /* | 1 /* |
| 2 * Copyright 2011 Google Inc. | 2 * Copyright 2011 Google Inc. |
| 3 * | 3 * |
| 4 * Use of this source code is governed by a BSD-style license that can be | 4 * Use of this source code is governed by a BSD-style license that can be |
| 5 * found in the LICENSE file. | 5 * found in the LICENSE file. |
| 6 */ | 6 */ |
| 7 | 7 |
| 8 #include "GrGLProgram.h" | 8 #include "GrGLProgram.h" |
| 9 | 9 |
| 10 #include "GrAllocator.h" | 10 #include "GrAllocator.h" |
| (...skipping 126 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 137 case SkXfermode::kZero_Coeff: /** 0 */ | 137 case SkXfermode::kZero_Coeff: /** 0 */ |
| 138 *str = ""; | 138 *str = ""; |
| 139 break; | 139 break; |
| 140 case SkXfermode::kOne_Coeff: /** 1 */ | 140 case SkXfermode::kOne_Coeff: /** 1 */ |
| 141 *str = value; | 141 *str = value; |
| 142 break; | 142 break; |
| 143 case SkXfermode::kSC_Coeff: | 143 case SkXfermode::kSC_Coeff: |
| 144 str->printf("(%s * %s)", src, value); | 144 str->printf("(%s * %s)", src, value); |
| 145 break; | 145 break; |
| 146 case SkXfermode::kISC_Coeff: | 146 case SkXfermode::kISC_Coeff: |
| 147 str->printf("((%s - %s) * %s)", GrGLSLOnesVecf(4), src, value); | 147 str->printf("((%s - %s) * %s)", GrGLSLExpr<4>(1).c_str(), src, value); |
| 148 break; | 148 break; |
| 149 case SkXfermode::kDC_Coeff: | 149 case SkXfermode::kDC_Coeff: |
| 150 str->printf("(%s * %s)", dst, value); | 150 str->printf("(%s * %s)", dst, value); |
| 151 break; | 151 break; |
| 152 case SkXfermode::kIDC_Coeff: | 152 case SkXfermode::kIDC_Coeff: |
| 153 str->printf("((%s - %s) * %s)", GrGLSLOnesVecf(4), dst, value); | 153 str->printf("((%s - %s) * %s)", GrGLSLExpr<4>(1).c_str(), dst, value); |
| 154 break; | 154 break; |
| 155 case SkXfermode::kSA_Coeff: /** src alpha */ | 155 case SkXfermode::kSA_Coeff: /** src alpha */ |
| 156 str->printf("(%s.a * %s)", src, value); | 156 str->printf("(%s.a * %s)", src, value); |
| 157 break; | 157 break; |
| 158 case SkXfermode::kISA_Coeff: /** inverse src alpha (i.e. 1 - sa) */ | 158 case SkXfermode::kISA_Coeff: /** inverse src alpha (i.e. 1 - sa) */ |
| 159 str->printf("((1.0 - %s.a) * %s)", src, value); | 159 str->printf("((1.0 - %s.a) * %s)", src, value); |
| 160 break; | 160 break; |
| 161 case SkXfermode::kDA_Coeff: /** dst alpha */ | 161 case SkXfermode::kDA_Coeff: /** dst alpha */ |
| 162 str->printf("(%s.a * %s)", dst, value); | 162 str->printf("(%s.a * %s)", dst, value); |
| 163 break; | 163 break; |
| (...skipping 11 matching lines...) Expand all Loading... | |
| 175 */ | 175 */ |
| 176 void add_color_filter(GrGLShaderBuilder* builder, | 176 void add_color_filter(GrGLShaderBuilder* builder, |
| 177 const char * outputVar, | 177 const char * outputVar, |
| 178 SkXfermode::Coeff uniformCoeff, | 178 SkXfermode::Coeff uniformCoeff, |
| 179 SkXfermode::Coeff colorCoeff, | 179 SkXfermode::Coeff colorCoeff, |
| 180 const char* filterColor, | 180 const char* filterColor, |
| 181 const char* inColor) { | 181 const char* inColor) { |
| 182 SkString colorStr, constStr; | 182 SkString colorStr, constStr; |
| 183 blend_term_string(&colorStr, colorCoeff, filterColor, inColor, inColor); | 183 blend_term_string(&colorStr, colorCoeff, filterColor, inColor, inColor); |
| 184 blend_term_string(&constStr, uniformCoeff, filterColor, inColor, filterColor ); | 184 blend_term_string(&constStr, uniformCoeff, filterColor, inColor, filterColor ); |
| 185 | 185 GrGLSLExpr<4> sum; |
| 186 SkString sum; | 186 if (colorStr.isEmpty() && constStr.isEmpty()) { |
| 187 GrGLSLAddf<4>(&sum, colorStr.c_str(), constStr.c_str()); | 187 sum = GrGLSLExpr<4>(0); |
| 188 } else if (colorStr.isEmpty()) { | |
| 189 sum = constStr; | |
| 190 } else if (constStr.isEmpty()) { | |
| 191 sum = colorStr; | |
| 192 } else { | |
| 193 sum = GrGLSLExpr<4>(colorStr) + GrGLSLExpr<4>(constStr); | |
| 194 } | |
| 188 builder->fsCodeAppendf("\t%s = %s;\n", outputVar, sum.c_str()); | 195 builder->fsCodeAppendf("\t%s = %s;\n", outputVar, sum.c_str()); |
| 189 } | 196 } |
| 190 } | 197 } |
| 191 | 198 |
| 192 namespace { | |
| 193 | |
| 194 void expand_known_value4f(SkString* string, GrSLConstantVec vec) { | |
| 195 SkASSERT(string->isEmpty() == (vec != kNone_GrSLConstantVec)); | |
| 196 switch (vec) { | |
| 197 case kNone_GrSLConstantVec: | |
| 198 break; | |
| 199 case kZeros_GrSLConstantVec: | |
| 200 *string = GrGLSLZerosVecf(4); | |
| 201 break; | |
| 202 case kOnes_GrSLConstantVec: | |
| 203 *string = GrGLSLOnesVecf(4); | |
| 204 break; | |
| 205 } | |
| 206 } | |
| 207 | |
| 208 } | |
| 209 | |
| 210 bool GrGLProgram::genProgram(const GrEffectStage* colorStages[], | 199 bool GrGLProgram::genProgram(const GrEffectStage* colorStages[], |
| 211 const GrEffectStage* coverageStages[]) { | 200 const GrEffectStage* coverageStages[]) { |
| 212 SkASSERT(0 == fProgramID); | 201 SkASSERT(0 == fProgramID); |
| 213 | 202 |
| 214 const GrGLProgramDesc::KeyHeader& header = fDesc.getHeader(); | 203 const GrGLProgramDesc::KeyHeader& header = fDesc.getHeader(); |
| 215 | 204 |
| 216 bool needsVertexShader = true; | 205 bool needsVertexShader = true; |
| 217 | 206 |
| 218 GrGLShaderBuilder builder(fGpu, fUniformManager, fDesc, needsVertexShader); | 207 GrGLShaderBuilder builder(fGpu, fUniformManager, fDesc, needsVertexShader); |
| 219 if (GrGLShaderBuilder::VertexBuilder* vertexBuilder = builder.getVertexBuild er()) { | 208 if (GrGLShaderBuilder::VertexBuilder* vertexBuilder = builder.getVertexBuild er()) { |
| 220 fUniformHandles.fViewMatrixUni = vertexBuilder->getViewMatrixUniform(); | 209 fUniformHandles.fViewMatrixUni = vertexBuilder->getViewMatrixUniform(); |
| 221 } | 210 } |
| 222 | 211 |
| 223 // incoming color to current stage being processed. | 212 // incoming color to current stage being processed. |
| 224 SkString inColor = builder.getInputColor(); | 213 GrGLSLExpr<4> inColor = builder.getInputColor(); |
| 225 GrSLConstantVec knownColorValue = builder.getKnownColorValue(); | |
| 226 | 214 |
| 227 // Get the coeffs for the Mode-based color filter, determine if color is nee ded. | 215 // Get the coeffs for the Mode-based color filter, determine if color is nee ded. |
| 228 SkXfermode::Coeff colorCoeff; | 216 SkXfermode::Coeff colorCoeff; |
| 229 SkXfermode::Coeff filterColorCoeff; | 217 SkXfermode::Coeff filterColorCoeff; |
| 230 SkAssertResult( | 218 SkAssertResult( |
| 231 SkXfermode::ModeAsCoeff(header.fColorFilterXfermode, | 219 SkXfermode::ModeAsCoeff(header.fColorFilterXfermode, |
| 232 &filterColorCoeff, | 220 &filterColorCoeff, |
| 233 &colorCoeff)); | 221 &colorCoeff)); |
| 234 bool needColor, needFilterColor; | 222 bool needColor, needFilterColor; |
| 235 need_blend_inputs(filterColorCoeff, colorCoeff, &needFilterColor, &needColor ); | 223 need_blend_inputs(filterColorCoeff, colorCoeff, &needFilterColor, &needColor ); |
| 236 | 224 |
| 237 // used in order for builder to return the per-stage uniform handles. | 225 // used in order for builder to return the per-stage uniform handles. |
| 238 typedef SkTArray<GrGLUniformManager::UniformHandle, true>* UniHandleArrayPtr ; | 226 typedef SkTArray<GrGLUniformManager::UniformHandle, true>* UniHandleArrayPtr ; |
| 239 int maxColorOrCovEffectCnt = GrMax(fDesc.numColorEffects(), fDesc.numCoverag eEffects()); | 227 int maxColorOrCovEffectCnt = GrMax(fDesc.numColorEffects(), fDesc.numCoverag eEffects()); |
| 240 SkAutoTArray<UniHandleArrayPtr> effectUniformArrays(maxColorOrCovEffectCnt); | 228 SkAutoTArray<UniHandleArrayPtr> effectUniformArrays(maxColorOrCovEffectCnt); |
| 241 SkAutoTArray<GrGLEffect*> glEffects(maxColorOrCovEffectCnt); | 229 SkAutoTArray<GrGLEffect*> glEffects(maxColorOrCovEffectCnt); |
| 242 | 230 |
| 243 if (needColor) { | 231 if (needColor) { |
| 244 for (int e = 0; e < fDesc.numColorEffects(); ++e) { | 232 for (int e = 0; e < fDesc.numColorEffects(); ++e) { |
| 245 effectUniformArrays[e] = &fColorEffects[e].fSamplerUnis; | 233 effectUniformArrays[e] = &fColorEffects[e].fSamplerUnis; |
| 246 } | 234 } |
| 247 | 235 |
| 248 builder.emitEffects(colorStages, | 236 builder.emitEffects(colorStages, |
| 249 fDesc.effectKeys(), | 237 fDesc.effectKeys(), |
| 250 fDesc.numColorEffects(), | 238 fDesc.numColorEffects(), |
| 251 &inColor, | 239 &inColor, |
| 252 &knownColorValue, | |
| 253 effectUniformArrays.get(), | 240 effectUniformArrays.get(), |
| 254 glEffects.get()); | 241 glEffects.get()); |
| 255 | 242 |
| 256 for (int e = 0; e < fDesc.numColorEffects(); ++e) { | 243 for (int e = 0; e < fDesc.numColorEffects(); ++e) { |
| 257 fColorEffects[e].fGLEffect = glEffects[e]; | 244 fColorEffects[e].fGLEffect = glEffects[e]; |
| 258 } | 245 } |
| 259 } | 246 } |
| 260 | 247 |
| 261 // Insert the color filter. This will soon be replaced by a color effect. | 248 // Insert the color filter. This will soon be replaced by a color effect. |
| 262 if (SkXfermode::kDst_Mode != header.fColorFilterXfermode) { | 249 if (SkXfermode::kDst_Mode != header.fColorFilterXfermode) { |
| 263 const char* colorFilterColorUniName = NULL; | 250 const char* colorFilterColorUniName = NULL; |
| 264 fUniformHandles.fColorFilterUni = builder.addUniform(GrGLShaderBuilder:: kFragment_Visibility, | 251 fUniformHandles.fColorFilterUni = builder.addUniform(GrGLShaderBuilder:: kFragment_Visibility, |
| 265 kVec4f_GrSLType, "F ilterColor", | 252 kVec4f_GrSLType, "F ilterColor", |
| 266 &colorFilterColorUn iName); | 253 &colorFilterColorUn iName); |
| 267 | 254 |
| 268 builder.fsCodeAppend("\tvec4 filteredColor;\n"); | 255 builder.fsCodeAppend("\tvec4 filteredColor;\n"); |
| 269 const char* color; | |
| 270 // add_color_filter requires a real input string. | |
| 271 if (knownColorValue == kOnes_GrSLConstantVec) { | |
| 272 color = GrGLSLOnesVecf(4); | |
| 273 } else if (knownColorValue == kZeros_GrSLConstantVec) { | |
| 274 color = GrGLSLZerosVecf(4); | |
| 275 } else { | |
| 276 color = inColor.c_str(); | |
| 277 } | |
| 278 add_color_filter(&builder, "filteredColor", filterColorCoeff, | 256 add_color_filter(&builder, "filteredColor", filterColorCoeff, |
| 279 colorCoeff, colorFilterColorUniName, color); | 257 colorCoeff, colorFilterColorUniName, inColor.c_str()); |
| 280 inColor = "filteredColor"; | 258 inColor = "filteredColor"; |
| 281 } | 259 } |
| 282 | 260 |
| 283 /////////////////////////////////////////////////////////////////////////// | 261 /////////////////////////////////////////////////////////////////////////// |
| 284 // compute the partial coverage | 262 // compute the partial coverage |
| 285 SkString inCoverage = builder.getInputCoverage(); | 263 GrGLSLExpr<4> inCoverage = builder.getInputCoverage(); |
| 286 GrSLConstantVec knownCoverageValue = builder.getKnownCoverageValue(); | |
| 287 | 264 |
| 288 for (int e = 0; e < fDesc.numCoverageEffects(); ++e) { | 265 for (int e = 0; e < fDesc.numCoverageEffects(); ++e) { |
| 289 effectUniformArrays[e] = &fCoverageEffects[e].fSamplerUnis; | 266 effectUniformArrays[e] = &fCoverageEffects[e].fSamplerUnis; |
| 290 } | 267 } |
| 291 | 268 |
| 292 builder.emitEffects(coverageStages, | 269 builder.emitEffects(coverageStages, |
| 293 fDesc.getEffectKeys() + fDesc.numColorEffects(), | 270 fDesc.getEffectKeys() + fDesc.numColorEffects(), |
| 294 fDesc.numCoverageEffects(), | 271 fDesc.numCoverageEffects(), |
| 295 &inCoverage, | 272 &inCoverage, |
| 296 &knownCoverageValue, | |
| 297 effectUniformArrays.get(), | 273 effectUniformArrays.get(), |
| 298 glEffects.get()); | 274 glEffects.get()); |
| 299 for (int e = 0; e < fDesc.numCoverageEffects(); ++e) { | 275 for (int e = 0; e < fDesc.numCoverageEffects(); ++e) { |
| 300 fCoverageEffects[e].fGLEffect = glEffects[e]; | 276 fCoverageEffects[e].fGLEffect = glEffects[e]; |
| 301 } | 277 } |
| 302 | 278 |
| 303 // discard if coverage is zero | 279 // discard if coverage is zero |
| 304 if (header.fDiscardIfZeroCoverage && kOnes_GrSLConstantVec != knownCoverageV alue) { | 280 if (header.fDiscardIfZeroCoverage && !inCoverage.isOnes()) { |
| 305 if (kZeros_GrSLConstantVec == knownCoverageValue) { | 281 if (inCoverage.isZeros()) { |
| 306 // This is unfortunate. | 282 // This is unfortunate. |
| 307 builder.fsCodeAppend("\tdiscard;\n"); | 283 builder.fsCodeAppend("\tdiscard;\n"); |
| 308 } else { | 284 } else { |
| 309 builder.fsCodeAppendf("\tif (all(lessThanEqual(%s, vec4(0.0)))) {\n\ t\tdiscard;\n\t}\n", | 285 builder.fsCodeAppendf("\tif (all(lessThanEqual(%s, vec4(0.0)))) {\n\ t\tdiscard;\n\t}\n", |
| 310 inCoverage.c_str()); | 286 inCoverage.c_str()); |
| 311 } | 287 } |
| 312 } | 288 } |
| 313 | 289 |
| 314 if (GrGLProgramDesc::CoverageOutputUsesSecondaryOutput(header.fCoverageOutpu t)) { | 290 if (GrGLProgramDesc::CoverageOutputUsesSecondaryOutput(header.fCoverageOutpu t)) { |
| 315 const char* secondaryOutputName = builder.enableSecondaryOutput(); | 291 const char* secondaryOutputName = builder.enableSecondaryOutput(); |
| 316 | 292 |
| 317 // default coeff to ones for kCoverage_DualSrcOutput | 293 // default coeff to ones for kCoverage_DualSrcOutput |
| 318 SkString coeff; | 294 GrGLSLExpr<4> coeff(1); |
| 319 GrSLConstantVec knownCoeffValue = kOnes_GrSLConstantVec; | |
| 320 if (GrGLProgramDesc::kSecondaryCoverageISA_CoverageOutput == header.fCov erageOutput) { | 295 if (GrGLProgramDesc::kSecondaryCoverageISA_CoverageOutput == header.fCov erageOutput) { |
| 321 // Get (1-A) into coeff | 296 // Get (1-A) into coeff |
| 322 SkString inColorAlpha; | 297 coeff = GrGLSLExpr<4>::VectorCast(1 - inColor.a()); |
|
bsalomon
2013/10/02 15:18:04
This line looks a bit odd to me. Does the 1 get ca
Kimmo Kinnunen
2013/10/08 12:18:29
It will use the Expr<M> operator-(int, Expr<M>).
T
bsalomon
2013/10/08 14:45:27
Ah-ha! I missed that operator on first read. This
Kimmo Kinnunen
2013/10/09 13:39:59
..
bsalomon
2013/10/09 19:53:31
I'm not 100% sure that I follow the example but I
| |
| 323 GrGLSLGetComponent4f(&inColorAlpha, | |
| 324 inColor.c_str(), | |
| 325 kA_GrColorComponentFlag, | |
| 326 knownColorValue, | |
| 327 true); | |
| 328 knownCoeffValue = GrGLSLSubtractf<1>(&coeff, | |
| 329 NULL, | |
| 330 inColorAlpha.c_str(), | |
| 331 kOnes_GrSLConstantVec, | |
| 332 knownColorValue, | |
| 333 true); | |
| 334 } else if (GrGLProgramDesc::kSecondaryCoverageISC_CoverageOutput == head er.fCoverageOutput) { | 298 } else if (GrGLProgramDesc::kSecondaryCoverageISC_CoverageOutput == head er.fCoverageOutput) { |
| 335 // Get (1-RGBA) into coeff | 299 // Get (1-RGBA) into coeff |
| 336 knownCoeffValue = GrGLSLSubtractf<4>(&coeff, | 300 coeff = 1 - inColor; |
| 337 NULL, | |
| 338 inColor.c_str(), | |
| 339 kOnes_GrSLConstantVec, | |
| 340 knownColorValue, | |
| 341 true); | |
| 342 } | 301 } |
| 343 // Get coeff * coverage into modulate and then write that to the dual so urce output. | 302 // Get coeff * coverage into modulate and then write that to the dual so urce output. |
| 344 SkString modulate; | 303 builder.fsCodeAppendf("\t%s = %s;\n", secondaryOutputName, (coeff * inCo verage).c_str()); |
| 345 GrGLSLModulatef<4>(&modulate, | |
| 346 coeff.c_str(), | |
| 347 inCoverage.c_str(), | |
| 348 knownCoeffValue, | |
| 349 knownCoverageValue, | |
| 350 false); | |
| 351 builder.fsCodeAppendf("\t%s = %s;\n", secondaryOutputName, modulate.c_st r()); | |
| 352 } | 304 } |
| 353 | 305 |
| 354 /////////////////////////////////////////////////////////////////////////// | 306 /////////////////////////////////////////////////////////////////////////// |
| 355 // combine color and coverage as frag color | 307 // combine color and coverage as frag color |
| 356 | 308 |
| 357 // Get "color * coverage" into fragColor | 309 // Get "color * coverage" into fragColor |
| 358 SkString fragColor; | 310 GrGLSLExpr<4> fragColor = inColor * inCoverage; |
| 359 GrSLConstantVec knownFragColorValue = GrGLSLModulatef<4>(&fragColor, | |
| 360 inColor.c_str(), | |
| 361 inCoverage.c_str(), | |
| 362 knownColorValue, | |
| 363 knownCoverageValue, | |
| 364 true); | |
| 365 // Now tack on "+(1-coverage)dst onto the frag color if we were asked to do so. | 311 // Now tack on "+(1-coverage)dst onto the frag color if we were asked to do so. |
| 366 if (GrGLProgramDesc::kCombineWithDst_CoverageOutput == header.fCoverageOutpu t) { | 312 if (GrGLProgramDesc::kCombineWithDst_CoverageOutput == header.fCoverageOutpu t) { |
| 367 SkString dstCoeff; | 313 GrGLSLExpr<4> dstCoeff = 1 - inCoverage; |
| 368 GrSLConstantVec knownDstCoeffValue = GrGLSLSubtractf<4>(&dstCoeff, | 314 |
| 369 NULL, | 315 GrGLSLExpr<4> dstContribution = dstCoeff * GrGLSLExpr<4>(builder.dstColo r()); |
| 370 inCoverage.c_str (), | 316 |
| 371 kOnes_GrSLConsta ntVec, | 317 fragColor = fragColor + dstContribution; |
| 372 knownCoverageVal ue, | |
| 373 true); | |
| 374 SkString dstContribution; | |
| 375 GrSLConstantVec knownDstContributionValue = GrGLSLModulatef<4>(&dstContr ibution, | |
| 376 dstCoeff. c_str(), | |
| 377 builder.d stColor(), | |
| 378 knownDstC oeffValue, | |
| 379 kNone_GrS LConstantVec, | |
| 380 true); | |
| 381 SkString oldFragColor = fragColor; | |
| 382 fragColor.reset(); | |
| 383 GrGLSLAddf<4>(&fragColor, | |
| 384 oldFragColor.c_str(), | |
| 385 dstContribution.c_str(), | |
| 386 knownFragColorValue, | |
| 387 knownDstContributionValue, | |
| 388 false); | |
| 389 } else { | |
| 390 expand_known_value4f(&fragColor, knownFragColorValue); | |
| 391 } | 318 } |
| 392 builder.fsCodeAppendf("\t%s = %s;\n", builder.getColorOutputName(), fragColo r.c_str()); | 319 builder.fsCodeAppendf("\t%s = %s;\n", builder.getColorOutputName(), fragColo r.c_str()); |
| 393 | 320 |
| 394 if (!builder.finish(&fProgramID)) { | 321 if (!builder.finish(&fProgramID)) { |
| 395 return false; | 322 return false; |
| 396 } | 323 } |
| 397 | 324 |
| 398 fUniformHandles.fRTHeightUni = builder.getRTHeightUniform(); | 325 fUniformHandles.fRTHeightUni = builder.getRTHeightUniform(); |
| 399 fUniformHandles.fDstCopyTopLeftUni = builder.getDstCopyTopLeftUniform(); | 326 fUniformHandles.fDstCopyTopLeftUni = builder.getDstCopyTopLeftUniform(); |
| 400 fUniformHandles.fDstCopyScaleUni = builder.getDstCopyScaleUniform(); | 327 fUniformHandles.fDstCopyScaleUni = builder.getDstCopyScaleUniform(); |
| (...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 625 | 552 |
| 626 fMatrixState.fViewMatrix = drawState.getViewMatrix(); | 553 fMatrixState.fViewMatrix = drawState.getViewMatrix(); |
| 627 fMatrixState.fRenderTargetSize = size; | 554 fMatrixState.fRenderTargetSize = size; |
| 628 fMatrixState.fRenderTargetOrigin = rt->origin(); | 555 fMatrixState.fRenderTargetOrigin = rt->origin(); |
| 629 | 556 |
| 630 GrGLfloat viewMatrix[3 * 3]; | 557 GrGLfloat viewMatrix[3 * 3]; |
| 631 fMatrixState.getGLMatrix<3>(viewMatrix); | 558 fMatrixState.getGLMatrix<3>(viewMatrix); |
| 632 fUniformManager.setMatrix3f(fUniformHandles.fViewMatrixUni, viewMatrix); | 559 fUniformManager.setMatrix3f(fUniformHandles.fViewMatrixUni, viewMatrix); |
| 633 } | 560 } |
| 634 } | 561 } |
| OLD | NEW |