Add support for directional lights.

Adds a new kDirectionLight_ShadowFlag which indicates that lightPosition
is a vector pointing towards the light, and lightRadius is the blur
radius when the occluder is at z == 1. For other values of z it will
scale linearly. The direction is specified in device space.

Bug: skia:10781
Change-Id: I14d530f006e5541ed6e22bfbeb29c7441c21fb8e
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/321792
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
diff --git a/src/utils/SkShadowUtils.cpp b/src/utils/SkShadowUtils.cpp
index 615b5d7..0c2b21b 100644
--- a/src/utils/SkShadowUtils.cpp
+++ b/src/utils/SkShadowUtils.cpp
@@ -141,7 +141,9 @@
         // The umbra can be dropped where it is occluded.
         kOpaquePartialUmbra,
         // It is known that the entire umbra is occluded.
-        kOpaqueNoUmbra
+        kOpaqueNoUmbra,
+        // The light is directional
+        kDirectional
     };
 
     SkVector fOffset;
@@ -171,6 +173,9 @@
                     return true;
                 }
                 return false;
+            case OccluderType::kDirectional:
+                *translate = that.fOffset - fOffset;
+                return true;
         }
         SK_ABORT("Uninitialized occluder type?");
     }
@@ -178,11 +183,16 @@
     sk_sp<SkVertices> makeVertices(const SkPath& path, const SkMatrix& ctm,
                                    SkVector* translate) const {
         bool transparent = OccluderType::kTransparent == fOccluderType;
+        bool directional = OccluderType::kDirectional == fOccluderType;
         SkPoint3 zParams = SkPoint3::Make(0, 0, fOccluderHeight);
-        if (ctm.hasPerspective() || OccluderType::kOpaquePartialUmbra == fOccluderType) {
+        if (directional) {
             translate->set(0, 0);
-            return SkShadowTessellator::MakeSpot(path, ctm, zParams,
-                                                 fDevLightPos, fLightRadius, transparent);
+            return SkShadowTessellator::MakeSpot(path, ctm, zParams, fDevLightPos, fLightRadius,
+                                                 transparent, true);
+        } else if (ctm.hasPerspective() || OccluderType::kOpaquePartialUmbra == fOccluderType) {
+            translate->set(0, 0);
+            return SkShadowTessellator::MakeSpot(path, ctm, zParams, fDevLightPos, fLightRadius,
+                                                 transparent, false);
         } else {
             // pick a canonical place to generate shadow, with light centered over path
             SkMatrix noTrans(ctm);
@@ -193,7 +203,7 @@
             SkPoint3 centerLightPos = SkPoint3::Make(devCenter.fX, devCenter.fY, fDevLightPos.fZ);
             *translate = fOffset;
             return SkShadowTessellator::MakeSpot(path, noTrans, zParams,
-                                                 centerLightPos, fLightRadius, transparent);
+                                                 centerLightPos, fLightRadius, transparent, false);
         }
     }
 };
@@ -499,13 +509,6 @@
     return !SkScalarNearlyZero(zPlaneParams.fX) || !SkScalarNearlyZero(zPlaneParams.fY);
 }
 
