Update how sample(matrix) calls are invoked in SkSL

This removes the kMixed type of SkSL::SampleMatrix. All analysis of FP
sampling due to parent-child relationships is tracked in flags on
GrFragmentProcessor now.

The sample strategy is tracked as follows:
- An FP marks itself as using the local coordinate builtin directly (automatically done for .fp code based on reference to sk_TransformedCoords2D[0]).
- This state propagates up the parent towards the root, marking FPs as using coordinates indirectly. We stop the propagation when we hit a parent FP that explicitly samples the child because it becomes the source of the child's coordinates.
   - If that parent references its local coordinates directly, that kicks off its own upwards propagation.
- Being sampled explicitly propagates down to all children, and effectively disables vertex-shader evaluation of transforms.
   - A variable matrix automatically marks this flag as well, since it's essentially a shortcut to (matrix expression) * coords.
- The matrix type also propagates down, but right now that's only for whether or not there's perspective.
   - This doesn't affect FS coord evaluation since each FP applies its action independently.
   - But for VS-promoted transforms, the child's varying may inherit perspective (or other more general matrix types) from the parent and switch from a float2 to a float3.
- A SampleMatrix no longer tracks a base or owner, GrFragmentProcessor exposes its parent FP. An FP's sample matrix is always owned by its immediate parent.
   - This means that you can have a hierarchy from root to leaf like: [uniform, none, none, uses local coords], and that leaf will have a SampleMatrix of kNone type. However, because of parent tracking, the coordinate generation can walk up to the root and detect the proper transform expression it needs to produce, and automatically de-duplicate across children.

Currently, all FP's that are explicitly sampled have a signature of (color, float2 coord). FP's that don't use local coords, or whose coords are promoted to a varying have a signature of (color).
   - In this case, the shader builder either updates args.fLocalCoords to point to the varying directly, or adds a float2 local to the function body that includes the perspective divide.

GrFragmentProcessor automatically pretends it has an identity coord transform if the FP is marked as referencing the local coord builtin. This allows these FPs to still be processed as part of GrGLSLGeometryProcessor::collectTransforms, but removes the need for FP implementations to declare an identity GrCoordTransform.
   - To test this theory, GrTextureEffect and GrSkSLFP no longer have coord transforms explicitly.
   - Later CLs can trivially remove them from a lot of the other effects.
   - The coord generation should not change because it detects in both cases that the coord transform matrices were identity.

GrGLSLGeometryProcessor's collectTransforms and emitTransformCode has been completely overhauled to recurse up an FP's parent pointers and collect the expressions that affect the result. It de-duplicates expressions between siblings, and is able to produce a single varying for the base local coord (either when there are no intervening transforms, or the root FP needs an explicit coordinate to start off with).


This also adds the fp_sample_chaining GM from Brian, with a few more configurations to fill out the cells.

Bug: skia:10396
Change-Id: I86acc0c34c9f29d6371b34370bee9a18c2acf1c1
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/297868
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
index 90f50c2..dc42cf9 100644
--- a/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentProcessor.cpp
@@ -24,33 +24,13 @@
         fFunctionNames.emplace_back();
     }
 
-    // Subtle bug workaround: If an FP (this) has a child, and wishes to sample it, but does not
-    // want to *force* explicit coord sampling, then the obvious solution is to call it with
-    // invokeChild and no coords. However, if this FP is then adopted as a child of another FP that
-    // does want to sample with explicit coords, that property is propagated (recursively) to all
-    // children, and we need to supply explicit coords. So we propagate our own "_coords" (this is
-    // the name of our explicit coords parameter generated in the helper function).
-    if (args.fFp.isSampledWithExplicitCoords() && skslCoords.length() == 0) {
-        skslCoords = "_coords";
+    if (skslCoords.empty()) {
+        // Empty coords means passing through the coords of the parent
+        skslCoords = args.fSampleCoord;
     }
 
     const GrFragmentProcessor& childProc = args.fFp.childProcessor(childIndex);
 
-    // If the fragment processor is invoked with overridden coordinates, it must *always* be invoked
-    // with overridden coords.
-    SkASSERT(childProc.isSampledWithExplicitCoords() == !skslCoords.empty());
-
-    if (skslCoords.length() == 0) {
-        switch (childProc.sampleMatrix().fKind) {
-            case SkSL::SampleMatrix::Kind::kMixed:
-            case SkSL::SampleMatrix::Kind::kVariable:
-                skslCoords = "_matrix";
-                break;
-            default:
-                break;
-        }
-    }
-
     // Emit the child's helper function if this is the first time we've seen a call
     if (fFunctionNames[childIndex].size() == 0) {
         TransformedCoordVars coordVars = args.fTransformedCoords.childInputs(childIndex);
@@ -62,20 +42,28 @@
                            childProc,
                            "_output",
                            "_input",
+                           "_coords",
                            coordVars,
                            textureSamplers);
         fFunctionNames[childIndex] =
                 fragBuilder->writeProcessorFunction(this->childProcessor(childIndex), childArgs);
     }
 
