/* * Copyright 2016 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkNormalMapSource.h" #include "SkArenaAlloc.h" #include "SkLightingShader.h" #include "SkMatrix.h" #include "SkNormalSource.h" #include "SkReadBuffer.h" #include "SkWriteBuffer.h" #if SK_SUPPORT_GPU #include "GrCoordTransform.h" #include "glsl/GrGLSLFragmentProcessor.h" #include "glsl/GrGLSLFragmentShaderBuilder.h" #include "SkGr.h" class NormalMapFP : public GrFragmentProcessor { public: static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> mapFP, const SkMatrix& invCTM) { return std::unique_ptr<GrFragmentProcessor>(new NormalMapFP(std::move(mapFP), invCTM)); } const char* name() const override { return "NormalMapFP"; } const SkMatrix& invCTM() const { return fInvCTM; } std::unique_ptr<GrFragmentProcessor> clone() const override { return Make(this->childProcessor(0).clone(), fInvCTM); } private: class GLSLNormalMapFP : public GrGLSLFragmentProcessor { public: GLSLNormalMapFP() : fColumnMajorInvCTM22{0.0f} {} void emitCode(EmitArgs& args) override { GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder; GrGLSLUniformHandler* uniformHandler = args.fUniformHandler; // add uniform const char* xformUniName = nullptr; fXformUni = uniformHandler->addUniform(kFragment_GrShaderFlag, kFloat2x2_GrSLType, "Xform", &xformUniName); SkString dstNormalColorName("dstNormalColor"); this->emitChild(0, &dstNormalColorName, args); fragBuilder->codeAppendf("float3 normal = normalize(%s.rgb - float3(0.5));", dstNormalColorName.c_str()); // If there's no x & y components, return (0, 0, +/- 1) instead to avoid division by 0 fragBuilder->codeAppend( "if (abs(normal.z) > 0.999) {"); fragBuilder->codeAppendf(" %s = normalize(half4(0.0, 0.0, half(normal.z), 0.0));", args.fOutputColor); // Else, Normalizing the transformed X and Y, while keeping constant both Z and the // vector's angle in the XY plane. This maintains the "slope" for the surface while // appropriately rotating the normal regardless of any anisotropic scaling that occurs. // Here, we call 'scaling factor' the number that must divide the transformed X and Y so // that the normal's length remains equal to 1. fragBuilder->codeAppend( "} else {"); fragBuilder->codeAppendf(" float2 transformed = %s * normal.xy;", xformUniName); fragBuilder->codeAppend( " float scalingFactorSquared = " "( (transformed.x * transformed.x) " "+ (transformed.y * transformed.y) )" "/(1.0 - (normal.z * normal.z));"); fragBuilder->codeAppendf(" %s = half4(half2(transformed * " "inversesqrt(scalingFactorSquared))," "half(normal.z), 0.0);", args.fOutputColor); fragBuilder->codeAppend( "}"); } static void GenKey(const GrProcessor&, const GrShaderCaps&, GrProcessorKeyBuilder* b) { b->add32(0x0); } private: void onSetData(const GrGLSLProgramDataManager& pdman, const GrFragmentProcessor& proc) override { const NormalMapFP& normalMapFP = proc.cast<NormalMapFP>(); const SkMatrix& invCTM = normalMapFP.invCTM(); fColumnMajorInvCTM22[0] = invCTM.get(SkMatrix::kMScaleX); fColumnMajorInvCTM22[1] = invCTM.get(SkMatrix::kMSkewY); fColumnMajorInvCTM22[2] = invCTM.get(SkMatrix::kMSkewX); fColumnMajorInvCTM22[3] = invCTM.get(SkMatrix::kMScaleY); pdman.setMatrix2f(fXformUni, fColumnMajorInvCTM22); } private: // Upper-right 2x2 corner of the inverse of the CTM in column-major form float fColumnMajorInvCTM22[4]; GrGLSLProgramDataManager::UniformHandle fXformUni; }; void onGetGLSLProcessorKey(const GrShaderCaps& caps, GrProcessorKeyBuilder* b) const override { GLSLNormalMapFP::GenKey(*this, caps, b); } NormalMapFP(std::unique_ptr<GrFragmentProcessor> mapFP, const SkMatrix& invCTM) : INHERITED(kMappedNormalsFP_ClassID, kNone_OptimizationFlags) , fInvCTM(invCTM) { this->registerChildProcessor(std::move(mapFP)); } GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLNormalMapFP; } bool onIsEqual(const GrFragmentProcessor& proc) const override { const NormalMapFP& normalMapFP = proc.cast<NormalMapFP>(); return fInvCTM == normalMapFP.fInvCTM; } SkMatrix fInvCTM; typedef GrFragmentProcessor INHERITED; }; std::unique_ptr<GrFragmentProcessor> SkNormalMapSourceImpl::asFragmentProcessor( const GrFPArgs& args) const { std::unique_ptr<GrFragmentProcessor> mapFP = as_SB(fMapShader)->asFragmentProcessor(args); if (!mapFP) { return nullptr; } return NormalMapFP::Make(std::move(mapFP), fInvCTM); } #endif // SK_SUPPORT_GPU //////////////////////////////////////////////////////////////////////////// SkNormalMapSourceImpl::Provider::Provider(const SkNormalMapSourceImpl& source, SkShaderBase::Context* mapContext) : fSource(source) , fMapContext(mapContext) {} SkNormalSource::Provider* SkNormalMapSourceImpl::asProvider(const SkShaderBase::ContextRec &rec, SkArenaAlloc* alloc) const { SkMatrix normTotalInv; if (!this->computeNormTotalInverse(rec, &normTotalInv)) { return nullptr; } // Normals really aren't colors, so to ensure we can always make the context, we ignore // the rec's colorspace SkColorSpace* dstColorSpace = nullptr; // Overriding paint's alpha because we need the normal map's RGB channels to be unpremul'd SkPaint overridePaint {*(rec.fPaint)}; overridePaint.setAlpha(0xFF); SkShaderBase::ContextRec overrideRec(overridePaint, *(rec.fMatrix), rec.fLocalMatrix, rec.fDstColorType, dstColorSpace); auto* context = as_SB(fMapShader)->makeContext(overrideRec, alloc); if (!context) { return nullptr; } return alloc->make<Provider>(*this, context); } bool SkNormalMapSourceImpl::computeNormTotalInverse(const SkShaderBase::ContextRec& rec, SkMatrix* normTotalInverse) const { SkMatrix total = SkMatrix::Concat(*rec.fMatrix, fMapShader->getLocalMatrix()); if (rec.fLocalMatrix) { total.preConcat(*rec.fLocalMatrix); } return total.invert(normTotalInverse); } #define BUFFER_MAX 16 void SkNormalMapSourceImpl::Provider::fillScanLine(int x, int y, SkPoint3 output[], int count) const { SkPMColor tmpNormalColors[BUFFER_MAX]; do { int n = SkTMin(count, BUFFER_MAX); fMapContext->shadeSpan(x, y, tmpNormalColors, n); for (int i = 0; i < n; i++) { SkPoint3 tempNorm; tempNorm.set(SkIntToScalar(SkGetPackedR32(tmpNormalColors[i])) - 127.0f, SkIntToScalar(SkGetPackedG32(tmpNormalColors[i])) - 127.0f, SkIntToScalar(SkGetPackedB32(tmpNormalColors[i])) - 127.0f); tempNorm.normalize(); if (!SkScalarNearlyEqual(SkScalarAbs(tempNorm.fZ), 1.0f)) { SkVector transformed = fSource.fInvCTM.mapVector(tempNorm.fX, tempNorm.fY); // Normalizing the transformed X and Y, while keeping constant both Z and the // vector's angle in the XY plane. This maintains the "slope" for the surface while // appropriately rotating the normal for any anisotropic scaling that occurs. // Here, we call scaling factor the number that must divide the transformed X and Y // so that the normal's length remains equal to 1. SkScalar scalingFactorSquared = (SkScalarSquare(transformed.fX) + SkScalarSquare(transformed.fY)) / (1.0f - SkScalarSquare(tempNorm.fZ)); SkScalar invScalingFactor = SkScalarInvert(SkScalarSqrt(scalingFactorSquared)); output[i].fX = transformed.fX * invScalingFactor; output[i].fY = transformed.fY * invScalingFactor; output[i].fZ = tempNorm.fZ; } else { output[i] = {0.0f, 0.0f, tempNorm.fZ}; output[i].normalize(); } SkASSERT(SkScalarNearlyEqual(output[i].length(), 1.0f)); } output += n; x += n; count -= n; } while (count > 0); } //////////////////////////////////////////////////////////////////////////////// sk_sp<SkFlattenable> SkNormalMapSourceImpl::CreateProc(SkReadBuffer& buf) { sk_sp<SkShader> mapShader = buf.readFlattenable<SkShaderBase>(); SkMatrix invCTM; buf.readMatrix(&invCTM); return sk_make_sp<SkNormalMapSourceImpl>(std::move(mapShader), invCTM); } void SkNormalMapSourceImpl::flatten(SkWriteBuffer& buf) const { this->INHERITED::flatten(buf); buf.writeFlattenable(fMapShader.get()); buf.writeMatrix(fInvCTM); } //////////////////////////////////////////////////////////////////////////// sk_sp<SkNormalSource> SkNormalSource::MakeFromNormalMap(sk_sp<SkShader> map, const SkMatrix& ctm) { SkMatrix invCTM; if (!ctm.invert(&invCTM) || !map) { return nullptr; } return sk_make_sp<SkNormalMapSourceImpl>(std::move(map), invCTM); }