Refactoring of GPU NormalMap handling out into its own class.

The purpose of this change is to refactor the handling of normal maps out of SkLightingShader, laying the groundwork to eventually allow for multiple normal sources.

What this CL includes:

- Created a new 'NormalMapFP', out of the existing normal map reading behavior in LightingFP.

- Encapsulates this new fragment processor on a new class NormalMapSource.

- Created a NormalSource abstraction that will interface with SkLightingShader.

- Adapted SkLightingShader to use the normals from its NormalSource field ON THE GPU SIDE. No changes done to the CPU side yet.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2043393002

Review-Url: https://codereview.chromium.org/2043393002
diff --git a/gyp/core.gypi b/gyp/core.gypi
index ff3e270..d3102e4 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -154,6 +154,7 @@
         '<(skia_src_path)/core/SkLayerInfo.h',
         '<(skia_src_path)/core/SkLightingShader.h',
         '<(skia_src_path)/core/SkLightingShader.cpp',
+        '<(skia_src_path)/core/SkLightingShader_NormalSource.cpp',
         '<(skia_src_path)/core/SkLinearBitmapPipeline.cpp',
         '<(skia_src_path)/core/SkLinearBitmapPipeline.h',
         '<(skia_src_path)/core/SkLinearBitmapPipeline_core.h',
diff --git a/include/core/SkFlattenable.h b/include/core/SkFlattenable.h
index 260ea33..5eabcb3 100644
--- a/include/core/SkFlattenable.h
+++ b/include/core/SkFlattenable.h
@@ -81,6 +81,7 @@
         kSkShader_Type,
         kSkUnused_Type,     // used to be SkUnitMapper
         kSkXfermode_Type,
+        kNormalSource_Type,
     };
 
     typedef sk_sp<SkFlattenable> (*Factory)(SkReadBuffer&);
diff --git a/src/core/SkBitmapProcShader.cpp b/src/core/SkBitmapProcShader.cpp
index c86dfdd..b2dee0b 100644
--- a/src/core/SkBitmapProcShader.cpp
+++ b/src/core/SkBitmapProcShader.cpp
@@ -305,6 +305,12 @@
     return fRawBitmap.isOpaque();
 }
 
+bool SkBitmapProcShader::BitmapIsTooBig(const SkBitmap& bm) {
+    static const int kMaxSize = 65535;
+
+    return bm.width() > kMaxSize || bm.height() > kMaxSize;
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 #include "SkUnPreMultiply.h"
diff --git a/src/core/SkBitmapProcShader.h b/src/core/SkBitmapProcShader.h
index 1d37433..5f0b5ee 100644
--- a/src/core/SkBitmapProcShader.h
+++ b/src/core/SkBitmapProcShader.h
@@ -21,6 +21,11 @@
 
     bool isOpaque() const override;
 
+    // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer, as it
+    // communicates between its matrix-proc and its sampler-proc. Until we can
+    // widen that, we have to reject bitmaps that are larger.
+    static bool BitmapIsTooBig(const SkBitmap&);
+
     SK_TO_STRING_OVERRIDE()
     SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBitmapProcShader)
 
diff --git a/src/core/SkLightingShader.cpp b/src/core/SkLightingShader.cpp
index aaf29fc..87867a9 100644
--- a/src/core/SkLightingShader.cpp
+++ b/src/core/SkLightingShader.cpp
@@ -42,17 +42,19 @@
 
     /** Create a new lighting shader that uses the provided normal map and
         lights to light the diffuse bitmap.
-        @param diffuse    the diffuse bitmap
-        @param normal     the normal map
-        @param lights     the lights applied to the normal map
-        @param invNormRotation rotation applied to the normal map's normals
-        @param diffLocalM the local matrix for the diffuse coordinates
-        @param normLocalM the local matrix for the normal coordinates
+        @param diffuse           the diffuse bitmap
+        @param normal            the normal map
+        @param lights            the lights applied to the normal map
+        @param invNormRotation   rotation applied to the normal map's normals
+        @param diffLocalM        the local matrix for the diffuse coordinates
+        @param normLocalM        the local matrix for the normal coordinates
+        @param normalSource      the normal source for GPU computations
     */
     SkLightingShaderImpl(const SkBitmap& diffuse, const SkBitmap& normal,
                          const sk_sp<SkLights> lights,
                          const SkVector& invNormRotation,
-                         const SkMatrix* diffLocalM, const SkMatrix* normLocalM)
+                         const SkMatrix* diffLocalM, const SkMatrix* normLocalM,
+                         sk_sp<SkLightingShader::NormalSource> normalSource)
         : INHERITED(diffLocalM)
         , fDiffuseMap(diffuse)
         , fNormalMap(normal)
