Update SkLightingShader to support rotation

This also:
makes the SkLightingShader handle normal maps where the rects aren't aligned between the diffuse and normal maps.

adds a light aggregating class (Lights) to SkLightingShader (along with a Builder nested class).

Split out of https://codereview.chromium.org/1261433009/ (Add SkCanvas::drawLitAtlas call)

Committed: https://skia.googlesource.com/skia/+/45b59ed6e4e231814dbdb9f707b3d2a7ee50de84

Review URL: https://codereview.chromium.org/1291783003
diff --git a/gm/lightingshader.cpp b/gm/lightingshader.cpp
index 7e33504..c73ad5b 100644
--- a/gm/lightingshader.cpp
+++ b/gm/lightingshader.cpp
@@ -19,7 +19,7 @@
     sk_tool_utils::draw_checkerboard(&canvas,
                                      sk_tool_utils::color_to_565(0x0),
                                      sk_tool_utils::color_to_565(0xFF804020),
-                                     2);
+                                     8);
     return bitmap;
 }
 
@@ -58,10 +58,13 @@
     LightingShaderGM() {
         this->setBGColor(sk_tool_utils::color_to_565(0xFFCCCCCC));
 
-        fLight.fColor = SkColor3f::Make(1.0f, 1.0f, 1.0f);
-        fLight.fDirection = SkVector3::Make(0.0f, 0.0f, 1.0f);
+        SkLightingShader::Lights::Builder builder;
 
-        fAmbient = SkColor3f::Make(0.1f, 0.1f, 0.1f);
+        builder.add(SkLight(SkColor3f::Make(1.0f, 1.0f, 1.0f),
+                            SkVector3::Make(1.0f, 0.0f, 0.0f)));
+        builder.add(SkLight(SkColor3f::Make(0.2f, 0.2f, 0.2f)));
+
+        fLights.reset(builder.finish());
     }
 
 protected:
@@ -98,11 +101,16 @@
         SkMatrix matrix;
         matrix.setRectToRect(bitmapBounds, r, SkMatrix::kFill_ScaleToFit);
     
+        const SkMatrix& ctm = canvas->getTotalMatrix();
+
+        // TODO: correctly pull out the pure rotation
+        SkVector invNormRotation = { ctm[SkMatrix::kMScaleX], ctm[SkMatrix::kMSkewY] };
+
         SkAutoTUnref<SkShader> fShader(SkLightingShader::Create(
                                                         fDiffuse,
                                                         fNormalMaps[mapType],
-                                                        fLight, fAmbient,
-                                                        &matrix));
+                                                        fLights,
+                                                        invNormRotation, &matrix, &matrix));
 
         SkPaint paint;
         paint.setShader(fShader);
@@ -111,17 +119,56 @@
     }
 
     void onDraw(SkCanvas* canvas) override {
-        SkRect r = SkRect::MakeWH(SkIntToScalar(kTexSize), SkIntToScalar(kTexSize));
-        this->drawRect(canvas, r, kHemi_NormalMap);
+        SkMatrix m;
+        SkRect r;
 
-        r.offset(kGMSize - kTexSize, 0);
-        this->drawRect(canvas, r, kFrustum_NormalMap);
+        {
+            r = SkRect::MakeWH(SkIntToScalar(kTexSize), SkIntToScalar(kTexSize));
+            this->drawRect(canvas, r, kHemi_NormalMap);
 
-        r.offset(0, kGMSize - kTexSize);
-        this->drawRect(canvas, r, kTetra_NormalMap);
+            canvas->save();
+            m.setRotate(45.0f, r.centerX(), r.centerY());
+            m.postTranslate(kGMSize/2.0f - kTexSize/2.0f, 0.0f);
+            canvas->setMatrix(m);
+            this->drawRect(canvas, r, kHemi_NormalMap);
+            canvas->restore();
+        }
 
-        r.offset(kTexSize - kGMSize, 0);
-        this->drawRect(canvas, r, kHemi_NormalMap);
+        {
+            r.offset(kGMSize - kTexSize, 0);
+            this->drawRect(canvas, r, kFrustum_NormalMap);
+
+            canvas->save();
+            m.setRotate(45.0f, r.centerX(), r.centerY());
+            m.postTranslate(0.0f, kGMSize/2.0f - kTexSize/2.0f);
+            canvas->setMatrix(m);
+            this->drawRect(canvas, r, kFrustum_NormalMap);
+            canvas->restore();
+        }
+
+        {
+            r.offset(0, kGMSize - kTexSize);
+            this->drawRect(canvas, r, kTetra_NormalMap);
+
+            canvas->save();
+            m.setRotate(45.0f, r.centerX(), r.centerY());
+            m.postTranslate(-kGMSize/2.0f + kTexSize/2.0f, 0.0f);
+            canvas->setMatrix(m);
+            this->drawRect(canvas, r, kTetra_NormalMap);
+            canvas->restore();
+        }
+
+        {
+            r.offset(kTexSize - kGMSize, 0);
+            this->drawRect(canvas, r, kHemi_NormalMap);
+
+            canvas->save();
+            m.setRotate(45.0f, r.centerX(), r.centerY());
+            m.postTranslate(0.0f, -kGMSize/2.0f + kTexSize/2.0f);
+            canvas->setMatrix(m);
+            this->drawRect(canvas, r, kHemi_NormalMap);
+            canvas->restore();
+        }
     }
 
 private:
@@ -131,8 +178,7 @@
     SkBitmap                fDiffuse;
     SkBitmap                fNormalMaps[kNormalMapCount];
 
-    SkLightingShader::Light fLight;
-    SkColor3f               fAmbient;
+    SkAutoTUnref<const SkLightingShader::Lights>  fLights;
 
     typedef GM INHERITED;
 };
diff --git a/gyp/SampleApp.gyp b/gyp/SampleApp.gyp
index f2301db..dd697b7 100644
--- a/gyp/SampleApp.gyp
+++ b/gyp/SampleApp.gyp
@@ -84,7 +84,7 @@
         '../samplecode/SampleLayerMask.cpp',
         '../samplecode/SampleLayers.cpp',
         '../samplecode/SampleLCD.cpp',
-	'../samplecode/SampleLighting.cpp',
+        '../samplecode/SampleLighting.cpp',
         '../samplecode/SampleLines.cpp',
         '../samplecode/SampleLua.cpp',
         '../samplecode/SampleManyRects.cpp',
@@ -245,9 +245,9 @@
             'android_deps.gyp:Android_SampleApp',
           ],
         }],