-    // Produce a string containing the call to the helper function
-    SkString result = SkStringPrintf("%s(%s", fFunctionNames[childIndex].c_str(),
-                                              inputColor ? inputColor : "half4(1)");
-    if (skslCoords.length()) {
-        result.appendf(", %s", skslCoords.c_str());
+    if (childProc.isSampledWithExplicitCoords()) {
+        // The child's function takes a half4 color and a float2 coordinate
+        return SkStringPrintf("%s(%s, %s)", fFunctionNames[childIndex].c_str(),
+                                            inputColor ? inputColor : "half4(1)",
+                                            skslCoords.c_str());
+    } else {
+        // The child's function just takes a color; we should only get here for a call to
+        // sample(color) without explicit coordinates, so assert that the child has no sample matrix
+        // and skslCoords is _coords (a const/uniform sample call would go through
+        // invokeChildWithMatrix, and if a child was sampled with sample(matrix) and sample(), it
+        // should have been flagged as variable and hit the branch above).
+        SkASSERT(skslCoords == args.fSampleCoord && childProc.sampleMatrix().isNoOp());
+        return SkStringPrintf("%s(%s)", fFunctionNames[childIndex].c_str(),
+                                        inputColor ? inputColor : "half4(1)");
     }
-    result.append(")");
-    return result;
 }
 
 SkString GrGLSLFragmentProcessor::invokeChildWithMatrix(int childIndex, const char* inputColor,
@@ -99,16 +87,73 @@
                            childProc,
                            "_output",
                            "_input",
+                           "_coords",
                            coordVars,
                            textureSamplers);
         fFunctionNames[childIndex] =
                 fragBuilder->writeProcessorFunction(this->childProcessor(childIndex), childArgs);
     }
 
-    // Produce a string containing the call to the helper function
-    return SkStringPrintf("%s(%s, %s)", fFunctionNames[childIndex].c_str(),
-                                        inputColor ? inputColor : "half4(1)",
-                                        skslMatrix.c_str());
+    // Since this is const/uniform, the provided sksl expression should exactly match the
+    // expression stored on the FP, or it should match the mangled uniform name.
+    if (skslMatrix.empty()) {
+        // Empty matrix expression replaces with the sampleMatrix expression stored on the FP, but
+        // that is only valid for const/uniform sampled FPs
+        SkASSERT(childProc.sampleMatrix().isConstUniform());
+        skslMatrix = childProc.sampleMatrix().fExpression;
+    }
+
+    if (childProc.sampleMatrix().isConstUniform()) {
+        // Attempt to resolve the uniform name from the raw name that was stored in the sample
+        // matrix. Since this is const/uniform, the provided expression better match what was given
+        // to the FP.
+        SkASSERT(childProc.sampleMatrix().fExpression == skslMatrix);
+        GrShaderVar uniform = args.fUniformHandler->getUniformMapping(
+                args.fFp, childProc.sampleMatrix().fExpression);
+        if (uniform.getType() != kVoid_GrSLType) {
+            // Found the uniform, so replace the expression with the actual uniform name
+            SkASSERT(uniform.getType() == kFloat3x3_GrSLType);
+            skslMatrix = uniform.getName().c_str();
+        } // else assume it's a constant expression
+    }
+
+    // Produce a string containing the call to the helper function. sample(matrix) is special where
+    // the provided skslMatrix expression means that the child FP should be invoked with coords
+    // equal to matrix * parent coords. However, if matrix is a constant/uniform AND the parent
+    // coords were produced by const/uniform transforms, then this expression is lifted to a vertex
+    // shader and is stored in a varying. In that case, childProc will not have a variable sample
+    // matrix and will not be sampled explicitly, so its function signature will not take in coords.
+    //
+    // In all other cases, we need to insert sksl to compute matrix * parent coords and then invoke
+    // the function.
+    if (childProc.isSampledWithExplicitCoords()) {
+        SkASSERT(!childProc.sampleMatrix().isNoOp());
+        // Only check perspective for this specific matrix transform, not the aggregate FP property.
+        // Any parent perspective will have already been applied when evaluated in the FS.
+        if (childProc.sampleMatrix().fHasPerspective) {
+            SkString coords3 = fragBuilder->newTmpVarName("coords3");
+            fragBuilder->codeAppendf("float3 %s = (%s) * %s.xy1;\n",
+                                     coords3.c_str(), skslMatrix.c_str(), args.fSampleCoord);
+            return SkStringPrintf("%s(%s, %s.xy / %s.z)",
+                                  fFunctionNames[childIndex].c_str(),
+                                  inputColor ? inputColor : "half4(1)",
+                                  coords3.c_str(), coords3.c_str());
+        } else {
+            return SkStringPrintf("%s(%s, ((%s) * %s.xy1).xy)",
+                                  fFunctionNames[childIndex].c_str(),
+                                  inputColor ? inputColor : "half4(1)",
+                                  skslMatrix.c_str(), args.fSampleCoord);
+        }
+    } else {
+        // A variable matrix expression should mark the child as explicitly sampled. A no-op
+        // matrix should match sample(color), not sample(color, matrix).
+        SkASSERT(childProc.sampleMatrix().isConstUniform());
+
+        // Since this is const/uniform and not explicitly sampled, it's transform has been
+        // promoted to the vertex shader and the signature doesn't take a float2 coord.
+        return SkStringPrintf("%s(%s)", fFunctionNames[childIndex].c_str(),
+                                        inputColor ? inputColor : "half4(1)");
+    }
 }
 
 //////////////////////////////////////////////////////////////////////////////