@@ -67,6 +69,7 @@
         // Pre-cache so future calls to fNormLocalMatrix.getType() are threadsafe.
         (void)fNormLocalMatrix.getType();
 
+        fNormalSource = std::move(normalSource);
     }
 
     bool isOpaque() const override;
@@ -117,6 +120,8 @@
     SkMatrix        fNormLocalMatrix;
     SkVector        fInvNormRotation;
 
+    sk_sp<SkLightingShader::NormalSource> fNormalSource;
+
     friend class SkLightingShader;
 
     typedef SkShader INHERITED;
@@ -134,24 +139,18 @@
 #include "glsl/GrGLSLFragmentShaderBuilder.h"
 #include "glsl/GrGLSLProgramDataManager.h"
 #include "glsl/GrGLSLUniformHandler.h"
+#include "SkBitmapProcShader.h"
 #include "SkGr.h"
 #include "SkGrPriv.h"
 
 class LightingFP : public GrFragmentProcessor {
 public:
-    LightingFP(GrTexture* diffuse, GrTexture* normal, const SkMatrix& diffMatrix,
-               const SkMatrix& normMatrix, const GrTextureParams& diffParams,
-               const GrTextureParams& normParams, sk_sp<SkLights> lights,
-               const SkVector& invNormRotation)
+    LightingFP(GrTexture* diffuse, const SkMatrix& diffMatrix, const GrTextureParams& diffParams,
+               sk_sp<SkLights> lights, sk_sp<GrFragmentProcessor> normalFP)
         : fDiffDeviceTransform(kLocal_GrCoordSet, diffMatrix, diffuse, diffParams.filterMode())
-        , fNormDeviceTransform(kLocal_GrCoordSet, normMatrix, normal, normParams.filterMode())
-        , fDiffuseTextureAccess(diffuse, diffParams)
-        , fNormalTextureAccess(normal, normParams)
-        , fInvNormRotation(invNormRotation) {
+        , fDiffuseTextureAccess(diffuse, diffParams) {
         this->addCoordTransform(&fDiffDeviceTransform);
-        this->addCoordTransform(&fNormDeviceTransform);
         this->addTextureAccess(&fDiffuseTextureAccess);
-        this->addTextureAccess(&fNormalTextureAccess);
 
         // fuse all ambient lights into a single one
         fAmbientColor.set(0.0f, 0.0f, 0.0f);
@@ -165,16 +164,16 @@
             }
         }
 
+        this->registerChildProcessor(std::move(normalFP));
         this->initClassID<LightingFP>();
     }
 
-    class LightingGLFP : public GrGLSLFragmentProcessor {
+    class GLSLLightingFP : public GrGLSLFragmentProcessor {
     public:
-        LightingGLFP() {
+        GLSLLightingFP() {
             fLightDir.fX = 10000.0f;
             fLightColor.fX = 0.0f;
             fAmbientColor.fX = 0.0f;
-            fInvNormRotation.set(0.0f, 0.0f);
         }
 
         void emitCode(EmitArgs& args) override {
@@ -198,33 +197,16 @@
                                                           kVec3f_GrSLType, kDefault_GrSLPrecision,
                                                           "AmbientColor", &ambientColorUniName);
 
-            const char* xformUniName = nullptr;
-            fXformUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
-                                                   kVec2f_GrSLType, kDefault_GrSLPrecision,
-                                                   "Xform", &xformUniName);
-
             fragBuilder->codeAppend("vec4 diffuseColor = ");
             fragBuilder->appendTextureLookupAndModulate(args.fInputColor, args.fTexSamplers[0],
                                                 args.fCoords[0].c_str(),
                                                 args.fCoords[0].getType());
             fragBuilder->codeAppend(";");
 
-            fragBuilder->codeAppend("vec4 normalColor = ");
-            fragBuilder->appendTextureLookup(args.fTexSamplers[1],
-                                     args.fCoords[1].c_str(),
-                                     args.fCoords[1].getType());
-            fragBuilder->codeAppend(";");
+            SkString dstNormalName("dstNormal");
+            this->emitChild(0, nullptr, &dstNormalName, args);
 
-            fragBuilder->codeAppend("vec3 normal = normalColor.rgb - vec3(0.5);");
-
-            fragBuilder->codeAppendf(
-                                 "mat3 m = mat3(%s.x, -%s.y, 0.0, %s.y, %s.x, 0.0, 0.0, 0.0, 1.0);",
-                                 xformUniName, xformUniName, xformUniName, xformUniName);
-
-            // TODO: inverse map the light direction vectors in the vertex shader rather than
-            // transforming all the normals here!
-            fragBuilder->codeAppend("normal = normalize(m*normal);");
-
+            fragBuilder->codeAppendf("vec3 normal = %s.xyz;", dstNormalName.c_str());
             fragBuilder->codeAppendf("float NdotL = clamp(dot(normal, %s), 0.0, 1.0);",
                                      lightDirUniName);
             // diffuse light
@@ -262,12 +244,6 @@
                 pdman.set3fv(fAmbientColorUni, 1, &ambientColor.fX);
                 fAmbientColor = ambientColor;
             }
-
-            const SkVector& invNormRotation = lightingFP.invNormRotation();
-            if (invNormRotation != fInvNormRotation) {
-                pdman.set2fv(fXformUni, 1, &invNormRotation.fX);
-                fInvNormRotation = invNormRotation;
-            }
         }
 
     private:
@@ -279,13 +255,10 @@
 
         SkColor3f fAmbientColor;
         GrGLSLProgramDataManager::UniformHandle fAmbientColorUni;
-
-        SkVector fInvNormRotation;
-        GrGLSLProgramDataManager::UniformHandle fXformUni;
     };
 
     void onGetGLSLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override {
-        LightingGLFP::GenKey(*this, caps, b);
+        GLSLLightingFP::GenKey(*this, caps, b);
     }
 
     const char* name() const override { return "LightingFP"; }
@@ -297,32 +270,24 @@
     const SkVector3& lightDir() const { return fLightDir; }
     const SkColor3f& lightColor() const { return fLightColor; }
     const SkColor3f& ambientColor() const { return fAmbientColor; }
-    const SkVector& invNormRotation() const { return fInvNormRotation; }
 
 private:
-    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new LightingGLFP; }
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLLightingFP; }
 
     bool onIsEqual(const GrFragmentProcessor& proc) const override {
         const LightingFP& lightingFP = proc.cast<LightingFP>();
         return fDiffDeviceTransform == lightingFP.fDiffDeviceTransform &&
-               fNormDeviceTransform == lightingFP.fNormDeviceTransform &&
                fDiffuseTextureAccess == lightingFP.fDiffuseTextureAccess &&
-               fNormalTextureAccess == lightingFP.fNormalTextureAccess &&
                fLightDir == lightingFP.fLightDir &&
                fLightColor == lightingFP.fLightColor &&
-               fAmbientColor == lightingFP.fAmbientColor &&
-               fInvNormRotation == lightingFP.fInvNormRotation;
+               fAmbientColor == lightingFP.fAmbientColor;
     }
 
     GrCoordTransform fDiffDeviceTransform;
-    GrCoordTransform fNormDeviceTransform;
     GrTextureAccess  fDiffuseTextureAccess;
-    GrTextureAccess  fNormalTextureAccess;
     SkVector3        fLightDir;
     SkColor3f        fLightColor;
     SkColor3f        fAmbientColor;
-
-    SkVector         fInvNormRotation;
 };
 
 ////////////////////////////////////////////////////////////////////////////
@@ -357,56 +322,39 @@
                                                      SkFilterQuality filterQuality,
                                                      SkSourceGammaTreatment gammaTreatment) const {
     // we assume diffuse and normal maps have same width and height
-    // TODO: support different sizes
+    // TODO: support different sizes, will be addressed when diffuse maps are factored out of
+    //       SkLightingShader in a future CL
     SkASSERT(fDiffuseMap.width() == fNormalMap.width() &&
              fDiffuseMap.height() == fNormalMap.height());
-    SkMatrix diffM, normM;
+    SkMatrix diffM;
 
     if (!make_mat(fDiffuseMap, this->getLocalMatrix(), localMatrix, &diffM)) {
         return nullptr;
     }
 
-    if (!make_mat(fNormalMap, fNormLocalMatrix, localMatrix, &normM)) {
-        return nullptr;
-    }
-
     bool doBicubic;
     GrTextureParams::FilterMode diffFilterMode = GrSkFilterQualityToGrFilterMode(
-                                        SkTMin(filterQuality, kMedium_SkFilterQuality),
-                                        viewM,
-                                        this->getLocalMatrix(),
-                                        &doBicubic);
-    SkASSERT(!doBicubic);
-
-    GrTextureParams::FilterMode normFilterMode = GrSkFilterQualityToGrFilterMode(
-                                        SkTMin(filterQuality, kMedium_SkFilterQuality),
-                                        viewM,
-                                        fNormLocalMatrix,
-                                        &doBicubic);
+                                                    SkTMin(filterQuality, kMedium_SkFilterQuality),
+                                                    viewM,
+                                                    this->getLocalMatrix(),
+                                                    &doBicubic);
     SkASSERT(!doBicubic);
 
     // TODO: support other tile modes
     GrTextureParams diffParams(kClamp_TileMode, diffFilterMode);