-static SkPoint3 map(const SkMatrix& m, const SkPoint3& pt) {
-    SkPoint3 result;
-    m.mapXY(pt.fX, pt.fY, (SkPoint*)&result.fX);
-    result.fZ = pt.fZ;
-    return result;
-}
-
 void SkShadowUtils::ComputeTonalColors(SkColor inAmbientColor, SkColor inSpotColor,
                                        SkColor* outAmbientColor, SkColor* outSpotColor) {
     // For tonal color we only compute color values for the spot shadow.
@@ -563,18 +566,24 @@
 
 // Draw an offset spot shadow and outlining ambient shadow for the given path.
 void SkShadowUtils::DrawShadow(SkCanvas* canvas, const SkPath& path, const SkPoint3& zPlaneParams,
-                               const SkPoint3& devLightPos, SkScalar lightRadius,
+                               const SkPoint3& lightPos, SkScalar lightRadius,
                                SkColor ambientColor, SkColor spotColor,
                                uint32_t flags) {
-    SkMatrix inverse;
-    if (!canvas->getTotalMatrix().invert(&inverse)) {
-        return;
+
+    SkPoint pt = { lightPos.fX, lightPos.fY };
+    if (!SkToBool(flags & kDirectionalLight_ShadowFlag)) {
+        // If light position is in device space, need to transform to local space
+        // before applying to SkCanvas.
+        SkMatrix inverse;
+        if (!canvas->getTotalMatrix().invert(&inverse)) {
+            return;
+        }
+        inverse.mapPoints(&pt, 1);
     }
-    SkPoint pt = inverse.mapXY(devLightPos.fX, devLightPos.fY);
 
     SkDrawShadowRec rec;
     rec.fZPlaneParams   = zPlaneParams;
-    rec.fLightPos       = { pt.fX, pt.fY, devLightPos.fZ };
+    rec.fLightPos       = { pt.fX, pt.fY, lightPos.fZ };
     rec.fLightRadius    = lightRadius;
     rec.fAmbientColor   = ambientColor;
     rec.fSpotColor      = spotColor;
@@ -614,10 +623,16 @@
 
     bool tiltZPlane = tilted(rec.fZPlaneParams);
     bool transparent = SkToBool(rec.fFlags & SkShadowFlags::kTransparentOccluder_ShadowFlag);
+    bool directional = SkToBool(rec.fFlags & kDirectionalLight_ShadowFlag);
     bool uncached = tiltZPlane || path.isVolatile();
 
     SkPoint3 zPlaneParams = rec.fZPlaneParams;
-    SkPoint3 devLightPos = map(viewMatrix, rec.fLightPos);
+    SkPoint3 devLightPos = rec.fLightPos;
+    if (directional) {
+        ((SkPoint*)&devLightPos.fX)->normalize();
+    } else {
+        viewMatrix.mapPoints((SkPoint*)&devLightPos.fX, 1);
+    }
     float lightRadius = rec.fLightRadius;
 
     if (SkColorGetA(rec.fAmbientColor) > 0) {
@@ -706,7 +721,8 @@
             sk_sp<SkVertices> vertices = SkShadowTessellator::MakeSpot(path, viewMatrix,
                                                                        zPlaneParams,
                                                                        devLightPos, lightRadius,
-                                                                       transparent);
+                                                                       transparent,
+                                                                       directional);
             if (vertices) {
                 SkPaint paint;
                 // Run the vertex color through a GaussianColorFilter and then modulate the
@@ -730,14 +746,24 @@
             factory.fLocalCenter = center;
             viewMatrix.mapPoints(&center, 1);
             SkScalar radius, scale;
-            SkDrawShadowMetrics::GetSpotParams(zPlaneParams.fZ, devLightPos.fX - center.fX,
-                                               devLightPos.fY - center.fY, devLightPos.fZ,
-                                               lightRadius, &radius, &scale, &factory.fOffset);
+            if (SkToBool(rec.fFlags & kDirectionalLight_ShadowFlag)) {
+                SkDrawShadowMetrics::GetDirectionalParams(zPlaneParams.fZ, devLightPos.fX,
+                                                          devLightPos.fY, devLightPos.fZ,
+                                                          lightRadius, &radius, &scale,
+                                                          &factory.fOffset);
+            } else {
+                SkDrawShadowMetrics::GetSpotParams(zPlaneParams.fZ, devLightPos.fX - center.fX,
+                                                   devLightPos.fY - center.fY, devLightPos.fZ,
+                                                   lightRadius, &radius, &scale, &factory.fOffset);
+            }
+
             SkRect devBounds;
             viewMatrix.mapRect(&devBounds, path.getBounds());
-            if (transparent ||
-                SkTAbs(factory.fOffset.fX) > 0.5f*devBounds.width() ||
-                SkTAbs(factory.fOffset.fY) > 0.5f*devBounds.height()) {
+            if (directional) {
+                factory.fOccluderType = SpotVerticesFactory::OccluderType::kDirectional;
+            } else if (transparent ||
+                       SkTAbs(factory.fOffset.fX) > 0.5f*devBounds.width() ||
+                       SkTAbs(factory.fOffset.fY) > 0.5f*devBounds.height()) {
                 // if the translation of the shadow is big enough we're going to end up
                 // filling the entire umbra, so we can treat these as all the same
                 factory.fOccluderType = SpotVerticesFactory::OccluderType::kTransparent;
@@ -765,6 +791,9 @@
                 case SpotVerticesFactory::OccluderType::kOpaqueNoUmbra:
                     color = 0xFFE5E500;  // corn yellow for covered
                     break;
+                case SpotVerticesFactory::OccluderType::kDirectional:
+                    color = 0xFF550000;  // dark red for directional
+                    break;
             }
 #endif
             if (!draw_shadow(factory, drawVertsProc, shadowedPath, color)) {
@@ -772,7 +801,7 @@
                 SkMatrix shadowMatrix;
                 if (!SkDrawShadowMetrics::GetSpotShadowTransform(devLightPos, lightRadius,
                                                                  viewMatrix, zPlaneParams,
-                                                                 path.getBounds(),
+                                                                 path.getBounds(), directional,
                                                                  &shadowMatrix, &radius)) {
                     return;
                 }