-	[ 'skia_os == "chromeos"', {
-	  'sources!': [
-	    '../samplecode/SampleLighting.cpp',  #doesn't compile due to gpu dependencies
+        [ 'skia_os == "chromeos"', {
+          'sources!': [
+            '../samplecode/SampleLighting.cpp',  #doesn't compile due to gpu dependencies
           ],
         }],
         [ 'skia_gpu == 1', {
diff --git a/gyp/core.gypi b/gyp/core.gypi
index ef449c0..badd321 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -117,8 +117,11 @@
         '<(skia_src_path)/core/SkImageCacherator.cpp',
         '<(skia_src_path)/core/SkImageGenerator.cpp',
         '<(skia_src_path)/core/SkLayerInfo.h',
-        '<(skia_src_path)/core/SkLocalMatrixShader.cpp',
+        '<(skia_src_path)/core/SkLight.h',
+        '<(skia_src_path)/core/SkLightingShader.h',
+        '<(skia_src_path)/core/SkLightingShader.cpp',
         '<(skia_src_path)/core/SkLineClipper.cpp',
+        '<(skia_src_path)/core/SkLocalMatrixShader.cpp',
         '<(skia_src_path)/core/SkMallocPixelRef.cpp',
         '<(skia_src_path)/core/SkMask.cpp',
         '<(skia_src_path)/core/SkMaskCache.cpp',
@@ -162,6 +165,7 @@
         '<(skia_src_path)/core/SkPixelRef.cpp',
         '<(skia_src_path)/core/SkPixmap.cpp',
         '<(skia_src_path)/core/SkPoint.cpp',
+        '<(skia_src_path)/core/SkPoint3.cpp',
         '<(skia_src_path)/core/SkPtrRecorder.cpp',
         '<(skia_src_path)/core/SkQuadClipper.cpp',
         '<(skia_src_path)/core/SkQuadClipper.h',
@@ -305,6 +309,7 @@
         '<(skia_include_path)/core/SkPictureRecorder.h',
         '<(skia_include_path)/core/SkPixelRef.h',
         '<(skia_include_path)/core/SkPoint.h',
+        '<(skia_include_path)/core/SkPoint3.h',
         '<(skia_include_path)/core/SkPreConfig.h',
         '<(skia_include_path)/core/SkRasterizer.h',
         '<(skia_include_path)/core/SkRect.h',
diff --git a/gyp/effects.gypi b/gyp/effects.gypi
index 04dcd08..0539424 100644
--- a/gyp/effects.gypi
+++ b/gyp/effects.gypi
@@ -45,8 +45,6 @@
     '<(skia_src_path)/effects/SkLayerRasterizer.cpp',
     '<(skia_src_path)/effects/SkLerpXfermode.cpp',
     '<(skia_src_path)/effects/SkLightingImageFilter.cpp',
-    '<(skia_src_path)/effects/SkLightingShader.h',
-    '<(skia_src_path)/effects/SkLightingShader.cpp',
     '<(skia_src_path)/effects/SkLumaColorFilter.cpp',
     '<(skia_src_path)/effects/SkMagnifierImageFilter.cpp',
     '<(skia_src_path)/effects/SkMatrixConvolutionImageFilter.cpp',
@@ -57,7 +55,6 @@
     '<(skia_src_path)/effects/SkPerlinNoiseShader.cpp',
     '<(skia_src_path)/effects/SkPictureImageFilter.cpp',
     '<(skia_src_path)/effects/SkPixelXorXfermode.cpp',
-    '<(skia_src_path)/effects/SkPoint3.cpp',
     '<(skia_src_path)/effects/SkRectShaderImageFilter.cpp',
     '<(skia_src_path)/effects/SkTableColorFilter.cpp',
     '<(skia_src_path)/effects/SkTableMaskFilter.cpp',
@@ -114,7 +111,6 @@
     '<(skia_include_path)/effects/SkPaintFlagsDrawFilter.h',
     '<(skia_include_path)/effects/SkPerlinNoiseShader.h',
     '<(skia_include_path)/effects/SkPixelXorXfermode.h',
-    '<(skia_include_path)/effects/SkPoint3.h',
     '<(skia_include_path)/effects/SkRectShaderImageFilter.h',
     '<(skia_include_path)/effects/SkTableColorFilter.h',
     '<(skia_include_path)/effects/SkTableMaskFilter.h',
diff --git a/gyp/tools.gyp b/gyp/tools.gyp
index 17068fe..d7b2e13 100644
--- a/gyp/tools.gyp
+++ b/gyp/tools.gyp
@@ -101,6 +101,7 @@
       ],
       'include_dirs': [
         '../src/fonts',
+        '../src/core',
       ],
       'dependencies': [
         'resources',
diff --git a/include/effects/SkPoint3.h b/include/core/SkPoint3.h
similarity index 89%
rename from include/effects/SkPoint3.h
rename to include/core/SkPoint3.h
index f31f820..af24a8d 100644
--- a/include/effects/SkPoint3.h
+++ b/include/core/SkPoint3.h
@@ -91,6 +91,22 @@
         return v;
     }
 
+    /** Add v's coordinates to the point's
+    */
+    void operator+=(const SkPoint3& v) {
+        fX += v.fX;
+        fY += v.fY;
+        fZ += v.fZ;
+    }
+
+    /** Subtract v's coordinates from the point's
+    */
+    void operator-=(const SkPoint3& v) {
+        fX -= v.fX;
+        fY -= v.fY;
+        fZ -= v.fZ;
+    }
+
     /** Returns the dot product of a and b, treating them as 3D vectors
     */
     static SkScalar DotProduct(const SkPoint3& a, const SkPoint3& b) {
diff --git a/include/effects/SkLightingImageFilter.h b/include/effects/SkLightingImageFilter.h
index c481050..d0b489d 100644
--- a/include/effects/SkLightingImageFilter.h
+++ b/include/effects/SkLightingImageFilter.h
@@ -12,7 +12,7 @@
 #include "SkColor.h"
 
 
-class SkLight;
+class SkImageFilterLight;
 struct SkPoint3;
 
 class SK_API SkLightingImageFilter : public SkImageFilter {
@@ -42,17 +42,17 @@
     SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
 
 protected:
-    SkLightingImageFilter(SkLight* light,
+    SkLightingImageFilter(SkImageFilterLight* light,
                           SkScalar surfaceScale,
                           SkImageFilter* input,
                           const CropRect* cropRect);
     void flatten(SkWriteBuffer&) const override;
-    const SkLight* light() const { return fLight.get(); }
+    const SkImageFilterLight* light() const { return fLight.get(); }
     SkScalar surfaceScale() const { return fSurfaceScale; }
 
 private:
     typedef SkImageFilter INHERITED;
-    SkAutoTUnref<SkLight> fLight;
+    SkAutoTUnref<SkImageFilterLight> fLight;
     SkScalar fSurfaceScale;
 };
 
diff --git a/samplecode/SampleLighting.cpp b/samplecode/SampleLighting.cpp
index 5b4f2e0..4838cf8 100755
--- a/samplecode/SampleLighting.cpp
+++ b/samplecode/SampleLighting.cpp
@@ -11,6 +11,21 @@
 #include "SkCanvas.h"
 #include "SkImageDecoder.h"
 #include "SkLightingShader.h"
+#include "SkPoint3.h"
+
+static const SkLightingShader::Lights* create_lights(SkScalar angle, SkScalar blue) {
+
+    const SkVector3 dir = SkVector3::Make(SkScalarSin(angle)*SkScalarSin(SK_ScalarPI*0.25f),
+                                          SkScalarCos(angle)*SkScalarSin(SK_ScalarPI*0.25f),
+                                          SkScalarCos(SK_ScalarPI*0.25f));
+
+    SkLightingShader::Lights::Builder builder;
+
+    builder.add(SkLight(SkColor3f::Make(1.0f, 1.0f, blue), dir));
+    builder.add(SkLight(SkColor3f::Make(0.1f, 0.1f, 0.1f)));
+
+    return builder.finish();
+}
 
 ////////////////////////////////////////////////////////////////////////////
 
@@ -21,7 +36,6 @@
     SkBitmap               fNormalBitmap;
     SkScalar               fLightAngle;
     SkScalar               fColorFactor;
-    SkColor3f              fAmbientColor;
 
     LightingView() {
         SkString diffusePath = GetResourcePath("brickwork-texture.jpg");
@@ -32,16 +46,11 @@
         fLightAngle = 0.0f;
         fColorFactor = 0.0f;
 
-        SkLightingShader::Light light;
-        light.fColor = SkColor3f::Make(1.0f, 1.0f, 1.0f);
-        light.fDirection.fX = SkScalarSin(fLightAngle)*SkScalarSin(SK_ScalarPI*0.25f);
-        light.fDirection.fY = SkScalarCos(fLightAngle)*SkScalarSin(SK_ScalarPI*0.25f);
-        light.fDirection.fZ = SkScalarCos(SK_ScalarPI*0.25f);
-
-        fAmbientColor = SkColor3f::Make(0.1f, 0.1f, 0.1f);
+        SkAutoTUnref<const SkLightingShader::Lights> lights(create_lights(fLightAngle, 1.0f));
 
         fShader.reset(SkLightingShader::Create(fDiffuseBitmap, fNormalBitmap,
-                                               light, fAmbientColor, nullptr));
+                                               lights, SkVector::Make(1.0f, 0.0f),
+                                               nullptr, nullptr));
     }
 
     virtual ~LightingView() {}
@@ -63,14 +72,12 @@
             fColorFactor = 0.0f;
         }
 
-        SkLightingShader::Light light;
-        light.fColor = SkColor3f::Make(1.0f, 1.0f, fColorFactor);
-        light.fDirection.fX = SkScalarSin(fLightAngle)*SkScalarSin(SK_ScalarPI*0.25f);
-        light.fDirection.fY = SkScalarCos(fLightAngle)*SkScalarSin(SK_ScalarPI*0.25f);
-        light.fDirection.fZ = SkScalarCos(SK_ScalarPI*0.25f);
+        SkAutoTUnref<const SkLightingShader::Lights> lights(create_lights(fLightAngle,
+                                                                          fColorFactor));
 
         fShader.reset(SkLightingShader::Create(fDiffuseBitmap, fNormalBitmap,
-                                               light, fAmbientColor, nullptr));
+                                               lights, SkVector::Make(1.0f, 0.0f),
+                                               nullptr, nullptr));
 
         SkPaint paint;
         paint.setShader(fShader);
diff --git a/src/core/SkLight.h b/src/core/SkLight.h
new file mode 100644
index 0000000..4a6e149
--- /dev/null
+++ b/src/core/SkLight.h
@@ -0,0 +1,56 @@
+
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkLight_DEFINED
+#define SkLight_DEFINED
+
+#include "SkPoint3.h"
+
+class SK_API SkLight {
+public:
+    enum LightType {
+        kAmbient_LightType,       // only 'fColor' is used
+        kDirectional_LightType
+    };
+
+    SkLight() : fType(kAmbient_LightType) {
+        fColor.set(0.0f, 0.0f, 0.0f);
+        fDirection.set(0.0f, 0.0f, 1.0f);
+    }
+
+    SkLight(const SkColor3f& color)
+        : fType(kAmbient_LightType)
+        , fColor(color) {
+        fDirection.set(0.0f, 0.0f, 1.0f);
+    }
+
+    SkLight(const SkColor3f& color, const SkVector3& dir)
+        : fType(kDirectional_LightType)
+        , fColor(color)
+        , fDirection(dir) {
+        if (!fDirection.normalize()) {
+            fDirection.set(0.0f, 0.0f, 1.0f);
+        }
+    }
+
+    LightType type() const { return fType; }
+    const SkColor3f& color() const { return fColor; }
+    const SkVector3& dir() const { 
+        SkASSERT(kAmbient_LightType != fType);
+        return fDirection; 
+    }
+
+private:
+    LightType   fType;
+    SkColor3f   fColor;           // linear (unpremul) color. Range is 0..1 in each channel.
+    SkVector3   fDirection;       // direction towards the light (+Z is out of the screen).
+                                  // If degenerate, it will be replaced with (0, 0, 1).
+};
+
+
+#endif
diff --git a/src/core/SkLightingShader.cpp b/src/core/SkLightingShader.cpp
new file mode 100644
index 0000000..ce5c0dd
--- /dev/null
+++ b/src/core/SkLightingShader.cpp
@@ -0,0 +1,738 @@
+
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBitmapProcState.h"
+#include "SkColor.h"
+#include "SkEmptyShader.h"
+#include "SkErrorInternals.h"
+#include "SkLightingShader.h"
+#include "SkMathPriv.h"
+#include "SkPoint3.h"
+#include "SkReadBuffer.h"
+#include "SkWriteBuffer.h"
+
+////////////////////////////////////////////////////////////////////////////
+
+/*
+   SkLightingShader TODOs:
+        support other than clamp mode
+        allow 'diffuse' & 'normal' to be of different dimensions?
+        support different light types
+        support multiple lights
+        enforce normal map is 4 channel
+        use SkImages instead if SkBitmaps
+
+    To Test:
+        non-opaque diffuse textures
+        A8 diffuse textures
+        down & upsampled draws
+*/
+
+
+
+/** \class SkLightingShaderImpl
+    This subclass of shader applies lighting.
+*/
+class SK_API SkLightingShaderImpl : public SkShader {
+public:
+
+    /** 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
+    */
+    SkLightingShaderImpl(const SkBitmap& diffuse, const SkBitmap& normal,
+                         const SkLightingShader::Lights* lights,
+                         const SkVector& invNormRotation,
+                         const SkMatrix* diffLocalM, const SkMatrix* normLocalM) 
+        : INHERITED(diffLocalM)
+        , fDiffuseMap(diffuse)
+        , fNormalMap(normal)
+        , fLights(SkRef(lights))
+        , fInvNormRotation(invNormRotation) {
+
+        if (normLocalM) {
+            fNormLocalMatrix = *normLocalM;
+        } else {
+            fNormLocalMatrix.reset();
+        }
+        // Pre-cache so future calls to fNormLocalMatrix.getType() are threadsafe.
+        (void)fNormLocalMatrix.getType();
+
+    }
+
+    bool isOpaque() const override;
+
+    bool asFragmentProcessor(GrContext*, const SkPaint& paint, const SkMatrix& viewM,
+                             const SkMatrix* localMatrix, GrColor* color,
+                             GrProcessorDataManager*, GrFragmentProcessor** fp) const override;
+
+    size_t contextSize() const override;
+
+    class LightingShaderContext : public SkShader::Context {
+    public:
+        // The context takes ownership of the states. It will call their destructors
+        // but will NOT free the memory.
+        LightingShaderContext(const SkLightingShaderImpl&, const ContextRec&,
+                              SkBitmapProcState* diffuseState, SkBitmapProcState* normalState);
+        ~LightingShaderContext() override;
+
+        void shadeSpan(int x, int y, SkPMColor[], int count) override;
+
+        uint32_t getFlags() const override { return fFlags; }
+
+    private:
+        SkBitmapProcState* fDiffuseState;
+        SkBitmapProcState* fNormalState;
+        uint32_t           fFlags;
+
+        typedef SkShader::Context INHERITED;
+    };
+
+    SK_TO_STRING_OVERRIDE()
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingShaderImpl)
+
+protected:
+    void flatten(SkWriteBuffer&) const override;
+    Context* onCreateContext(const ContextRec&, void*) const override;
+    bool computeNormTotalInverse(const ContextRec& rec, SkMatrix* normTotalInverse) const;
+
+private:
+    SkBitmap  fDiffuseMap;
+    SkBitmap  fNormalMap;
+
+    SkAutoTUnref<const SkLightingShader::Lights>   fLights;
+
+    SkMatrix  fNormLocalMatrix;
+    SkVector  fInvNormRotation;
+
+    friend class SkLightingShader;
+
+    typedef SkShader INHERITED;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+
+#include "GrCoordTransform.h"
+#include "GrFragmentProcessor.h"
+#include "GrTextureAccess.h"
+#include "gl/GrGLProcessor.h"
+#include "gl/builders/GrGLProgramBuilder.h"
+#include "SkGr.h"
+
+class LightingFP : public GrFragmentProcessor {
+public:
+    LightingFP(GrProcessorDataManager* pdm, GrTexture* diffuse, GrTexture* normal,
+               const SkMatrix& diffMatrix, const SkMatrix& normMatrix,
+               const GrTextureParams& diffParams, const GrTextureParams& normParams,
+               const SkLightingShader::Lights* lights, const SkVector& invNormRotation)
+        : fDiffDeviceTransform(kLocal_GrCoordSet, diffMatrix, diffuse, diffParams.filterMode())
+        , fNormDeviceTransform(kLocal_GrCoordSet, normMatrix, normal, normParams.filterMode())
+        , fDiffuseTextureAccess(diffuse, diffParams)
+        , fNormalTextureAccess(normal, normParams)
+        , fInvNormRotation(invNormRotation) {
+        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);
+        for (int i = 0; i < lights->numLights(); ++i) {
+            if (SkLight::kAmbient_LightType == lights->light(i).type()) {
+                fAmbientColor += lights->light(i).color();
+            } else {
+                // TODO: handle more than one of these
+                fLightColor = lights->light(i).color();
+                fLightDir = lights->light(i).dir();
+            }
+        }
+
+        this->initClassID<LightingFP>();
+    }
+
+    class LightingGLFP : public GrGLFragmentProcessor {
+    public:
+        LightingGLFP() {
+            fLightDir.fX = 10000.0f;
+            fLightColor.fX = 0.0f;
+            fAmbientColor.fX = 0.0f;
+            fInvNormRotation.fX = 0.0f;
+        }
+
+        void emitCode(EmitArgs& args) override {
+
+            GrGLFragmentBuilder* fpb = args.fBuilder->getFragmentShaderBuilder();
+
+            // add uniforms
+            const char* lightDirUniName = NULL;
+            fLightDirUni = args.fBuilder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
+                                                     kVec3f_GrSLType, kDefault_GrSLPrecision,
+                                                     "LightDir", &lightDirUniName);
+
+            const char* lightColorUniName = NULL;
+            fLightColorUni = args.fBuilder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
+                                                       kVec3f_GrSLType, kDefault_GrSLPrecision,
+                                                       "LightColor", &lightColorUniName);
+
+            const char* ambientColorUniName = NULL;
+            fAmbientColorUni = args.fBuilder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
+                                                         kVec3f_GrSLType, kDefault_GrSLPrecision,
+                                                         "AmbientColor", &ambientColorUniName);
+
+            const char* xformUniName = NULL;
+            fXformUni = args.fBuilder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
+                                                  kVec2f_GrSLType, kDefault_GrSLPrecision,
+                                                  "Xform", &xformUniName);
+
+            fpb->codeAppend("vec4 diffuseColor = ");
+            fpb->appendTextureLookupAndModulate(args.fInputColor, args.fSamplers[0], 
+                                                args.fCoords[0].c_str(), 
+                                                args.fCoords[0].getType());
+            fpb->codeAppend(";");
+
+            fpb->codeAppend("vec4 normalColor = ");
+            fpb->appendTextureLookup(args.fSamplers[1],
+                                     args.fCoords[1].c_str(), 
+                                     args.fCoords[1].getType());
+            fpb->codeAppend(";");
+
+            fpb->codeAppend("vec3 normal = normalColor.rgb - vec3(0.5);");
+
+            fpb->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!
+            fpb->codeAppend("normal = normalize(m*normal);");
+
+            fpb->codeAppendf("float NdotL = clamp(dot(normal, %s), 0.0, 1.0);", lightDirUniName);
+            // diffuse light
+            fpb->codeAppendf("vec3 result = %s*diffuseColor.rgb*NdotL;", lightColorUniName);
+            // ambient light
+            fpb->codeAppendf("result += %s;", ambientColorUniName);
+            fpb->codeAppendf("%s = vec4(result.rgb, diffuseColor.a);", args.fOutputColor);
+        }
+
+        static void GenKey(const GrProcessor& proc, const GrGLSLCaps&,
+                           GrProcessorKeyBuilder* b) {
+//            const LightingFP& lightingFP = proc.cast<LightingFP>();
+            // only one shader generated currently
+            b->add32(0x0);
+        }
+
+    protected:
+        void onSetData(const GrGLProgramDataManager& pdman, const GrProcessor& proc) override {
+            const LightingFP& lightingFP = proc.cast<LightingFP>();
+
+            const SkVector3& lightDir = lightingFP.lightDir();
+            if (lightDir != fLightDir) {
+                pdman.set3fv(fLightDirUni, 1, &lightDir.fX);
+                fLightDir = lightDir;
+            }
+
+            const SkColor3f& lightColor = lightingFP.lightColor();
+            if (lightColor != fLightColor) {
+                pdman.set3fv(fLightColorUni, 1, &lightColor.fX);
+                fLightColor = lightColor;
+            }
+
+            const SkColor3f& ambientColor = lightingFP.ambientColor();
+            if (ambientColor != fAmbientColor) {
+                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:
+        SkVector3 fLightDir;
+        GrGLProgramDataManager::UniformHandle fLightDirUni;
+
+        SkColor3f fLightColor;
+        GrGLProgramDataManager::UniformHandle fLightColorUni;
+
+        SkColor3f fAmbientColor;
+        GrGLProgramDataManager::UniformHandle fAmbientColorUni;
+
+        SkVector fInvNormRotation;
+        GrGLProgramDataManager::UniformHandle fXformUni;
+    };
+
+    void onGetGLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override {
+        LightingGLFP::GenKey(*this, caps, b);
+    }
+
+    const char* name() const override { return "LightingFP"; }
+
+    void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
+        inout->mulByUnknownFourComponents();
+    }
+
+    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:
+    GrGLFragmentProcessor* onCreateGLInstance() const override { return SkNEW(LightingGLFP); }
+
+    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;
+    }
+
+    GrCoordTransform fDiffDeviceTransform;
+    GrCoordTransform fNormDeviceTransform;
+    GrTextureAccess  fDiffuseTextureAccess;
+    GrTextureAccess  fNormalTextureAccess;
+    SkVector3        fLightDir;
+    SkColor3f        fLightColor;
+    SkColor3f        fAmbientColor;
+
+    SkVector         fInvNormRotation;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+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;
+}
+
+bool SkLightingShaderImpl::asFragmentProcessor(GrContext* context, const SkPaint& paint, 
+                                               const SkMatrix& viewM, const SkMatrix* localMatrix, 
+                                               GrColor* color, GrProcessorDataManager* pdm,
+                                               GrFragmentProcessor** fp) const {
+    // we assume diffuse and normal maps have same width and height
+    // TODO: support different sizes
+    SkASSERT(fDiffuseMap.width() == fNormalMap.width() &&
+             fDiffuseMap.height() == fNormalMap.height());
+    SkMatrix diffM, normM;
+    
+    if (!make_mat(fDiffuseMap, this->getLocalMatrix(), localMatrix, &diffM)) {
+        return false;    
+    }
+
+    if (!make_mat(fNormalMap, fNormLocalMatrix, localMatrix, &normM)) {
+        return false;
+    }
+
+    bool doBicubic;
+    GrTextureParams::FilterMode diffFilterMode = GrSkFilterQualityToGrFilterMode(
+                                        SkTMin(paint.getFilterQuality(), kMedium_SkFilterQuality), 
+                                        viewM,
+                                        this->getLocalMatrix(),
+                                        &doBicubic); 
+    SkASSERT(!doBicubic);
+
+    GrTextureParams::FilterMode normFilterMode = GrSkFilterQualityToGrFilterMode(
+                                        SkTMin(paint.getFilterQuality(), kMedium_SkFilterQuality), 
+                                        viewM,
+                                        fNormLocalMatrix,
+                                        &doBicubic); 
+    SkASSERT(!doBicubic);
+
+    // TODO: support other tile modes
+    GrTextureParams diffParams(kClamp_TileMode, diffFilterMode);
+    SkAutoTUnref<GrTexture> diffuseTexture(GrRefCachedBitmapTexture(context,
+                                                                    fDiffuseMap, &diffParams));
+    if (!diffuseTexture) {
+        SkErrorInternals::SetError(kInternalError_SkError,
+            "Couldn't convert bitmap to texture.");
+        return false;
+    }
+
+    GrTextureParams normParams(kClamp_TileMode, normFilterMode);
+    SkAutoTUnref<GrTexture> normalTexture(GrRefCachedBitmapTexture(context,
+                                                                   fNormalMap, &normParams));
+    if (!normalTexture) {
+        SkErrorInternals::SetError(kInternalError_SkError,
+            "Couldn't convert bitmap to texture.");
+        return false;
+    }
+
+
+    *fp = SkNEW_ARGS(LightingFP, (pdm, diffuseTexture, normalTexture,
+                                  diffM, normM, diffParams, normParams, fLights,
+                                  fInvNormRotation));
+
+    *color = GrColorPackA4(paint.getAlpha());
+    return true;
+}
+#else
+
+bool SkLightingShaderImpl::asFragmentProcessor(GrContext* context, const SkPaint& paint, 
+                                               const SkMatrix& viewM, const SkMatrix* localMatrix, 
+                                               GrColor* color, GrProcessorDataManager*,
+                                               GrFragmentProcessor** fp) const {
+    SkDEBUGFAIL("Should not call in GPU-less build");
+    return false;
+}
+
+#endif
+
+////////////////////////////////////////////////////////////////////////////
+
+bool SkLightingShaderImpl::isOpaque() const {
+    return fDiffuseMap.isOpaque();
+}
+
+size_t SkLightingShaderImpl::contextSize() const {
+    return 2 * sizeof(SkBitmapProcState) + sizeof(LightingShaderContext);
+}
+
+SkLightingShaderImpl::LightingShaderContext::LightingShaderContext(const SkLightingShaderImpl& shader,
+                                                                   const ContextRec& rec,
+                                                                   SkBitmapProcState* diffuseState,
+                                                                   SkBitmapProcState* normalState)
+    : INHERITED(shader, rec)
+    , fDiffuseState(diffuseState)
+    , fNormalState(normalState)
+{
+    const SkPixmap& pixmap = fDiffuseState->fPixmap;
+    bool isOpaque = pixmap.isOpaque();
+
+    // update fFlags
+    uint32_t flags = 0;
+    if (isOpaque && (255 == this->getPaintAlpha())) {
+        flags |= kOpaqueAlpha_Flag;
+    }
+
+    fFlags = flags;
+}
+
+SkLightingShaderImpl::LightingShaderContext::~LightingShaderContext() {
+    // The bitmap proc states have been created outside of the context on memory that will be freed
+    // elsewhere. Call the destructors but leave the freeing of the memory to the caller.
+    fDiffuseState->~SkBitmapProcState();
+    fNormalState->~SkBitmapProcState();
+}
+
+static inline SkPMColor convert(SkColor3f color, U8CPU a) {
+    if (color.fX <= 0.0f) {
+        color.fX = 0.0f;
+    } else if (color.fX >= 255.0f) {
+        color.fX = 255.0f;
+    } 
+
+    if (color.fY <= 0.0f) {
+        color.fY = 0.0f;
+    } else if (color.fY >= 255.0f) {
+        color.fY = 255.0f;
+    } 
+
+    if (color.fZ <= 0.0f) {
+        color.fZ = 0.0f;
+    } else if (color.fZ >= 255.0f) {
+        color.fZ = 255.0f;
+    } 
+
+    return SkPreMultiplyARGB(a, (int) color.fX,  (int) color.fY, (int) color.fZ);
+}
+
+// larger is better (fewer times we have to loop), but we shouldn't
+// take up too much stack-space (each one here costs 16 bytes)
+#define TMP_COUNT     16
+
+void SkLightingShaderImpl::LightingShaderContext::shadeSpan(int x, int y,
+                                                            SkPMColor result[], int count) {
+    const SkLightingShaderImpl& lightShader = static_cast<const SkLightingShaderImpl&>(fShader);
+
+    uint32_t  tmpColor[TMP_COUNT], tmpNormal[TMP_COUNT];
+    SkPMColor tmpColor2[2*TMP_COUNT], tmpNormal2[2*TMP_COUNT];
+
+    SkBitmapProcState::MatrixProc   diffMProc = fDiffuseState->getMatrixProc();
+    SkBitmapProcState::SampleProc32 diffSProc = fDiffuseState->getSampleProc32();
+
+    SkBitmapProcState::MatrixProc   normalMProc = fNormalState->getMatrixProc();
+    SkBitmapProcState::SampleProc32 normalSProc = fNormalState->getSampleProc32();
+
+    int diffMax = fDiffuseState->maxCountForBufferSize(sizeof(tmpColor[0]) * TMP_COUNT);
+    int normMax = fNormalState->maxCountForBufferSize(sizeof(tmpNormal[0]) * TMP_COUNT);
+    int max = SkTMin(diffMax, normMax);
+
+    SkASSERT(fDiffuseState->fPixmap.addr());
+    SkASSERT(fNormalState->fPixmap.addr());
+
+    SkPoint3 norm, xformedNorm;
+
+    do {
+        int n = count;
+        if (n > max) {
+            n = max;
+        }
+
+        diffMProc(*fDiffuseState, tmpColor, n, x, y);
+        diffSProc(*fDiffuseState, tmpColor, n, tmpColor2);
+
+        normalMProc(*fNormalState, tmpNormal, n, x, y);
+        normalSProc(*fNormalState, tmpNormal, n, tmpNormal2);
+
+        for (int i = 0; i < n; ++i) {
+            SkASSERT(0xFF == SkColorGetA(tmpNormal2[i]));  // opaque -> unpremul
+            norm.set(SkIntToScalar(SkGetPackedR32(tmpNormal2[i]))-127.0f,
+                     SkIntToScalar(SkGetPackedG32(tmpNormal2[i]))-127.0f,
+                     SkIntToScalar(SkGetPackedB32(tmpNormal2[i]))-127.0f);
+            norm.normalize();
+
+            xformedNorm.fX = lightShader.fInvNormRotation.fX * norm.fX +
+                             lightShader.fInvNormRotation.fY * norm.fY;
+            xformedNorm.fY = lightShader.fInvNormRotation.fX * norm.fX - 
+                             lightShader.fInvNormRotation.fY * norm.fY;
+            xformedNorm.fZ = norm.fZ;
+
+            SkColor diffColor = SkUnPreMultiply::PMColorToColor(tmpColor2[i]);
+
+            SkColor3f accum = SkColor3f::Make(0.0f, 0.0f, 0.0f);
+            // This is all done in linear unpremul color space (each component 0..255.0f though)
+            for (int l = 0; l < lightShader.fLights->numLights(); ++l) {
+                const SkLight& light = lightShader.fLights->light(l);
+
+                if (SkLight::kAmbient_LightType == light.type()) {
+                    accum += light.color().makeScale(255.0f);
+                } else {
+                    SkScalar NdotL = xformedNorm.dot(light.dir());
+                    if (NdotL < 0.0f) {
+                        NdotL = 0.0f;
+                    }
+
+                    accum.fX += light.color().fX * SkColorGetR(diffColor) * NdotL;
+                    accum.fY += light.color().fY * SkColorGetG(diffColor) * NdotL;
+                    accum.fZ += light.color().fZ * SkColorGetB(diffColor) * NdotL;
+                }
+            }
+
+            result[i] = convert(accum, SkColorGetA(diffColor));
+        }
+
+        result += n;
+        x += n;
+        count -= n;
+    } while (count > 0);
+}
+
+////////////////////////////////////////////////////////////////////////////
+
+#ifndef SK_IGNORE_TO_STRING
+void SkLightingShaderImpl::toString(SkString* str) const {
+    str->appendf("LightingShader: ()");
+}
+#endif
+
+SkFlattenable* SkLightingShaderImpl::CreateProc(SkReadBuffer& buf) {
+    SkMatrix diffLocalM;
+    bool hasDiffLocalM = buf.readBool();
+    if (hasDiffLocalM) {
+        buf.readMatrix(&diffLocalM);
+    } else {
+        diffLocalM.reset();
+    }
+
+    SkMatrix normLocalM;
+    bool hasNormLocalM = buf.readBool();
+    if (hasNormLocalM) {
+        buf.readMatrix(&normLocalM);
+    } else {
+        normLocalM.reset();
+    }
+
+    SkBitmap diffuse;
+    if (!buf.readBitmap(&diffuse)) {
+        return NULL;
+    }
+    diffuse.setImmutable();
+
+    SkBitmap normal;
+    if (!buf.readBitmap(&normal)) {
+        return NULL;
+    }
+    normal.setImmutable();
+
+    int numLights = buf.readInt();
+
+    SkLightingShader::Lights::Builder builder;
+
+    for (int l = 0; l < numLights; ++l) {
+        bool isAmbient = buf.readBool();
+
+        SkColor3f color;
+        if (!buf.readScalarArray(&color.fX, 3)) {
+            return NULL;
+        }
+
+        if (isAmbient) {
+            builder.add(SkLight(color));
+        } else {
+            SkVector3 dir;
+            if (!buf.readScalarArray(&dir.fX, 3)) {
+                return NULL;
+            }
+            builder.add(SkLight(color, dir));        
+        }
+    }
+
+    SkAutoTUnref<const SkLightingShader::Lights> lights(builder.finish());
+
+    return SkNEW_ARGS(SkLightingShaderImpl, (diffuse, normal, lights,
+                                             SkVector::Make(1.0f, 0.0f),
+                                             &diffLocalM, &normLocalM));
+}
+
+void SkLightingShaderImpl::flatten(SkWriteBuffer& buf) const {
+    this->INHERITED::flatten(buf);
+
+    bool hasNormLocalM = !fNormLocalMatrix.isIdentity();
+    buf.writeBool(hasNormLocalM);
+    if (hasNormLocalM) {
+        buf.writeMatrix(fNormLocalMatrix);
+    }
+
+    buf.writeBitmap(fDiffuseMap);
+    buf.writeBitmap(fNormalMap);
+
+    buf.writeInt(fLights->numLights());
+    for (int l = 0; l < fLights->numLights(); ++l) {
+        const SkLight& light = fLights->light(l);
+
+        bool isAmbient = SkLight::kAmbient_LightType == light.type();
+
+        buf.writeBool(isAmbient);
+        buf.writeScalarArray(&light.color().fX, 3);
+        if (!isAmbient) {
+            buf.writeScalarArray(&light.dir().fX, 3);
+        }
+    }
+}
+
+bool SkLightingShaderImpl::computeNormTotalInverse(const ContextRec& rec,
+                                                   SkMatrix* normTotalInverse) const {
+    SkMatrix total;
+    total.setConcat(*rec.fMatrix, fNormLocalMatrix);
+
+    const SkMatrix* m = &total;
+    if (rec.fLocalMatrix) {
+        total.setConcat(*m, *rec.fLocalMatrix);
+        m = &total;
+    }
+    return m->invert(normTotalInverse);
+}
+
+SkShader::Context* SkLightingShaderImpl::onCreateContext(const ContextRec& rec,
+                                                         void* storage) const {
+
+    SkMatrix diffTotalInv;
+    // computeTotalInverse was called in SkShader::createContext so we know it will succeed
+    SkAssertResult(this->computeTotalInverse(rec, &diffTotalInv));
+
+    SkMatrix normTotalInv;
+    if (!this->computeNormTotalInverse(rec, &normTotalInv)) {
+        return NULL;
+    }
+
+    void* diffuseStateStorage = (char*)storage + sizeof(LightingShaderContext);
+    SkBitmapProcState* diffuseState = SkNEW_PLACEMENT(diffuseStateStorage, SkBitmapProcState);
+    SkASSERT(diffuseState);
+
+    diffuseState->fTileModeX = SkShader::kClamp_TileMode;
+    diffuseState->fTileModeY = SkShader::kClamp_TileMode;
+    diffuseState->fOrigBitmap = fDiffuseMap;
+    if (!diffuseState->chooseProcs(diffTotalInv, *rec.fPaint)) {
+        diffuseState->~SkBitmapProcState();
+        return NULL;
+    }
+
+    void* normalStateStorage = (char*)storage + sizeof(LightingShaderContext) + sizeof(SkBitmapProcState);
+    SkBitmapProcState* normalState = SkNEW_PLACEMENT(normalStateStorage, SkBitmapProcState);
+    SkASSERT(normalState);
+
+    normalState->fTileModeX = SkShader::kClamp_TileMode;
+    normalState->fTileModeY = SkShader::kClamp_TileMode;
+    normalState->fOrigBitmap = fNormalMap;
+    if (!normalState->chooseProcs(normTotalInv, *rec.fPaint)) {
+        diffuseState->~SkBitmapProcState();
+        normalState->~SkBitmapProcState();
+        return NULL;
+    }
+
+    return SkNEW_PLACEMENT_ARGS(storage, LightingShaderContext, (*this, rec,
+                                                                 diffuseState, normalState));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+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;
+}
+
+SkShader* SkLightingShader::Create(const SkBitmap& diffuse, const SkBitmap& normal,
+                                   const Lights* 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() ||
+        diffuse.height() != normal.height()) {
+        return nullptr;
+    }
+
+    SkASSERT(SkScalarNearlyEqual(invNormRotation.lengthSqd(), SK_Scalar1));
+
+    return SkNEW_ARGS(SkLightingShaderImpl, (diffuse, normal, lights,
+                                             invNormRotation, diffLocalM, normLocalM));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkLightingShader)
+    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingShaderImpl)
+SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
+
+///////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/SkLightingShader.h b/src/core/SkLightingShader.h
new file mode 100644
index 0000000..499e358
--- /dev/null
+++ b/src/core/SkLightingShader.h
@@ -0,0 +1,101 @@
+
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkLightingShader_DEFINED
+#define SkLightingShader_DEFINED
+
+#include "SkFlattenable.h"
+#include "SkLight.h"
+#include "SkShader.h"
+#include "SkTDArray.h"
+
+class SkBitmap;
+class SkMatrix;
+
+class SK_API SkLightingShader {
+public:
+    class Lights  : public SkRefCnt {
+    public:
+        class Builder {
+        public:
+            Builder(const SkLight lights[], int numLights)
+                : fLights(SkNEW_ARGS(Lights, (lights, numLights))) {
+            }
+
+            Builder() : fLights(SkNEW(Lights)) { }
+    
+            // TODO: limit the number of lights here or just ignore those
+            // above some maximum?
+            void add(const SkLight& light) {
+                if (fLights) {
+                    *fLights->fLights.push() = light;
+                }
+            }
+
+            const Lights* finish() {
+                return fLights.detach();
+            }
+
+        private:
+            SkAutoTUnref<Lights> fLights;
+        };
+
+        int numLights() const {
+            return fLights.count();
+        }
+
+        const SkLight& light(int index) const {
+            return fLights[index];
+        }
+
+    private:
+        Lights() {}
+        Lights(const SkLight lights[], int numLights) : fLights(lights, numLights) {}
+
+        SkTDArray<SkLight> fLights;
+
+        typedef SkRefCnt INHERITED;
+    };
+
+    /** Returns a shader that lights the diffuse and normal maps with a single light.
+
+        It returns a shader with a reference count of 1.
+        The caller should decrement the shader's reference count when done with the shader.
+        It is an error for count to be < 2.
+        @param  diffuse     the diffuse bitmap
+        @param  normal      the normal map
+        @param  light       the light applied to the normal map
+        @param  ambient     the linear (unpremul) ambient light color. Range is 0..1/channel.
+        @param  localMatrix the matrix mapping the textures to the dest rect 
+
+        NULL will be returned if:
+            either 'diffuse' or 'normal' are empty
+            either 'diffuse' or 'normal' are too big (> 65535 on a side)
+            'diffuse' and 'normal' aren't the same size
+
+        The lighting equation is currently:
+            result = LightColor * DiffuseColor * (Normal * LightDir) + AmbientColor
+
+        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).
+    */
+    static SkShader* Create(const SkBitmap& diffuse, const SkBitmap& normal,
+                            const Lights* lights, const SkVector& invNormRotation,
+                            const SkMatrix* diffLocalMatrix, const SkMatrix* normLocalMatrix);
+
+    SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
+};
+
+#endif
diff --git a/src/effects/SkPoint3.cpp b/src/core/SkPoint3.cpp
similarity index 100%
rename from src/effects/SkPoint3.cpp
rename to src/core/SkPoint3.cpp
diff --git a/src/effects/SkLightingImageFilter.cpp b/src/effects/SkLightingImageFilter.cpp
index 60904c7..c63b2eb 100644
--- a/src/effects/SkLightingImageFilter.cpp
+++ b/src/effects/SkLightingImageFilter.cpp
@@ -181,9 +181,12 @@
                          surfaceScale);
 }
 