-    SkAutoTUnref<GrTexture> diffuseTexture(GrRefCachedBitmapTexture(context,
-                                                                    fDiffuseMap, diffParams,
-                                                                    gammaTreatment));
+    SkAutoTUnref<GrTexture> diffuseTexture(GrRefCachedBitmapTexture(context, fDiffuseMap,
+                                                                    diffParams, gammaTreatment));
     if (!diffuseTexture) {
         SkErrorInternals::SetError(kInternalError_SkError, "Couldn't convert bitmap to texture.");
         return nullptr;
     }
 
-    GrTextureParams normParams(kClamp_TileMode, normFilterMode);
-    SkAutoTUnref<GrTexture> normalTexture(GrRefCachedBitmapTexture(context,
-                                                                   fNormalMap, normParams,
-                                                                   gammaTreatment));
-    if (!normalTexture) {
-        SkErrorInternals::SetError(kInternalError_SkError, "Couldn't convert bitmap to texture.");
-        return nullptr;
-    }
-
+    sk_sp<GrFragmentProcessor> normalFP(
+            fNormalSource->asFragmentProcessor(context, viewM, localMatrix, filterQuality,
+                                               gammaTreatment));
     sk_sp<GrFragmentProcessor> inner (
-        new LightingFP(diffuseTexture, normalTexture, diffM, normM, diffParams, normParams, fLights,
-                       fInvNormRotation));
+            new LightingFP(diffuseTexture, diffM, diffParams, fLights, std::move(normalFP)));
+
     return GrFragmentProcessor::MulOutputByInputAlpha(std::move(inner));
 }
 
@@ -418,14 +366,14 @@
     return fDiffuseMap.isOpaque();
 }
 
-SkLightingShaderImpl::LightingShaderContext::LightingShaderContext(const SkLightingShaderImpl& shader,
-                                                                   const ContextRec& rec,
-                                                                   SkBitmapProcState* diffuseState,
-                                                                   SkBitmapProcState* normalState)
+SkLightingShaderImpl::LightingShaderContext::LightingShaderContext(
+                                                                const SkLightingShaderImpl& shader,
+                                                                const ContextRec& rec,
+                                                                SkBitmapProcState* diffuseState,
+                                                                SkBitmapProcState* normalState)
     : INHERITED(shader, rec)
     , fDiffuseState(diffuseState)
-    , fNormalState(normalState)
-{
+    , fNormalState(normalState) {
     const SkPixmap& pixmap = fDiffuseState->fPixmap;
     bool isOpaque = pixmap.isOpaque();
 
@@ -615,8 +563,11 @@
         invNormRotation = buf.readPoint();
     }
 
+    sk_sp<SkLightingShader::NormalSource> normalSource(
+            buf.readFlattenable<SkLightingShader::NormalSource>());
+
     return sk_make_sp<SkLightingShaderImpl>(diffuse, normal, std::move(lights), invNormRotation,
-                                            &diffLocalM, &normLocalM);
+                                            &diffLocalM, &normLocalM, std::move(normalSource));
 }
 
 void SkLightingShaderImpl::flatten(SkWriteBuffer& buf) const {
@@ -644,6 +595,8 @@
         }
     }
     buf.writePoint(fInvNormRotation);
+
+    buf.writeFlattenable(fNormalSource.get());
 }
 
 bool SkLightingShaderImpl::computeNormTotalInverse(const ContextRec& rec,
@@ -685,7 +638,9 @@
         return nullptr;
     }
 
-    void* normalStateStorage = (char*)storage + sizeof(LightingShaderContext) + sizeof(SkBitmapProcState);
+    void* normalStateStorage = (char*)storage +
+                                sizeof(LightingShaderContext) +
+                                sizeof(SkBitmapProcState);
     SkBitmapProcState* normalState = new (normalStateStorage) SkBitmapProcState(fNormalMap,
                                             SkShader::kClamp_TileMode, SkShader::kClamp_TileMode,
                                                                     SkMipMap::DeduceTreatment(rec));