-template <class LightingType, class LightType> void lightBitmap(
-        const LightingType& lightingType, const SkLight* light, const SkBitmap& src, SkBitmap* dst,
-        SkScalar surfaceScale, const SkIRect& bounds) {
+template <class LightingType, class LightType> void lightBitmap(const LightingType& lightingType,
+                                                                const SkImageFilterLight* light,
+                                                                const SkBitmap& src,
+                                                                SkBitmap* dst,
+                                                                SkScalar surfaceScale,
+                                                                const SkIRect& bounds) {
     SkASSERT(dst->width() == bounds.width() && dst->height() == bounds.height());
     const LightType* l = static_cast<const LightType*>(light);
     int left = bounds.left(), right = bounds.right();
@@ -308,7 +311,7 @@
 
 class SkLightingImageFilterInternal : public SkLightingImageFilter {
 protected:
-    SkLightingImageFilterInternal(SkLight* light,
+    SkLightingImageFilterInternal(SkImageFilterLight* light,
                                   SkScalar surfaceScale,
                                   SkImageFilter* input,
                                   const CropRect* cropRect)
@@ -431,7 +434,8 @@
 
 class SkDiffuseLightingImageFilter : public SkLightingImageFilterInternal {
 public:
-    static SkImageFilter* Create(SkLight* light, SkScalar surfaceScale, SkScalar kd, SkImageFilter*,
+    static SkImageFilter* Create(SkImageFilterLight* light, SkScalar surfaceScale,
+                                 SkScalar kd, SkImageFilter*,
                                  const CropRect*);
 
     SK_TO_STRING_OVERRIDE()
@@ -439,7 +443,7 @@
     SkScalar kd() const { return fKD; }
 
 protected:
-    SkDiffuseLightingImageFilter(SkLight* light, SkScalar surfaceScale,
+    SkDiffuseLightingImageFilter(SkImageFilterLight* light, SkScalar surfaceScale,
                                  SkScalar kd, SkImageFilter* input, const CropRect* cropRect);
     void flatten(SkWriteBuffer& buffer) const override;
     bool onFilterImage(Proxy*, const SkBitmap& src, const Context&,
@@ -457,7 +461,7 @@
 
 class SkSpecularLightingImageFilter : public SkLightingImageFilterInternal {
 public:
-    static SkImageFilter* Create(SkLight* light, SkScalar surfaceScale,
+    static SkImageFilter* Create(SkImageFilterLight* light, SkScalar surfaceScale,
                                  SkScalar ks, SkScalar shininess, SkImageFilter*, const CropRect*);
 
     SK_TO_STRING_OVERRIDE()
@@ -467,7 +471,7 @@
     SkScalar shininess() const { return fShininess; }
 
 protected:
-    SkSpecularLightingImageFilter(SkLight* light, SkScalar surfaceScale, SkScalar ks,
+    SkSpecularLightingImageFilter(SkImageFilterLight* light, SkScalar surfaceScale, SkScalar ks,
                                   SkScalar shininess, SkImageFilter* input, const CropRect*);
     void flatten(SkWriteBuffer& buffer) const override;
     bool onFilterImage(Proxy*, const SkBitmap& src, const Context&,
@@ -488,11 +492,11 @@
 
 class GrLightingEffect : public GrSingleTextureEffect {
 public:
-    GrLightingEffect(GrProcessorDataManager*, GrTexture* texture, const SkLight* light,
+    GrLightingEffect(GrProcessorDataManager*, GrTexture* texture, const SkImageFilterLight* light,
                      SkScalar surfaceScale, const SkMatrix& matrix, BoundaryMode boundaryMode);
     virtual ~GrLightingEffect();
 
-    const SkLight* light() const { return fLight; }
+    const SkImageFilterLight* light() const { return fLight; }
     SkScalar surfaceScale() const { return fSurfaceScale; }
     const SkMatrix& filterMatrix() const { return fFilterMatrix; }
     BoundaryMode boundaryMode() const { return fBoundaryMode; }
@@ -506,18 +510,19 @@
     }
 
 private:
-    typedef GrSingleTextureEffect INHERITED;
-    const SkLight* fLight;
+    const SkImageFilterLight* fLight;
     SkScalar fSurfaceScale;
     SkMatrix fFilterMatrix;
     BoundaryMode fBoundaryMode;
+
+    typedef GrSingleTextureEffect INHERITED;
 };
 
 class GrDiffuseLightingEffect : public GrLightingEffect {
 public:
     static GrFragmentProcessor* Create(GrProcessorDataManager* procDataManager,
                                        GrTexture* texture,
-                                       const SkLight* light,
+                                       const SkImageFilterLight* light,
                                        SkScalar surfaceScale,
                                        const SkMatrix& matrix,
                                        SkScalar kd,
@@ -544,7 +549,7 @@
 
     GrDiffuseLightingEffect(GrProcessorDataManager*,
                             GrTexture* texture,
-                            const SkLight* light,
+                            const SkImageFilterLight* light,
                             SkScalar surfaceScale,
                             const SkMatrix& matrix,
                             SkScalar kd,
@@ -559,7 +564,7 @@
 public:
     static GrFragmentProcessor* Create(GrProcessorDataManager* procDataManager,
                                        GrTexture* texture,
-                                       const SkLight* light,
+                                       const SkImageFilterLight* light,
                                        SkScalar surfaceScale,
                                        const SkMatrix& matrix,
                                        SkScalar ks,
@@ -589,7 +594,7 @@
 
     GrSpecularLightingEffect(GrProcessorDataManager*,
                              GrTexture* texture,
-                             const SkLight* light,
+                             const SkImageFilterLight* light,
                              SkScalar surfaceScale,
                              const SkMatrix& matrix,
                              SkScalar ks,
@@ -627,8 +632,7 @@
 
     // This is called from GrGLLightingEffect's setData(). Subclasses of GrGLLight must call
     // INHERITED::setData().
-    virtual void setData(const GrGLProgramDataManager&,
-                         const SkLight* light) const;
+    virtual void setData(const GrGLProgramDataManager&, const SkImageFilterLight* light) const;
 
 protected:
     /**
@@ -648,7 +652,7 @@
 class GrGLDistantLight : public GrGLLight {
 public:
     virtual ~GrGLDistantLight() {}
-    void setData(const GrGLProgramDataManager&, const SkLight* light) const override;
+    void setData(const GrGLProgramDataManager&, const SkImageFilterLight* light) const override;
     void emitSurfaceToLight(GrGLFPBuilder*, const char* z) override;
 
 private:
@@ -661,7 +665,7 @@
 class GrGLPointLight : public GrGLLight {
 public:
     virtual ~GrGLPointLight() {}
-    void setData(const GrGLProgramDataManager&, const SkLight* light) const override;
+    void setData(const GrGLProgramDataManager&, const SkImageFilterLight* light) const override;
     void emitSurfaceToLight(GrGLFPBuilder*, const char* z) override;
 
 private:
@@ -674,7 +678,7 @@
 class GrGLSpotLight : public GrGLLight {
 public:
     virtual ~GrGLSpotLight() {}
-    void setData(const GrGLProgramDataManager&, const SkLight* light) const override;
+    void setData(const GrGLProgramDataManager&, const SkImageFilterLight* light) const override;
     void emitSurfaceToLight(GrGLFPBuilder*, const char* z) override;
     void emitLightColor(GrGLFPBuilder*, const char *surfaceToLight) override;
 
@@ -699,7 +703,7 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-class SkLight : public SkRefCnt {
+class SkImageFilterLight : public SkRefCnt {
 public:
     
 
@@ -711,26 +715,26 @@
     virtual LightType type() const = 0;
     const SkPoint3& color() const { return fColor; }
     virtual GrGLLight* createGLLight() const = 0;
-    virtual bool isEqual(const SkLight& other) const {
+    virtual bool isEqual(const SkImageFilterLight& other) const {
         return fColor == other.fColor;
     }
     // Called to know whether the generated GrGLLight will require access to the fragment position.
     virtual bool requiresFragmentPosition() const = 0;
-    virtual SkLight* transform(const SkMatrix& matrix) const = 0;
+    virtual SkImageFilterLight* transform(const SkMatrix& matrix) const = 0;
 
     // Defined below SkLight's subclasses.
     void flattenLight(SkWriteBuffer& buffer) const;
-    static SkLight* UnflattenLight(SkReadBuffer& buffer);
+    static SkImageFilterLight* UnflattenLight(SkReadBuffer& buffer);
 
 protected:
-    SkLight(SkColor color) {
+    SkImageFilterLight(SkColor color) {
         fColor = SkPoint3::Make(SkIntToScalar(SkColorGetR(color)),
                                 SkIntToScalar(SkColorGetG(color)),
                                 SkIntToScalar(SkColorGetB(color)));
     }
-    SkLight(const SkPoint3& color)
+    SkImageFilterLight(const SkPoint3& color)
       : fColor(color) {}
-    SkLight(SkReadBuffer& buffer) {
+    SkImageFilterLight(SkReadBuffer& buffer) {
         fColor = readPoint3(buffer);
     }
 
@@ -744,7 +748,7 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-class SkDistantLight : public SkLight {
+class SkDistantLight : public SkImageFilterLight {
 public:
     SkDistantLight(const SkPoint3& direction, SkColor color)
       : INHERITED(color), fDirection(direction) {
@@ -766,7 +770,7 @@
     }
     bool requiresFragmentPosition() const override { return false; }
 
-    bool isEqual(const SkLight& other) const override {
+    bool isEqual(const SkImageFilterLight& other) const override {
         if (other.type() != kDistant_LightType) {
             return false;
         }
@@ -784,7 +788,7 @@
     SkDistantLight(const SkPoint3& direction, const SkPoint3& color)
       : INHERITED(color), fDirection(direction) {
     }
-    SkLight* transform(const SkMatrix& matrix) const override {
+    SkImageFilterLight* transform(const SkMatrix& matrix) const override {
         return new SkDistantLight(direction(), color());
     }
     void onFlattenLight(SkWriteBuffer& buffer) const override {
@@ -792,13 +796,14 @@
     }
 
 private:
-    typedef SkLight INHERITED;
     SkPoint3 fDirection;
+
+    typedef SkImageFilterLight INHERITED;
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 
-class SkPointLight : public SkLight {
+class SkPointLight : public SkImageFilterLight {
 public:
     SkPointLight(const SkPoint3& location, SkColor color)
      : INHERITED(color), fLocation(location) {}
@@ -823,7 +828,7 @@
 #endif
     }
     bool requiresFragmentPosition() const override { return true; }
-    bool isEqual(const SkLight& other) const override {
+    bool isEqual(const SkImageFilterLight& other) const override {
         if (other.type() != kPoint_LightType) {
             return false;
         }
@@ -831,7 +836,7 @@
         return INHERITED::isEqual(other) &&
                fLocation == o.fLocation;
     }
-    SkLight* transform(const SkMatrix& matrix) const override {
+    SkImageFilterLight* transform(const SkMatrix& matrix) const override {
         SkPoint location2 = SkPoint::Make(fLocation.fX, fLocation.fY);
         matrix.mapPoints(&location2, 1);
         // Use X scale and Y scale on Z and average the result
@@ -855,13 +860,14 @@
     }
 
 private:
-    typedef SkLight INHERITED;
     SkPoint3 fLocation;
+
+    typedef SkImageFilterLight INHERITED;
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 
-class SkSpotLight : public SkLight {
+class SkSpotLight : public SkImageFilterLight {
 public:
     SkSpotLight(const SkPoint3& location,
                 const SkPoint3& target,
@@ -881,7 +887,7 @@
        fConeScale = SkScalarInvert(antiAliasThreshold);
     }
 
-    SkLight* transform(const SkMatrix& matrix) const override {
+    SkImageFilterLight* transform(const SkMatrix& matrix) const override {
         SkPoint location2 = SkPoint::Make(fLocation.fX, fLocation.fY);
         matrix.mapPoints(&location2, 1);
         // Use X scale and Y scale on Z and average the result
@@ -987,7 +993,7 @@
         writePoint3(fS, buffer);
     }
 
-    bool isEqual(const SkLight& other) const override {
+    bool isEqual(const SkImageFilterLight& other) const override {
         if (other.type() != kSpot_LightType) {
             return false;
         }
@@ -1004,7 +1010,6 @@
     static const SkScalar kSpecularExponentMin;
     static const SkScalar kSpecularExponentMax;
 
-    typedef SkLight INHERITED;
     SkPoint3 fLocation;
     SkPoint3 fTarget;
     SkScalar fSpecularExponent;
@@ -1012,6 +1017,8 @@
     SkScalar fCosInnerConeAngle;
     SkScalar fConeScale;
     SkPoint3 fS;
+
+    typedef SkImageFilterLight INHERITED;
 };
 
 // According to the spec, the specular term should be in the range [1, 128] :
@@ -1021,22 +1028,22 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-void SkLight::flattenLight(SkWriteBuffer& buffer) const {
+void SkImageFilterLight::flattenLight(SkWriteBuffer& buffer) const {
     // Write type first, then baseclass, then subclass.
     buffer.writeInt(this->type());
     writePoint3(fColor, buffer);
     this->onFlattenLight(buffer);
 }
 
-/*static*/ SkLight* SkLight::UnflattenLight(SkReadBuffer& buffer) {
+/*static*/ SkImageFilterLight* SkImageFilterLight::UnflattenLight(SkReadBuffer& buffer) {
     // Read type first.
-    const SkLight::LightType type = (SkLight::LightType)buffer.readInt();
+    const SkImageFilterLight::LightType type = (SkImageFilterLight::LightType)buffer.readInt();
     switch (type) {
         // Each of these constructors must first call SkLight's, so we'll read the baseclass
         // then subclass, same order as flattenLight.
-        case SkLight::kDistant_LightType: return SkNEW_ARGS(SkDistantLight, (buffer));
-        case SkLight::kPoint_LightType:   return SkNEW_ARGS(SkPointLight, (buffer));
-        case SkLight::kSpot_LightType:    return SkNEW_ARGS(SkSpotLight, (buffer));
+        case SkImageFilterLight::kDistant_LightType: return SkNEW_ARGS(SkDistantLight, (buffer));
+        case SkImageFilterLight::kPoint_LightType:   return SkNEW_ARGS(SkPointLight, (buffer));
+        case SkImageFilterLight::kSpot_LightType:    return SkNEW_ARGS(SkSpotLight, (buffer));
         default:
             SkDEBUGFAIL("Unknown LightType.");
             buffer.validate(false);
@@ -1045,7 +1052,7 @@
 }
 ///////////////////////////////////////////////////////////////////////////////
 
-SkLightingImageFilter::SkLightingImageFilter(SkLight* light, SkScalar surfaceScale,
+SkLightingImageFilter::SkLightingImageFilter(SkImageFilterLight* light, SkScalar surfaceScale,
                                              SkImageFilter* input, const CropRect* cropRect)
   : INHERITED(1, &input, cropRect)
   , fLight(SkRef(light))
@@ -1058,7 +1065,7 @@
                                                               SkScalar kd,
                                                               SkImageFilter* input,
                                                               const CropRect* cropRect) {
-    SkAutoTUnref<SkLight> light(SkNEW_ARGS(SkDistantLight, (direction, lightColor)));
+    SkAutoTUnref<SkImageFilterLight> light(SkNEW_ARGS(SkDistantLight, (direction, lightColor)));
     return SkDiffuseLightingImageFilter::Create(light, surfaceScale, kd, input, cropRect);
 }
 
@@ -1068,7 +1075,7 @@
                                                             SkScalar kd,
                                                             SkImageFilter* input,
                                                             const CropRect* cropRect) {
-    SkAutoTUnref<SkLight> light(SkNEW_ARGS(SkPointLight, (location, lightColor)));
+    SkAutoTUnref<SkImageFilterLight> light(SkNEW_ARGS(SkPointLight, (location, lightColor)));
     return SkDiffuseLightingImageFilter::Create(light, surfaceScale, kd, input, cropRect);
 }
 
@@ -1081,8 +1088,9 @@
                                                            SkScalar kd,
                                                            SkImageFilter* input,
                                                            const CropRect* cropRect) {
-    SkAutoTUnref<SkLight> light(SkNEW_ARGS(SkSpotLight, (location, target, specularExponent,
-                                                         cutoffAngle, lightColor)));
+    SkAutoTUnref<SkImageFilterLight> light(SkNEW_ARGS(SkSpotLight, (location, target,
+                                                                    specularExponent,
+                                                                    cutoffAngle, lightColor)));
     return SkDiffuseLightingImageFilter::Create(light, surfaceScale, kd, input, cropRect);
 }
 
@@ -1093,7 +1101,7 @@
                                                                SkScalar shine,
                                                                SkImageFilter* input,
                                                                const CropRect* cropRect) {
-    SkAutoTUnref<SkLight> light(SkNEW_ARGS(SkDistantLight, (direction, lightColor)));
+    SkAutoTUnref<SkImageFilterLight> light(SkNEW_ARGS(SkDistantLight, (direction, lightColor)));
     return SkSpecularLightingImageFilter::Create(light, surfaceScale, ks, shine, input, cropRect);
 }
 
@@ -1104,7 +1112,7 @@
                                                              SkScalar shine,
                                                              SkImageFilter* input,
                                                              const CropRect* cropRect) {
-    SkAutoTUnref<SkLight> light(SkNEW_ARGS(SkPointLight, (location, lightColor)));
+    SkAutoTUnref<SkImageFilterLight> light(SkNEW_ARGS(SkPointLight, (location, lightColor)));
     return SkSpecularLightingImageFilter::Create(light, surfaceScale, ks, shine, input, cropRect);
 }
 
@@ -1118,8 +1126,9 @@
                                                             SkScalar shine,
                                                             SkImageFilter* input,
                                                             const CropRect* cropRect) {
-    SkAutoTUnref<SkLight> light(SkNEW_ARGS(SkSpotLight, (location, target, specularExponent,
-                                                         cutoffAngle, lightColor)));
+    SkAutoTUnref<SkImageFilterLight> light(SkNEW_ARGS(SkSpotLight, (location, target,
+                                                                    specularExponent,
+                                                                    cutoffAngle, lightColor)));
     return SkSpecularLightingImageFilter::Create(light, surfaceScale, ks, shine, input, cropRect);
 }
 
@@ -1133,8 +1142,11 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-SkImageFilter* SkDiffuseLightingImageFilter::Create(SkLight* light, SkScalar surfaceScale,
-                                    SkScalar kd, SkImageFilter* input, const CropRect* cropRect) {
+SkImageFilter* SkDiffuseLightingImageFilter::Create(SkImageFilterLight* light,
+                                                    SkScalar surfaceScale,
+                                                    SkScalar kd,
+                                                    SkImageFilter* input,
+                                                    const CropRect* cropRect) {
     if (NULL == light) {
         return NULL;
     }
@@ -1149,7 +1161,7 @@
     return SkNEW_ARGS(SkDiffuseLightingImageFilter, (light, surfaceScale, kd, input, cropRect));
 }
 
-SkDiffuseLightingImageFilter::SkDiffuseLightingImageFilter(SkLight* light,
+SkDiffuseLightingImageFilter::SkDiffuseLightingImageFilter(SkImageFilterLight* light,
                                                            SkScalar surfaceScale,
                                                            SkScalar kd,
                                                            SkImageFilter* input,
@@ -1161,7 +1173,7 @@
 
 SkFlattenable* SkDiffuseLightingImageFilter::CreateProc(SkReadBuffer& buffer) {
     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
-    SkAutoTUnref<SkLight> light(SkLight::UnflattenLight(buffer));
+    SkAutoTUnref<SkImageFilterLight> light(SkImageFilterLight::UnflattenLight(buffer));
     SkScalar surfaceScale = buffer.readScalar();
     SkScalar kd = buffer.readScalar();
     return Create(light, surfaceScale, kd, common.getInput(0), &common.cropRect());
@@ -1205,14 +1217,14 @@
         return false;
     }
 
-    SkAutoTUnref<SkLight> transformedLight(light()->transform(ctx.ctm()));
+    SkAutoTUnref<SkImageFilterLight> transformedLight(light()->transform(ctx.ctm()));
 
     DiffuseLightingType lightingType(fKD);
     offset->fX = bounds.left();
     offset->fY = bounds.top();
     bounds.offset(-srcOffset);
     switch (transformedLight->type()) {
-        case SkLight::kDistant_LightType:
+        case SkImageFilterLight::kDistant_LightType:
             lightBitmap<DiffuseLightingType, SkDistantLight>(lightingType,
                                                              transformedLight,
                                                              src,
@@ -1220,7 +1232,7 @@
                                                              surfaceScale(),
                                                              bounds);
             break;
-        case SkLight::kPoint_LightType:
+        case SkImageFilterLight::kPoint_LightType:
             lightBitmap<DiffuseLightingType, SkPointLight>(lightingType,
                                                            transformedLight,
                                                            src,
@@ -1228,7 +1240,7 @@
                                                            surfaceScale(),
                                                            bounds);
             break;
-        case SkLight::kSpot_LightType:
+        case SkImageFilterLight::kSpot_LightType:
             lightBitmap<DiffuseLightingType, SkSpotLight>(lightingType,
                                                           transformedLight,
                                                           src,
@@ -1265,8 +1277,12 @@
 
 ///////////////////////////////////////////////////////////////////////////////
 
-SkImageFilter* SkSpecularLightingImageFilter::Create(SkLight* light, SkScalar surfaceScale,
-                SkScalar ks, SkScalar shininess, SkImageFilter* input, const CropRect* cropRect) {
+SkImageFilter* SkSpecularLightingImageFilter::Create(SkImageFilterLight* light,
+                                                     SkScalar surfaceScale,
+                                                     SkScalar ks,
+                                                     SkScalar shininess,
+                                                     SkImageFilter* input,
+                                                     const CropRect* cropRect) {
     if (NULL == light) {
         return NULL;
     }
@@ -1282,7 +1298,7 @@
                       (light, surfaceScale, ks, shininess, input, cropRect));
 }
 
-SkSpecularLightingImageFilter::SkSpecularLightingImageFilter(SkLight* light,
+SkSpecularLightingImageFilter::SkSpecularLightingImageFilter(SkImageFilterLight* light,
                                                              SkScalar surfaceScale,
                                                              SkScalar ks,
                                                              SkScalar shininess,
@@ -1296,7 +1312,7 @@
 
 SkFlattenable* SkSpecularLightingImageFilter::CreateProc(SkReadBuffer& buffer) {
     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
-    SkAutoTUnref<SkLight> light(SkLight::UnflattenLight(buffer));
+    SkAutoTUnref<SkImageFilterLight> light(SkImageFilterLight::UnflattenLight(buffer));
     SkScalar surfaceScale = buffer.readScalar();
     SkScalar ks = buffer.readScalar();
     SkScalar shine = buffer.readScalar();
@@ -1347,9 +1363,9 @@
     offset->fX = bounds.left();
     offset->fY = bounds.top();
     bounds.offset(-srcOffset);
-    SkAutoTUnref<SkLight> transformedLight(light()->transform(ctx.ctm()));
+    SkAutoTUnref<SkImageFilterLight> transformedLight(light()->transform(ctx.ctm()));
     switch (transformedLight->type()) {
-        case SkLight::kDistant_LightType:
+        case SkImageFilterLight::kDistant_LightType:
             lightBitmap<SpecularLightingType, SkDistantLight>(lightingType,
                                                               transformedLight,
                                                               src,
@@ -1357,7 +1373,7 @@
                                                               surfaceScale(),
                                                               bounds);
             break;
-        case SkLight::kPoint_LightType:
+        case SkImageFilterLight::kPoint_LightType:
             lightBitmap<SpecularLightingType, SkPointLight>(lightingType,
                                                             transformedLight,
                                                             src,
@@ -1365,7 +1381,7 @@
                                                             surfaceScale(),
                                                             bounds);
             break;
-        case SkLight::kSpot_LightType:
+        case SkImageFilterLight::kSpot_LightType:
             lightBitmap<SpecularLightingType, SkSpotLight>(lightingType,
                                                            transformedLight,
                                                            src,
@@ -1409,7 +1425,7 @@
                           SkScalarToFloat(random->nextSScalar1()));
 }
 
-SkLight* create_random_light(SkRandom* random) {
+SkImageFilterLight* create_random_light(SkRandom* random) {
     int type = random->nextULessThan(3);
     switch (type) {
         case 0: {
@@ -1571,7 +1587,7 @@
 
 GrLightingEffect::GrLightingEffect(GrProcessorDataManager* procDataManager,
                                    GrTexture* texture,
-                                   const SkLight* light,
+                                   const SkImageFilterLight* light,
                                    SkScalar surfaceScale,
                                    const SkMatrix& matrix,
                                    BoundaryMode boundaryMode)
@@ -1601,7 +1617,7 @@
 
 GrDiffuseLightingEffect::GrDiffuseLightingEffect(GrProcessorDataManager* procDataManager,
                                                  GrTexture* texture,
-                                                 const SkLight* light,
+                                                 const SkImageFilterLight* light,
                                                  SkScalar surfaceScale,
                                                  const SkMatrix& matrix,
                                                  SkScalar kd,
@@ -1630,7 +1646,7 @@
 GrFragmentProcessor* GrDiffuseLightingEffect::TestCreate(GrProcessorTestData* d) {
     SkScalar surfaceScale = d->fRandom->nextSScalar1();
     SkScalar kd = d->fRandom->nextUScalar1();
-    SkAutoTUnref<SkLight> light(create_random_light(d->fRandom));
+    SkAutoTUnref<SkImageFilterLight> light(create_random_light(d->fRandom));
     SkMatrix matrix;
     for (int i = 0; i < 9; i++) {
         matrix[i] = d->fRandom->nextUScalar1();
@@ -1754,7 +1770,8 @@
     float ySign = texture->origin() == kTopLeft_GrSurfaceOrigin ? -1.0f : 1.0f;
     pdman.set2f(fImageIncrementUni, 1.0f / texture->width(), ySign / texture->height());
     pdman.set1f(fSurfaceScaleUni, lighting.surfaceScale());
-    SkAutoTUnref<SkLight> transformedLight(lighting.light()->transform(lighting.filterMatrix()));
+    SkAutoTUnref<SkImageFilterLight> transformedLight(
+                                            lighting.light()->transform(lighting.filterMatrix()));
     fLight->setData(pdman, transformedLight);
 }
 
@@ -1799,15 +1816,15 @@
 
 GrSpecularLightingEffect::GrSpecularLightingEffect(GrProcessorDataManager* procDataManager,
                                                    GrTexture* texture,
-                                                   const SkLight* light,
+                                                   const SkImageFilterLight* light,
                                                    SkScalar surfaceScale,
                                                    const SkMatrix& matrix,
                                                    SkScalar ks,
                                                    SkScalar shininess,
                                                    BoundaryMode boundaryMode)
-    : INHERITED(procDataManager, texture, light, surfaceScale, matrix, boundaryMode),
-      fKS(ks),
-      fShininess(shininess) {
+    : INHERITED(procDataManager, texture, light, surfaceScale, matrix, boundaryMode)
+    , fKS(ks)
+    , fShininess(shininess) {
     this->initClassID<GrSpecularLightingEffect>();
 }
 
@@ -1833,7 +1850,7 @@
     SkScalar surfaceScale = d->fRandom->nextSScalar1();
     SkScalar ks = d->fRandom->nextUScalar1();
     SkScalar shininess = d->fRandom->nextUScalar1();
-    SkAutoTUnref<SkLight> light(create_random_light(d->fRandom));
+    SkAutoTUnref<SkImageFilterLight> light(create_random_light(d->fRandom));
     SkMatrix matrix;
     for (int i = 0; i < 9; i++) {
         matrix[i] = d->fRandom->nextUScalar1();
@@ -1899,7 +1916,8 @@
     builder->getFragmentShaderBuilder()->codeAppend(builder->getUniformCStr(this->lightColorUni()));
 }
 
-void GrGLLight::setData(const GrGLProgramDataManager& pdman, const SkLight* light) const {
+void GrGLLight::setData(const GrGLProgramDataManager& pdman,
+                        const SkImageFilterLight* light) const {
     setUniformPoint3(pdman, fColorUni,
                      light->color().makeScale(SkScalarInvert(SkIntToScalar(255))));
 }
@@ -1907,9 +1925,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void GrGLDistantLight::setData(const GrGLProgramDataManager& pdman,
-                               const SkLight* light) const {
+                               const SkImageFilterLight* light) const {
     INHERITED::setData(pdman, light);
-    SkASSERT(light->type() == SkLight::kDistant_LightType);
+    SkASSERT(light->type() == SkImageFilterLight::kDistant_LightType);
     const SkDistantLight* distantLight = static_cast<const SkDistantLight*>(light);
     setUniformNormal3(pdman, fDirectionUni, distantLight->direction());
 }
@@ -1925,9 +1943,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void GrGLPointLight::setData(const GrGLProgramDataManager& pdman,
-                             const SkLight* light) const {
+                             const SkImageFilterLight* light) const {
     INHERITED::setData(pdman, light);
-    SkASSERT(light->type() == SkLight::kPoint_LightType);
+    SkASSERT(light->type() == SkImageFilterLight::kPoint_LightType);
     const SkPointLight* pointLight = static_cast<const SkPointLight*>(light);
     setUniformPoint3(pdman, fLocationUni, pointLight->location());
 }
@@ -1945,9 +1963,9 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 void GrGLSpotLight::setData(const GrGLProgramDataManager& pdman,
-                            const SkLight* light) const {
+                            const SkImageFilterLight* light) const {
     INHERITED::setData(pdman, light);
-    SkASSERT(light->type() == SkLight::kSpot_LightType);
+    SkASSERT(light->type() == SkImageFilterLight::kSpot_LightType);
     const SkSpotLight* spotLight = static_cast<const SkSpotLight *>(light);
     setUniformPoint3(pdman, fLocationUni, spotLight->location());
     pdman.set1f(fExponentUni, spotLight->specularExponent());
diff --git a/src/effects/SkLightingShader.cpp b/src/effects/SkLightingShader.cpp
deleted file mode 100644
index d441d9b..0000000
--- a/src/effects/SkLightingShader.cpp
+++ /dev/null
@@ -1,588 +0,0 @@
-
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "SkBitmapProcState.h"
-#include "SkColor.h"
-#include "SkEmptyShader.h"
-#include "SkErrorInternals.h"
-#include "SkLightingShader.h"
-#include "SkMathPriv.h"
-#include "SkReadBuffer.h"
-#include "SkWriteBuffer.h"
-
-////////////////////////////////////////////////////////////////////////////
-
-/*
-   SkLightingShader TODOs:
-        support other than clamp mode
-        allow 'diffuse' & 'normal' to be of different dimensions?
-        support different light types
-        support multiple lights
-        enforce normal map is 4 channel
-        use SkImages instead if SkBitmaps
-
-    To Test:
-        non-opaque diffuse textures
-        A8 diffuse textures
-        down & upsampled draws
-*/
-
-
-
-/** \class SkLightingShaderImpl
-    This subclass of shader applies lighting.
-*/
-class SK_API SkLightingShaderImpl : public SkShader {
-public:
-
-    /** Create a new lighting shader that use the provided normal map, light
-        and ambient color to light the diffuse bitmap.
-        @param diffuse the diffuse bitmap
-        @param normal  the normal map
-        @param light   the light applied to the normal map
-        @param ambient the linear (unpremul) ambient light color
-    */
-    SkLightingShaderImpl(const SkBitmap& diffuse, const SkBitmap& normal,
-                         const SkLightingShader::Light& light,
-                         const SkColor3f& ambient, const SkMatrix* localMatrix) 
-        : INHERITED(localMatrix)
-        , fDiffuseMap(diffuse)
-        , fNormalMap(normal)
-        , fLight(light)
-        , fAmbientColor(ambient) {
-        if (!fLight.fDirection.normalize()) {
-            fLight.fDirection = SkPoint3::Make(0.0f, 0.0f, 1.0f);
-        }
-    }
-
-    bool isOpaque() const override;
-
-    bool asFragmentProcessor(GrContext*, const SkPaint& paint, const SkMatrix& viewM,
-                             const SkMatrix* localMatrix, GrColor* color,
-                             GrProcessorDataManager*, GrFragmentProcessor** fp) const override;
-
-    size_t contextSize() const override;
-
-    class LightingShaderContext : public SkShader::Context {
-    public:
-        // The context takes ownership of the states. It will call their destructors
-        // but will NOT free the memory.
-        LightingShaderContext(const SkLightingShaderImpl&, const ContextRec&,
-                              SkBitmapProcState* diffuseState, SkBitmapProcState* normalState);
-        ~LightingShaderContext() override;
-
-        void shadeSpan(int x, int y, SkPMColor[], int count) override;
-
-        uint32_t getFlags() const override { return fFlags; }
-
-    private:
-        SkBitmapProcState* fDiffuseState;
-        SkBitmapProcState* fNormalState;
-        uint32_t           fFlags;
-
-        typedef SkShader::Context INHERITED;
-    };
-
-    SK_TO_STRING_OVERRIDE()
-    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingShaderImpl)
-
-protected:
-    void flatten(SkWriteBuffer&) const override;
-    Context* onCreateContext(const ContextRec&, void*) const override;
-
-private:
-    SkBitmap                fDiffuseMap;
-    SkBitmap                fNormalMap;
-    SkLightingShader::Light fLight;
-    SkColor3f               fAmbientColor;  // linear (unpremul) color. Range is 0..1/channel.
-
-    friend class SkLightingShader;
-
-    typedef SkShader INHERITED;
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-#if SK_SUPPORT_GPU
-
-#include "GrCoordTransform.h"
-#include "GrFragmentProcessor.h"
-#include "GrTextureAccess.h"
-#include "gl/GrGLProcessor.h"
-#include "gl/builders/GrGLProgramBuilder.h"
-#include "SkGr.h"
-
-class LightingFP : public GrFragmentProcessor {
-public:
-    LightingFP(GrTexture* diffuse, GrTexture* normal, const SkMatrix& matrix,
-               const SkVector3& lightDir, const SkColor3f& lightColor,
-               const SkColor3f& ambientColor)
-        : fDeviceTransform(kDevice_GrCoordSet, matrix)
-        , fDiffuseTextureAccess(diffuse)
-        , fNormalTextureAccess(normal)
-        , fLightDir(lightDir)
-        , fLightColor(lightColor)
-        , fAmbientColor(ambientColor) {
-        this->addCoordTransform(&fDeviceTransform);
-        this->addTextureAccess(&fDiffuseTextureAccess);
-        this->addTextureAccess(&fNormalTextureAccess);
-
-        this->initClassID<LightingFP>();
-    }
-
-    class LightingGLFP : public GrGLFragmentProcessor {
-    public:
-        LightingGLFP() {
-            fLightDir.fX = 10000.0f;
-            fLightColor.fX = 0.0f;
-            fAmbientColor.fX = 0.0f;
-        }
-
-        void emitCode(EmitArgs& args) override {
-
-            GrGLFragmentBuilder* fpb = args.fBuilder->getFragmentShaderBuilder();
-
-            // add uniforms
-            const char* lightDirUniName = NULL;
-            fLightDirUni = args.fBuilder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
-                                                     kVec3f_GrSLType, kDefault_GrSLPrecision,
-                                                     "LightDir", &lightDirUniName);
-
-            const char* lightColorUniName = NULL;
-            fLightColorUni = args.fBuilder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
-                                                       kVec3f_GrSLType, kDefault_GrSLPrecision,
-                                                       "LightColor", &lightColorUniName);
-
-            const char* ambientColorUniName = NULL;
-            fAmbientColorUni = args.fBuilder->addUniform(GrGLProgramBuilder::kFragment_Visibility,
-                                                         kVec3f_GrSLType, kDefault_GrSLPrecision,
-                                                         "AmbientColor", &ambientColorUniName);
-
-            fpb->codeAppend("vec4 diffuseColor = ");
-            fpb->appendTextureLookupAndModulate(args.fInputColor, args.fSamplers[0], 
-                                                args.fCoords[0].c_str(), 
-                                                args.fCoords[0].getType());
-            fpb->codeAppend(";");
-
-            fpb->codeAppend("vec4 normalColor = ");
-            fpb->appendTextureLookup(args.fSamplers[1],
-                                     args.fCoords[0].c_str(), 
-                                     args.fCoords[0].getType());
-            fpb->codeAppend(";");
-
-            fpb->codeAppend("vec3 normal = normalize(normalColor.rgb - vec3(0.5));");
-            fpb->codeAppendf("vec3 lightDir = normalize(%s);", lightDirUniName);
-            fpb->codeAppend("float NdotL = dot(normal, lightDir);");
-            // diffuse light
-            fpb->codeAppendf("vec3 result = %s*diffuseColor.rgb*NdotL;", lightColorUniName);
-            // ambient light
-            fpb->codeAppendf("result += %s;", ambientColorUniName);
-            fpb->codeAppendf("%s = vec4(result.rgb, diffuseColor.a);", args.fOutputColor);
-        }
-
-        static void GenKey(const GrProcessor& proc, const GrGLSLCaps&,
-                           GrProcessorKeyBuilder* b) {
-//            const LightingFP& lightingFP = proc.cast<LightingFP>();
-            // only one shader generated currently
-            b->add32(0x0);
-        }
-
-    protected:
-        void onSetData(const GrGLProgramDataManager& pdman, const GrProcessor& proc) override {
-            const LightingFP& lightingFP = proc.cast<LightingFP>();
-
-            const SkVector3& lightDir = lightingFP.lightDir();
-            if (lightDir != fLightDir) {
-                pdman.set3fv(fLightDirUni, 1, &lightDir.fX);
-                fLightDir = lightDir;
-            }
-
-            const SkColor3f& lightColor = lightingFP.lightColor();
-            if (lightColor != fLightColor) {
-                pdman.set3fv(fLightColorUni, 1, &lightColor.fX);
-                fLightColor = lightColor;
-            }
-
-            const SkColor3f& ambientColor = lightingFP.ambientColor();
-            if (ambientColor != fAmbientColor) {
-                pdman.set3fv(fAmbientColorUni, 1, &ambientColor.fX);
-                fAmbientColor = ambientColor;
-            }
-        }
-
-    private:
-        SkVector3 fLightDir;
-        GrGLProgramDataManager::UniformHandle fLightDirUni;
-
-        SkColor3f fLightColor;
-        GrGLProgramDataManager::UniformHandle fLightColorUni;
-
-        SkColor3f fAmbientColor;
-        GrGLProgramDataManager::UniformHandle fAmbientColorUni;
-    };
-
-    void onGetGLProcessorKey(const GrGLSLCaps& caps, GrProcessorKeyBuilder* b) const override {
-        LightingGLFP::GenKey(*this, caps, b);
-    }
-
-    const char* name() const override { return "LightingFP"; }
-
-    void onComputeInvariantOutput(GrInvariantOutput* inout) const override {
-        inout->mulByUnknownFourComponents();
-    }
-
-    const SkVector3& lightDir() const { return fLightDir; }
-    const SkColor3f& lightColor() const { return fLightColor; }
-    const SkColor3f& ambientColor() const { return fAmbientColor; }
-
-private:
-    GrGLFragmentProcessor* onCreateGLInstance() const override { return SkNEW(LightingGLFP); }
-
-    bool onIsEqual(const GrFragmentProcessor& proc) const override { 
-        const LightingFP& lightingFP = proc.cast<LightingFP>();
-        return fDeviceTransform == lightingFP.fDeviceTransform &&
-               fDiffuseTextureAccess == lightingFP.fDiffuseTextureAccess &&
-               fNormalTextureAccess == lightingFP.fNormalTextureAccess &&
-               fLightDir == lightingFP.fLightDir &&
-               fLightColor == lightingFP.fLightColor &&
-               fAmbientColor == lightingFP.fAmbientColor;
-    }
-
-    GrCoordTransform fDeviceTransform;
-    GrTextureAccess  fDiffuseTextureAccess;
-    GrTextureAccess  fNormalTextureAccess;
-    SkVector3        fLightDir;
-    SkColor3f        fLightColor;
-    SkColor3f        fAmbientColor;
-};
-
-////////////////////////////////////////////////////////////////////////////
-
-bool SkLightingShaderImpl::asFragmentProcessor(GrContext* context, const SkPaint& paint, 
-                                               const SkMatrix& viewM, const SkMatrix* localMatrix, 
-                                               GrColor* color, GrProcessorDataManager*,
-                                               GrFragmentProcessor** fp) const {
-    // we assume diffuse and normal maps have same width and height
-    // TODO: support different sizes
-    SkASSERT(fDiffuseMap.width() == fNormalMap.width() &&
-             fDiffuseMap.height() == fNormalMap.height());
-    SkMatrix matrix;
-    matrix.setIDiv(fDiffuseMap.width(), fDiffuseMap.height());
-
-    SkMatrix lmInverse;
-    if (!this->getLocalMatrix().invert(&lmInverse)) {
-        return false;
-    }
-    if (localMatrix) {
-        SkMatrix inv;
-        if (!localMatrix->invert(&inv)) {
-            return false;
-        }
-        lmInverse.postConcat(inv);
-    }
-    matrix.preConcat(lmInverse);
-
-    // Must set wrap and filter on the sampler before requesting a texture. In two places below
-    // we check the matrix scale factors to determine how to interpret the filter quality setting.
-    // This completely ignores the complexity of the drawVertices case where explicit local coords
-    // are provided by the caller.
-    GrTextureParams::FilterMode textureFilterMode = GrTextureParams::kBilerp_FilterMode;
-    switch (paint.getFilterQuality()) {
-    case kNone_SkFilterQuality:
-        textureFilterMode = GrTextureParams::kNone_FilterMode;
-        break;
-    case kLow_SkFilterQuality:
-        textureFilterMode = GrTextureParams::kBilerp_FilterMode;
-        break;
-    case kMedium_SkFilterQuality:{                          
-        SkMatrix matrix;
-        matrix.setConcat(viewM, this->getLocalMatrix());
-        if (matrix.getMinScale() < SK_Scalar1) {
-            textureFilterMode = GrTextureParams::kMipMap_FilterMode;
-        } else {
-            // Don't trigger MIP level generation unnecessarily.
-            textureFilterMode = GrTextureParams::kBilerp_FilterMode;
-        }
-        break;
-    }
-    case kHigh_SkFilterQuality:
-    default:
-        SkErrorInternals::SetError(kInvalidPaint_SkError,
-            "Sorry, I don't understand the filtering "
-            "mode you asked for.  Falling back to "
-            "MIPMaps.");
-        textureFilterMode = GrTextureParams::kMipMap_FilterMode;
-        break;
-
-    }
-
-    // TODO: support other tile modes
-    GrTextureParams params(kClamp_TileMode, textureFilterMode);
-    SkAutoTUnref<GrTexture> diffuseTexture(GrRefCachedBitmapTexture(context, fDiffuseMap, &params));
-    if (!diffuseTexture) {
-        SkErrorInternals::SetError(kInternalError_SkError,
-            "Couldn't convert bitmap to texture.");
-        return false;
-    }
-
-    SkAutoTUnref<GrTexture> normalTexture(GrRefCachedBitmapTexture(context, fNormalMap, &params));
-    if (!normalTexture) {
-        SkErrorInternals::SetError(kInternalError_SkError,
-            "Couldn't convert bitmap to texture.");
-        return false;
-    }
-
-    *fp = SkNEW_ARGS(LightingFP, (diffuseTexture, normalTexture, matrix,
-                                  fLight.fDirection, fLight.fColor, fAmbientColor));
-    *color = GrColorPackA4(paint.getAlpha());
-    return true;
-}
-#else
-
-bool SkLightingShaderImpl::asFragmentProcessor(GrContext* context, const SkPaint& paint, 
-                                               const SkMatrix& viewM, const SkMatrix* localMatrix, 
-                                               GrColor* color, GrProcessorDataManager*,
-                                               GrFragmentProcessor** fp) const {
-    SkDEBUGFAIL("Should not call in GPU-less build");
-    return false;
-}
-
-#endif
-
-////////////////////////////////////////////////////////////////////////////
-
-bool SkLightingShaderImpl::isOpaque() const {
-    return fDiffuseMap.isOpaque();
-}
-
-size_t SkLightingShaderImpl::contextSize() const {
-    return 2 * sizeof(SkBitmapProcState) + sizeof(LightingShaderContext);
-}
-
-SkLightingShaderImpl::LightingShaderContext::LightingShaderContext(const SkLightingShaderImpl& shader,
-                                                                   const ContextRec& rec,
-                                                                   SkBitmapProcState* diffuseState,
-                                                                   SkBitmapProcState* normalState)
-    : INHERITED(shader, rec)
-    , fDiffuseState(diffuseState)
-    , fNormalState(normalState)
-{
-    const SkPixmap& pixmap = fDiffuseState->fPixmap;
-    bool isOpaque = pixmap.isOpaque();
-
-    // update fFlags
-    uint32_t flags = 0;
-    if (isOpaque && (255 == this->getPaintAlpha())) {
-        flags |= kOpaqueAlpha_Flag;
-    }
-
-    fFlags = flags;
-}
-
-SkLightingShaderImpl::LightingShaderContext::~LightingShaderContext() {
-    // The bitmap proc states have been created outside of the context on memory that will be freed
-    // elsewhere. Call the destructors but leave the freeing of the memory to the caller.
-    fDiffuseState->~SkBitmapProcState();
-    fNormalState->~SkBitmapProcState();
-}
-
-static inline int light(SkScalar light, int diff, SkScalar NdotL, SkScalar ambient) {
-    SkScalar color = light * diff * NdotL + 255 * ambient;
-    if (color <= 0.0f) {
-        return 0;
-    } else if (color >= 255.0f) {
-        return 255;
-    } else {
-        return (int) color;
-    }
-}
-
-// larger is better (fewer times we have to loop), but we shouldn't
-// take up too much stack-space (each could here costs 16 bytes)
-#define TMP_COUNT     16
-
-void SkLightingShaderImpl::LightingShaderContext::shadeSpan(int x, int y,
-                                                            SkPMColor result[], int count) {
-    const SkLightingShaderImpl& lightShader = static_cast<const SkLightingShaderImpl&>(fShader);
-
-    SkPMColor   tmpColor[TMP_COUNT], tmpColor2[TMP_COUNT];
-    SkPMColor   tmpNormal[TMP_COUNT], tmpNormal2[TMP_COUNT];
-
-    SkBitmapProcState::MatrixProc   diffMProc = fDiffuseState->getMatrixProc();
-    SkBitmapProcState::SampleProc32 diffSProc = fDiffuseState->getSampleProc32();
-
-    SkBitmapProcState::MatrixProc   normalMProc = fNormalState->getMatrixProc();
-    SkBitmapProcState::SampleProc32 normalSProc = fNormalState->getSampleProc32();
-
-    SkASSERT(fDiffuseState->fPixmap.addr());
-    SkASSERT(fNormalState->fPixmap.addr());
-
-    SkPoint3 norm;
-    SkScalar NdotL;
-    int r, g, b;
-
-    do {
-        int n = count;
-        if (n > TMP_COUNT) {
-            n = TMP_COUNT;
-        }
-
-        diffMProc(*fDiffuseState, tmpColor, n, x, y);
-        diffSProc(*fDiffuseState, tmpColor, n, tmpColor2);
-
-        normalMProc(*fNormalState, tmpNormal, n, x, y);
-        normalSProc(*fNormalState, tmpNormal, n, tmpNormal2);
-
-        for (int i = 0; i < n; ++i) {
-            SkASSERT(0xFF == SkColorGetA(tmpNormal2[i]));  // opaque -> unpremul
-            norm.set(SkIntToScalar(SkGetPackedR32(tmpNormal2[i]))-127.0f,
-                     SkIntToScalar(SkGetPackedG32(tmpNormal2[i]))-127.0f,
-                     SkIntToScalar(SkGetPackedB32(tmpNormal2[i]))-127.0f);
-            norm.normalize();
-
-            SkColor diffColor = SkUnPreMultiply::PMColorToColor(tmpColor2[i]);
-            NdotL = norm.dot(lightShader.fLight.fDirection);
-
-            // This is all done in linear unpremul color space
-            r = light(lightShader.fLight.fColor.fX, SkColorGetR(diffColor), NdotL, 
-                      lightShader.fAmbientColor.fX);
-            g = light(lightShader.fLight.fColor.fY, SkColorGetG(diffColor), NdotL, 
-                      lightShader.fAmbientColor.fY);
-            b = light(lightShader.fLight.fColor.fZ, SkColorGetB(diffColor), NdotL, 
-                      lightShader.fAmbientColor.fZ);
-
-            result[i] = SkPreMultiplyARGB(SkColorGetA(diffColor), r, g, b);
-        }
-
-        result += n;
-        x += n;
-        count -= n;
-    } while (count > 0);
-}
-
-////////////////////////////////////////////////////////////////////////////
-
-#ifndef SK_IGNORE_TO_STRING
-void SkLightingShaderImpl::toString(SkString* str) const {
-    str->appendf("LightingShader: ()");
-}
-#endif
-
-SkFlattenable* SkLightingShaderImpl::CreateProc(SkReadBuffer& buf) {
-    SkMatrix localMatrix;
-    buf.readMatrix(&localMatrix);
-
-    SkBitmap diffuse;
-    if (!buf.readBitmap(&diffuse)) {
-        return NULL;
-    }
-    diffuse.setImmutable();
-
-    SkBitmap normal;
-    if (!buf.readBitmap(&normal)) {
-        return NULL;
-    }
-    normal.setImmutable();
-
-    SkLightingShader::Light light;
-    if (!buf.readScalarArray(&light.fDirection.fX, 3)) {
-        return NULL;
-    }
-    if (!buf.readScalarArray(&light.fColor.fX, 3)) {
-        return NULL;
-    }
-
-    SkColor3f ambient;
-    if (!buf.readScalarArray(&ambient.fX, 3)) {
-        return NULL;
-    }
-
-    return SkNEW_ARGS(SkLightingShaderImpl, (diffuse, normal, light, ambient, &localMatrix));
-}
-
-void SkLightingShaderImpl::flatten(SkWriteBuffer& buf) const {
-    buf.writeMatrix(this->getLocalMatrix());
-
-    buf.writeBitmap(fDiffuseMap);
-    buf.writeBitmap(fNormalMap);
-    buf.writeScalarArray(&fLight.fDirection.fX, 3);
-    buf.writeScalarArray(&fLight.fColor.fX, 3);
-    buf.writeScalarArray(&fAmbientColor.fX, 3);
-}
-
-SkShader::Context* SkLightingShaderImpl::onCreateContext(const ContextRec& rec,
-                                                         void* storage) const {
-
-    SkMatrix totalInverse;
-    // Do this first, so we know the matrix can be inverted.
-    if (!this->computeTotalInverse(rec, &totalInverse)) {
-        return NULL;
-    }
-
-    void* diffuseStateStorage = (char*)storage + sizeof(LightingShaderContext);
-    SkBitmapProcState* diffuseState = SkNEW_PLACEMENT(diffuseStateStorage, SkBitmapProcState);
-    SkASSERT(diffuseState);
-
-    diffuseState->fTileModeX = SkShader::kClamp_TileMode;
-    diffuseState->fTileModeY = SkShader::kClamp_TileMode;
-    diffuseState->fOrigBitmap = fDiffuseMap;
-    if (!diffuseState->chooseProcs(totalInverse, *rec.fPaint)) {
-        diffuseState->~SkBitmapProcState();
-        return NULL;
-    }
-
-    void* normalStateStorage = (char*)storage + sizeof(LightingShaderContext) + sizeof(SkBitmapProcState);
-    SkBitmapProcState* normalState = SkNEW_PLACEMENT(normalStateStorage, SkBitmapProcState);
-    SkASSERT(normalState);
-
-    normalState->fTileModeX = SkShader::kClamp_TileMode;
-    normalState->fTileModeY = SkShader::kClamp_TileMode;
-    normalState->fOrigBitmap = fNormalMap;
-    if (!normalState->chooseProcs(totalInverse, *rec.fPaint)) {
-        diffuseState->~SkBitmapProcState();
-        normalState->~SkBitmapProcState();
-        return NULL;
-    }
-
-    return SkNEW_PLACEMENT_ARGS(storage, LightingShaderContext, (*this, rec,
-                                                                 diffuseState, normalState));
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-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;
-}
-
-SkShader* SkLightingShader::Create(const SkBitmap& diffuse, const SkBitmap& normal,
-                                   const SkLightingShader::Light& light,
-                                   const SkColor3f& ambient,
-                                   const SkMatrix* localMatrix) {
-    if (diffuse.isNull() || bitmap_is_too_big(diffuse) ||
-        normal.isNull() || bitmap_is_too_big(normal) ||
-        diffuse.width() != normal.width() ||
-        diffuse.height() != normal.height()) {
-        return nullptr;
-    }
-
-    return SkNEW_ARGS(SkLightingShaderImpl, (diffuse, normal, light, ambient, localMatrix));
-}
-
-///////////////////////////////////////////////////////////////////////////////
-
-SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_START(SkLightingShader)
-    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkLightingShaderImpl)
-SK_DEFINE_FLATTENABLE_REGISTRAR_GROUP_END
-
-///////////////////////////////////////////////////////////////////////////////
diff --git a/src/effects/SkLightingShader.h b/src/effects/SkLightingShader.h
deleted file mode 100644
index 64d41a2..0000000
--- a/src/effects/SkLightingShader.h
+++ /dev/null
@@ -1,59 +0,0 @@
-
-/*
- * Copyright 2015 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-
-#ifndef SkLightingShader_DEFINED
-#define SkLightingShader_DEFINED
-
-#include "SkPoint3.h"
-#include "SkShader.h"
-
-class SK_API SkLightingShader {
-public:
-    struct Light {
-        SkVector3   fDirection;       // direction towards the light (+Z is out of the screen).
-                                      // If degenerate, it will be replaced with (0, 0, 1).
-        SkColor3f   fColor;           // linear (unpremul) color. Range is 0..1 in each channel.
-    };
-
-    /** Returns a shader that lights the diffuse and normal maps with a single light.
-
-        It returns a shader with a reference count of 1.
-        The caller should decrement the shader's reference count when done with the shader.
-        It is an error for count to be < 2.
-        @param  diffuse     the diffuse bitmap
-        @param  normal      the normal map
-        @param  light       the light applied to the normal map
-        @param  ambient     the linear (unpremul) ambient light color. Range is 0..1/channel.
-        @param  localMatrix the matrix mapping the textures to the dest rect 
-
-        NULL will be returned if:
-            either 'diffuse' or 'normal' are empty
-            either 'diffuse' or 'normal' are too big (> 65535 on a side)
-            'diffuse' and 'normal' aren't the same size
-
-        The lighting equation is currently:
-            result = LightColor * DiffuseColor * (Normal * LightDir) + AmbientColor
-
-        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).
-    */
-    static SkShader* Create(const SkBitmap& diffuse, const SkBitmap& normal,
-                            const SkLightingShader::Light& light, const SkColor3f& ambient,
-                            const SkMatrix* localMatrix);
-
-    SK_DECLARE_FLATTENABLE_REGISTRAR_GROUP()
-};
-
-#endif
diff --git a/src/ports/SkGlobalInitialization_chromium.cpp b/src/ports/SkGlobalInitialization_chromium.cpp
index 3aecbd5..04ca841 100644
--- a/src/ports/SkGlobalInitialization_chromium.cpp
+++ b/src/ports/SkGlobalInitialization_chromium.cpp
@@ -41,7 +41,7 @@
 #include "SkLayerRasterizer.h"
 #include "SkLerpXfermode.h"
 #include "SkLightingImageFilter.h"
-#include "../effects/SkLightingShader.h"
+#include "SkLightingShader.h"
 #include "SkLocalMatrixShader.h"
 #include "SkLumaColorFilter.h"
 #include "SkMagnifierImageFilter.h"
diff --git a/src/ports/SkGlobalInitialization_default.cpp b/src/ports/SkGlobalInitialization_default.cpp
index 47b00b9..0d05c1d 100644
--- a/src/ports/SkGlobalInitialization_default.cpp
+++ b/src/ports/SkGlobalInitialization_default.cpp
@@ -37,7 +37,7 @@
 #include "SkLayerRasterizer.h"
 #include "SkLerpXfermode.h"
 #include "SkLightingImageFilter.h"
-#include "../effects/SkLightingShader.h"
+#include "SkLightingShader.h"
 #include "SkLocalMatrixShader.h"
 #include "SkLumaColorFilter.h"
 #include "SkMagnifierImageFilter.h"