@@ -701,31 +656,23 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-static bool bitmap_is_too_big(const SkBitmap& bm) {
-    // SkBitmapProcShader stores bitmap coordinates in a 16bit buffer, as it
-    // communicates between its matrix-proc and its sampler-proc. Until we can
-    // widen that, we have to reject bitmaps that are larger.
-    //
-    static const int kMaxSize = 65535;
-
-    return bm.width() > kMaxSize || bm.height() > kMaxSize;
-}
-
 sk_sp<SkShader> SkLightingShader::Make(const SkBitmap& diffuse, const SkBitmap& normal,
                                        sk_sp<SkLights> lights,
                                        const SkVector& invNormRotation,
                                        const SkMatrix* diffLocalM, const SkMatrix* normLocalM) {
-    if (diffuse.isNull() || bitmap_is_too_big(diffuse) ||
-        normal.isNull() || bitmap_is_too_big(normal) ||
-        diffuse.width() != normal.width() ||
+    if (diffuse.isNull() || SkBitmapProcShader::BitmapIsTooBig(diffuse) ||
+        normal.isNull()  || SkBitmapProcShader::BitmapIsTooBig(normal) ||
+        diffuse.width()  != normal.width() ||
         diffuse.height() != normal.height()) {
         return nullptr;
     }
-
     SkASSERT(SkScalarNearlyEqual(invNormRotation.lengthSqd(), SK_Scalar1));
 
+    sk_sp<SkLightingShader::NormalSource> normalSource =
+            SkLightingShader::NormalMapSource::Make(normal, invNormRotation, normLocalM);
+
     return sk_make_sp<SkLightingShaderImpl>(diffuse, normal, std::move(lights),
-                                            invNormRotation, diffLocalM, normLocalM);
+            invNormRotation, diffLocalM, normLocalM, std::move(normalSource));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkLightingShader.h b/src/core/SkLightingShader.h
index f25b303..6301eee 100644
--- a/src/core/SkLightingShader.h
+++ b/src/core/SkLightingShader.h
@@ -16,6 +16,58 @@
 
 class SK_API SkLightingShader {
 public:
+    /** Abstract class that generates or reads in normals for use by SkLightingShader. Currently
+        implements the GPU side only. Not to be used as part of the API yet. Used internally by
+        SkLightingShader.
+    */
+    class NormalSource : public SkFlattenable {
+    public:
+        virtual ~NormalSource();
+
+#if SK_SUPPORT_GPU
+
+        /** Returns a fragment processor that takes no input and outputs a normal (already rotated)
+            as its output color. To be used as a child fragment processor.
+        */
+        virtual sk_sp<GrFragmentProcessor> asFragmentProcessor(
+                GrContext* context,
+                const SkMatrix& viewM,
+                const SkMatrix* localMatrix,
+                SkFilterQuality filterQuality,
+                SkSourceGammaTreatment gammaTreatment) const = 0;
+#endif
+
+        SK_DEFINE_FLATTENABLE_TYPE(NormalSource)
+    };
+
+    /** Returns a normal source that provides normals sourced from the the normal map argument.
+        Not to be used as part of the API yet. Used internally by SkLightingShader.
+
+        @param  normal                the normal map
+        @param  invNormRotation       rotation applied to the normal map's normals
+        @param  normLocalM            the local matrix for the normal map
+
+        nullptr will be returned if
+            'normal' is empty
+            'normal' too big (> 65535 on either side)
+
+        The normal map is currently assumed to be an 8888 image where the normal at a texel
+        is retrieved by:
+            N.x = R-127;
+            N.y = G-127;
+            N.z = B-127;
+            N.normalize();
+        The +Z axis is thus encoded in RGB as (127, 127, 255) while the -Z axis is
+        (127, 127, 0).
+    */
+    class NormalMapSource {
+    public:
+        static sk_sp<NormalSource> Make(const SkBitmap& normal, const SkVector& invNormRotation,
+                                        const SkMatrix* normLocalM);
+
+        SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
+    };
+
     /** Returns a shader that lights the diffuse and normal maps with a set of lights.
 
         It returns a shader with a reference count of 1.
diff --git a/src/core/SkLightingShader_NormalSource.cpp b/src/core/SkLightingShader_NormalSource.cpp
new file mode 100644
index 0000000..8ad3ed9
--- /dev/null
+++ b/src/core/SkLightingShader_NormalSource.cpp
@@ -0,0 +1,290 @@
+/*
+ * 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 "SkBitmapProcShader.h"
+#include "SkError.h"
+#include "SkErrorInternals.h"
+#include "SkLightingShader.h"
+#include "SkReadBuffer.h"
+#include "SkWriteBuffer.h"
+
+// Genretating vtable
+SkLightingShader::NormalSource::~NormalSource() {}
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SK_API NormalMapSourceImpl : public SkLightingShader::NormalSource {
+public:
+    NormalMapSourceImpl(const SkBitmap &normal, const SkVector &invNormRotation,
+                        const SkMatrix *normLocalM)
+        : fNormalMap(normal)
+        , fInvNormRotation(invNormRotation) {
+
+        if (normLocalM) {
+            fNormLocalMatrix = *normLocalM;
+        } else {
+            fNormLocalMatrix.reset();
+        }
+        // Pre-cache so future calls to fNormLocalMatrix.getType() are threadsafe.
+        (void)fNormLocalMatrix.getType();
+    }
+
+#if SK_SUPPORT_GPU
+    sk_sp<GrFragmentProcessor> asFragmentProcessor(GrContext*,
+                                                   const SkMatrix& viewM,
+                                                   const SkMatrix* localMatrix,
+                                                   SkFilterQuality,
+                                                   SkSourceGammaTreatment) const override;
+#endif
+
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(NormalMapSourceImpl)
+
+protected:
+    void flatten(SkWriteBuffer& buf) const override;
+
+private:
+    SkBitmap        fNormalMap;
+    SkMatrix        fNormLocalMatrix;
+    SkVector        fInvNormRotation;
+
+    friend class SkLightingShader::NormalMapSource;
+
+    typedef SkLightingShader::NormalSource INHERITED;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrCoordTransform.h"
+#include "GrInvariantOutput.h"
+#include "GrTextureParams.h"
+#include "glsl/GrGLSLFragmentProcessor.h"
+#include "glsl/GrGLSLFragmentShaderBuilder.h"
+#include "SkGr.h"
+
+class NormalMapFP : public GrFragmentProcessor {
+public:
+    NormalMapFP(GrTexture* normal, const SkMatrix& normMatrix, const GrTextureParams& normParams,
+                const SkVector& invNormRotation)
+        : fNormDeviceTransform(kLocal_GrCoordSet, normMatrix, normal, normParams.filterMode())
+        , fNormalTextureAccess(normal, normParams)
+        , fInvNormRotation(invNormRotation) {
+        this->addCoordTransform(&fNormDeviceTransform);
+        this->addTextureAccess(&fNormalTextureAccess);
+
+        this->initClassID<NormalMapFP>();
+    }
+
+    class GLSLNormalMapFP : public GrGLSLFragmentProcessor {
+    public:
+        GLSLNormalMapFP() {
+            fInvNormRotation.set(0.0f, 0.0f);
+        }
+
+        void emitCode(EmitArgs& args) override {
+
+            GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+            GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
+
+            // add uniform
+            const char* xformUniName = nullptr;
+            fXformUni = uniformHandler->addUniform(kFragment_GrShaderFlag,
+                                                   kVec2f_GrSLType, kDefault_GrSLPrecision,
+                                                   "Xform", &xformUniName);
+
+            fragBuilder->codeAppend("vec4 normalColor = ");
+            fragBuilder->appendTextureLookup(args.fTexSamplers[0],
+                                             args.fCoords[0].c_str(),
+                                             args.fCoords[0].getType());
+            fragBuilder->codeAppend(";");
+
+            fragBuilder->codeAppend("vec3 normal = normalColor.rgb - vec3(0.5);");
+
+            // TODO: inverse map the light direction vectors in the vertex shader rather than
+            // transforming all the normals here!
+            fragBuilder->codeAppendf(
+                    "mat3 m = mat3(%s.x, -%s.y, 0.0, %s.y, %s.x, 0.0, 0.0, 0.0, 1.0);",
+                    xformUniName, xformUniName, xformUniName, xformUniName);
+
+            fragBuilder->codeAppend("normal = normalize(m*normal);");
+            fragBuilder->codeAppendf("%s = vec4(normal, 0);", args.fOutputColor);
+        }
+
+        static void GenKey(const GrProcessor& proc, const GrGLSLCaps&,
+                           GrProcessorKeyBuilder* b) {
+            b->add32(0x0);
+        }
+
+    protected:
+        void onSetData(const GrGLSLProgramDataManager& pdman, const GrProcessor& proc) override {
+            const NormalMapFP& normalMapFP = proc.cast<NormalMapFP>();
+
+            const SkVector& invNormRotation = normalMapFP.invNormRotation();
+            if (invNormRotation != fInvNormRotation) {
+                pdman.set2fv(fXformUni, 1, &invNormRotation.fX);
+                fInvNormRotation = invNormRotation;
+            }
+        }
+
+    private:
+        SkVector fInvNormRotation;
+        GrGLSLProgramDataManager::UniformHandle fXformUni;
+    };
+
+    void onGetGLSLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override {
+        GLSLNormalMapFP::GenKey(*this, caps, b);
+    }
+
+    const char* name() const override { return "NormalMapFP"; }
+
+    void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
+        inout->setToUnknown(GrInvariantOutput::ReadInput::kWillNot_ReadInput);
+    }
+
+    const SkVector& invNormRotation() const { return fInvNormRotation; }
+
+private:
+    GrGLSLFragmentProcessor* onCreateGLSLInstance() const override { return new GLSLNormalMapFP; }
+
+    bool onIsEqual(const GrFragmentProcessor& proc) const override {
+        const NormalMapFP& normalMapFP = proc.cast<NormalMapFP>();
+        return fNormDeviceTransform == normalMapFP.fNormDeviceTransform &&
+               fNormalTextureAccess == normalMapFP.fNormalTextureAccess &&
+               fInvNormRotation     == normalMapFP.fInvNormRotation;
+    }
+
+    GrCoordTransform fNormDeviceTransform;
+    GrTextureAccess  fNormalTextureAccess;
+    SkVector         fInvNormRotation;
+};
+
+// TODO same code at SkLightingShader.cpp. Refactor to common source!
+static bool make_mat(const SkBitmap& bm,
+                     const SkMatrix& localMatrix1,
+                     const SkMatrix* localMatrix2,
+                     SkMatrix* result) {
+
+    result->setIDiv(bm.width(), bm.height());
+
+    SkMatrix lmInverse;
+    if (!localMatrix1.invert(&lmInverse)) {
+        return false;
+    }
+    if (localMatrix2) {
+        SkMatrix inv;
+        if (!localMatrix2->invert(&inv)) {
+            return false;
+        }
+        lmInverse.postConcat(inv);
+    }
+    result->preConcat(lmInverse);
+
+    return true;
+}
+
+sk_sp<GrFragmentProcessor> NormalMapSourceImpl::asFragmentProcessor(
+                                                     GrContext *context,
+                                                     const SkMatrix &viewM,
+                                                     const SkMatrix *localMatrix,
+                                                     SkFilterQuality filterQuality,
+                                                     SkSourceGammaTreatment gammaTreatment) const {
+
+    // TODO Here, the old code was checking that diffuse map and normal map are same size, that
+    //      will be addressed when diffuse maps are factored out of SkLightingShader in a future CL
+
+    SkMatrix normM;
+    if (!make_mat(fNormalMap, fNormLocalMatrix, localMatrix, &normM)) {
+        return nullptr;
+    }
+
+    bool doBicubic;
+    GrTextureParams::FilterMode normFilterMode = GrSkFilterQualityToGrFilterMode(
+            SkTMin(filterQuality, kMedium_SkFilterQuality),
+            viewM,
+            fNormLocalMatrix,
+            &doBicubic);
+    SkASSERT(!doBicubic);
+
+    // TODO: support other tile modes
+    GrTextureParams normParams(SkShader::kClamp_TileMode, normFilterMode);
+    SkAutoTUnref<GrTexture> normalTexture(GrRefCachedBitmapTexture(context,
+                                                                   fNormalMap,
+                                                                   normParams,
+                                                                   gammaTreatment));
+    if (!normalTexture) {
+        SkErrorInternals::SetError(kInternalError_SkError, "Couldn't convert bitmap to texture.");
+        return nullptr;
+    }
+
+    return sk_make_sp<NormalMapFP>(normalTexture, normM, normParams, fInvNormRotation);
+}
+
+#endif // SK_SUPPORT_GPU
+
+////////////////////////////////////////////////////////////////////////////
+
+sk_sp<SkFlattenable> NormalMapSourceImpl::CreateProc(SkReadBuffer& buf) {
+
+    SkMatrix normLocalM;
+    bool hasNormLocalM = buf.readBool();
+    if (hasNormLocalM) {
+        buf.readMatrix(&normLocalM);
+    } else {
+        normLocalM.reset();
+    }
+
+    SkBitmap normal;
+    if (!buf.readBitmap(&normal)) {
+        return nullptr;
+    }
+    normal.setImmutable();
+
+    SkVector invNormRotation = {1,0};
+    if (!buf.isVersionLT(SkReadBuffer::kLightingShaderWritesInvNormRotation)) {
+        invNormRotation = buf.readPoint();
+    }
+
+    return sk_make_sp<NormalMapSourceImpl>(normal, invNormRotation, &normLocalM);
+}
+
+void NormalMapSourceImpl::flatten(SkWriteBuffer& buf) const {
+    this->INHERITED::flatten(buf);
+
+    bool hasNormLocalM = !fNormLocalMatrix.isIdentity();
+    buf.writeBool(hasNormLocalM);
+    if (hasNormLocalM) {
+        buf.writeMatrix(fNormLocalMatrix);
+    }
+
+    buf.writeBitmap(fNormalMap);
+    buf.writePoint(fInvNormRotation);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+sk_sp<SkLightingShader::NormalSource> SkLightingShader::NormalMapSource::Make(
+        const SkBitmap &normal, const SkVector &invNormRotation, const SkMatrix *normLocalM) {
+
+    // TODO not checking normal and diffuse maps to be same size, will be addressed when diffuse
+    //      maps are factored out of SkLightingShader in a future CL
+    if (normal.isNull() || SkBitmapProcShader::BitmapIsTooBig(normal)) {
+        return nullptr;
+    }
+
+    SkASSERT(SkScalarNearlyEqual(invNormRotation.lengthSqd(), SK_Scalar1));
+
+    return sk_make_sp<NormalMapSourceImpl>(normal, invNormRotation, normLocalM);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkLightingShader::NormalMapSource)
+    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(NormalMapSourceImpl)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
+
+////////////////////////////////////////////////////////////////////////////
diff --git a/src/ports/SkGlobalInitialization_default.cpp b/src/ports/SkGlobalInitialization_default.cpp
index ea268f2..01f4c65 100644
--- a/src/ports/SkGlobalInitialization_default.cpp
+++ b/src/ports/SkGlobalInitialization_default.cpp
@@ -88,6 +88,8 @@
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkPerlinNoiseShader)
     SkGradientShader::InitializeFlattenables();
     SkLightingShader::InitializeFlattenables();
+    SkLightingShader::NormalMapSource::InitializeFlattenables();
+
 
     // PathEffect
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkArcToPathEffect)
diff --git a/tests/SerializationTest.cpp b/tests/SerializationTest.cpp
index 9e9b221..41143ec 100644
--- a/tests/SerializationTest.cpp
+++ b/tests/SerializationTest.cpp
@@ -5,13 +5,14 @@
  * found in the LICENSE file.
  */
 
-#include "SkAnnotationKeys.h"
 #include "Resources.h"
+#include "SkAnnotationKeys.h"
 #include "SkCanvas.h"
 #include "SkFixed.h"
 #include "SkFontDescriptor.h"
 #include "SkImage.h"
 #include "SkImageSource.h"
+#include "SkLightingShader.h"
 #include "SkMallocPixelRef.h"
 #include "SkOSFile.h"
 #include "SkPictureRecorder.h"
@@ -21,6 +22,7 @@
 #include "SkWriteBuffer.h"
 #include "SkValidatingReadBuffer.h"
 #include "SkXfermodeImageFilter.h"
+#include "sk_tool_utils.h"
 #include "Test.h"
 
 static const uint32_t kArraySize = 64;
@@ -182,8 +184,8 @@
     size_t bytesWritten = writer.bytesWritten();
     REPORTER_ASSERT(reporter, SkAlign4(bytesWritten) == bytesWritten);
 
+    SkASSERT(bytesWritten <= 4096);
     unsigned char dataWritten[4096];
-    SkASSERT(bytesWritten <= sizeof(dataWritten));
     writer.writeToMemory(dataWritten);
 
     // Make sure this fails when it should (test with smaller size, but still multiple of 4)
@@ -546,6 +548,43 @@
     }
 
     TestPictureTypefaceSerialization(reporter);
+
+    // Test SkLightingShader/NormalMapSource serialization
+    {
+        const int kTexSize = 2;
+
+        SkLights::Builder builder;
+
+        builder.add(SkLights::Light(SkColor3f::Make(1.0f, 1.0f, 1.0f),
+                                    SkVector3::Make(1.0f, 0.0f, 0.0f)));
+        builder.add(SkLights::Light(SkColor3f::Make(0.2f, 0.2f, 0.2f)));
+
+        sk_sp<SkLights> fLights = builder.finish();
+
+        SkBitmap diffuse = sk_tool_utils::create_checkerboard_bitmap(
+                kTexSize, kTexSize,
+                sk_tool_utils::color_to_565(0x0),
+                sk_tool_utils::color_to_565(0xFF804020),
+                8);
+
+        SkRect bitmapBounds = SkRect::MakeIWH(diffuse.width(), diffuse.height());
+
+        SkMatrix matrix;
+        SkRect r = SkRect::MakeWH(SkIntToScalar(kTexSize), SkIntToScalar(kTexSize));
+        matrix.setRectToRect(bitmapBounds, r, SkMatrix::kFill_ScaleToFit);
+
+        SkVector invNormRotation = { SkScalarSqrt(0.3f), SkScalarSqrt(0.7f) };
+        SkBitmap normals;
+        normals.allocN32Pixels(kTexSize, kTexSize);
+
+        sk_tool_utils::create_frustum_normal_map(&normals, SkIRect::MakeWH(kTexSize, kTexSize));
+        sk_sp<SkShader> lightingShader = SkLightingShader::Make(diffuse, normals, fLights,
+                invNormRotation, &matrix, &matrix);
+
+        TestFlattenableSerialization(lightingShader.get(), true, reporter);
+        // TODO test equality?
+
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////