Decouple SkShaders from Ganesh backend

This moves all asFragmentProcessor methods from the src/shaders
and src/shaders/gradients into GrFragmentProcessors. As a requirement
to do so, all shaders have had a public header made with the class
declaration. Many classes required new getters to expose some
fields for the construction of the GrFragmentProcessor. I briefly
toyed with the idea of using friend functions, but decided against
it for 2 reasons: 1) the syntax was verbose and gnarly; 2) the
syntax would make the functions of GrFragmentProcessors callable
from other places and not be static / contained to just
GrFragmentProcessors.cpp.

Other notable changes:
 - SkShaderBase::GradientType::kColor removed, as it has been
   superseded by SkShaderBase::ShaderType::kColor for detecting
   "is this a color shader?" Callsites that needed the color
   now cast it to SkColorShader
 - SkPerlinNoiseShader class removed from public API
   (Chromium migrated in https://crrev.com/c/4591270) and
   used internally instead of SkPerlinNoiseShaderImpl for closer
   alignment with the ShaderType enum.
 - SkPerlinNoiseShader::PaintingData no longer uses #ifdefs
   to make the bitmaps for the GPU backends, but this is now
   explicitly done in a function generateBitmaps().
 - SkShaderBase::MatrixRec has been moved to the SkShaders
   namespace and had the Ganesh-specific apply() changed to
   applyForFragmentProcessor(). The Ganesh-parts (e.g. the creation
   of the GrMatrixEffect) were made the responsibility of
   the caller (e.g. in GrFragmentProcessors.cpp).
 - I thought it was strange that SkBitmapProcShader.h (but not
   the .cpp) was in public.bzl. I made that not necessary by
   #ifdef'ing the one include that was necessary removing
   other unnecessary ones. This is because G3 sets
   SK_DISABLE_LEGACY_SHADERCONTEXT

The following shader subclasses did not appear to have Ganesh
implementations and that is still the case:
 - SkEmptyShader
 - SpriteShader (appears SkVM only)
 - DitherShader (appears SkVM only)
 - SkTransformShader
 - SkTriColorShader
 - SkUpdatableColorShader

I enforced IWYU on src/shaders and all other new files I created
in this CL. This had some impact in wider files, but I do not
expect it to impact clients as no public #includes had #includes
removed.

Suggested Review Order:
 - SkShaderBase.h/.cpp, noting that much of the .cpp came from
   SkShader.cpp
 - GrFragmentProcessors.cpp, glancing at the relevant subclass
   .h and .cpp changes as necessary
 - SkPerlinNoise* and GrPerlinNoise* to make sure I didn't overlook
   something in the refactor/extraction
 - Another pass through other changes in src/shaders and
   src/shaders/gradients
 - All other files in any order.


Change-Id: Icf3db955d0653f97d7c793b167463162fb70d9f2
Bug: skia:14317
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/706216
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Kevin Lubick <kjlubick@google.com>
diff --git a/bazel/exporter_tool/main.go b/bazel/exporter_tool/main.go
index a818410..73dd7619 100644
--- a/bazel/exporter_tool/main.go
+++ b/bazel/exporter_tool/main.go
@@ -67,6 +67,8 @@
 				"//src/opts:private_hdrs",
 				"//src/shaders:shader_hdrs",
 				"//src/shaders:shader_srcs",
+				"//src/shaders:sksl_hdrs",
+				"//src/shaders:sksl_srcs",
 				"//src/text:text_hdrs",
 				"//src/text:text_srcs",
 			}},
diff --git a/bench/PerlinNoiseBench.cpp b/bench/PerlinNoiseBench.cpp
index 6c93e46..a809836 100644
--- a/bench/PerlinNoiseBench.cpp
+++ b/bench/PerlinNoiseBench.cpp
@@ -42,9 +42,8 @@
               float baseFrequencyX, float baseFrequencyY, int numOctaves, float seed,
               bool stitchTiles) {
         SkPaint paint;
-        paint.setShader(SkPerlinNoiseShader::MakeFractalNoise(baseFrequencyX, baseFrequencyY,
-                                                              numOctaves, seed,
-                                                              stitchTiles ? &fSize : nullptr));
+        paint.setShader(SkShaders::MakeFractalNoise(
+                baseFrequencyX, baseFrequencyY, numOctaves, seed, stitchTiles ? &fSize : nullptr));
         for (int i = 0; i < loops; i++) {
             this->drawClippedRect(canvas, x, y, paint);
         }
diff --git a/docs/examples/skpaint_compose.cpp b/docs/examples/skpaint_compose.cpp
index 6f624c4..31f9052 100644
--- a/docs/examples/skpaint_compose.cpp
+++ b/docs/examples/skpaint_compose.cpp
@@ -5,11 +5,16 @@
 void draw(SkCanvas* canvas) {
     SkColor colors[2] = {SK_ColorBLUE, SK_ColorYELLOW};
     SkPaint paint;
-    paint.setShader(SkShaders::Blend(
-            SkBlendMode::kDifference,
-            SkGradientShader::MakeRadial(SkPoint::Make(128.0f, 128.0f), 180.0f, colors, nullptr, 2,
-                                         SkTileMode::kClamp, 0, nullptr),
-            SkPerlinNoiseShader::MakeTurbulence(0.025f, 0.025f, 2, 0.0f, nullptr)));
+    paint.setShader(SkShaders::Blend(SkBlendMode::kDifference,
+                                     SkGradientShader::MakeRadial(SkPoint::Make(128.0f, 128.0f),
+                                                                  180.0f,
+                                                                  colors,
+                                                                  nullptr,
+                                                                  2,
+                                                                  SkTileMode::kClamp,
+                                                                  0,
+                                                                  nullptr),
+                                     SkShaders::MakeTurbulence(0.025f, 0.025f, 2, 0.0f, nullptr)));
     canvas->drawPaint(paint);
 }
 }  // END FIDDLE
diff --git a/docs/examples/skpaint_compose_shader.cpp b/docs/examples/skpaint_compose_shader.cpp
index 671d103..c9f03fa 100644
--- a/docs/examples/skpaint_compose_shader.cpp
+++ b/docs/examples/skpaint_compose_shader.cpp
@@ -6,11 +6,16 @@
 void draw(SkCanvas* canvas) {
     SkColor colors[2] = {SK_ColorBLUE, SK_ColorYELLOW};
     SkPaint paint;
-    paint.setShader(SkShaders::Blend(
-            SkBlendMode::kDifference,
-            SkGradientShader::MakeRadial(SkPoint::Make(128.0f, 128.0f), 180.0f, colors, nullptr,
-                                         2, SkTileMode::kClamp, 0, nullptr),
-            SkPerlinNoiseShader::MakeTurbulence(0.025f, 0.025f, 2, 0.0f, nullptr)));
+    paint.setShader(SkShaders::Blend(SkBlendMode::kDifference,
+                                     SkGradientShader::MakeRadial(SkPoint::Make(128.0f, 128.0f),
+                                                                  180.0f,
+                                                                  colors,
+                                                                  nullptr,
+                                                                  2,
+                                                                  SkTileMode::kClamp,
+                                                                  0,
+                                                                  nullptr),
+                                     SkShaders::MakeTurbulence(0.025f, 0.025f, 2, 0.0f, nullptr)));
     canvas->drawPaint(paint);
 }
 }  // END FIDDLE
diff --git a/docs/examples/skpaint_perlin.cpp b/docs/examples/skpaint_perlin.cpp
index e390841..08991d4 100644
--- a/docs/examples/skpaint_perlin.cpp
+++ b/docs/examples/skpaint_perlin.cpp
@@ -5,7 +5,7 @@
 void draw(SkCanvas* canvas) {
     canvas->clear(SK_ColorWHITE);
     SkPaint paint;
-    paint.setShader(SkPerlinNoiseShader::MakeFractalNoise(0.05f, 0.05f, 4, 0.0f, nullptr));
+    paint.setShader(SkShaders::MakeFractalNoise(0.05f, 0.05f, 4, 0.0f, nullptr));
     canvas->drawPaint(paint);
 }
 }  // END FIDDLE
diff --git a/docs/examples/skpaint_turb.cpp b/docs/examples/skpaint_turb.cpp
index 9557959..b652c0c 100644
--- a/docs/examples/skpaint_turb.cpp
+++ b/docs/examples/skpaint_turb.cpp
@@ -5,7 +5,7 @@
 void draw(SkCanvas* canvas) {
     canvas->clear(SK_ColorWHITE);
     SkPaint paint;
-    paint.setShader(SkPerlinNoiseShader::MakeTurbulence(0.05f, 0.05f, 4, 0.0f, nullptr));
+    paint.setShader(SkShaders::MakeTurbulence(0.05f, 0.05f, 4, 0.0f, nullptr));
     canvas->drawPaint(paint);
 }
 }  // END FIDDLE
diff --git a/fuzz/FuzzCanvas.cpp b/fuzz/FuzzCanvas.cpp
index 7f28e15..0e9d794 100644
--- a/fuzz/FuzzCanvas.cpp
+++ b/fuzz/FuzzCanvas.cpp
@@ -328,13 +328,17 @@
             }
             fuzz->nextRange(&numOctaves, 2, 7);
             if (turbulence) {
-                return SkPerlinNoiseShader::MakeTurbulence(baseFrequencyX, baseFrequencyY,
-                                                           numOctaves, seed,
-                                                           useTileSize ? &tileSize : nullptr);
+                return SkShaders::MakeTurbulence(baseFrequencyX,
+                                                 baseFrequencyY,
+                                                 numOctaves,
+                                                 seed,
+                                                 useTileSize ? &tileSize : nullptr);
             } else {
-                return SkPerlinNoiseShader::MakeFractalNoise(baseFrequencyX, baseFrequencyY,
-                                                             numOctaves, seed,
-                                                             useTileSize ? &tileSize : nullptr);
+                return SkShaders::MakeFractalNoise(baseFrequencyX,
+                                                   baseFrequencyY,
+                                                   numOctaves,
+                                                   seed,
+                                                   useTileSize ? &tileSize : nullptr);
             }
         }
         default:
diff --git a/gm/composecolorfilter.cpp b/gm/composecolorfilter.cpp
index ca5c574..2a47ae0 100644
--- a/gm/composecolorfilter.cpp
+++ b/gm/composecolorfilter.cpp
@@ -108,7 +108,7 @@
     // ways (direct and via ::Compose). This ensures the use (or non-use in this case) of the source
     // image is the same across both means of composition.
     auto cf = MakeTintColorFilter(0xff300000, 0xffa00000, /*useSkSL=*/false);
-    auto shader = SkPerlinNoiseShader::MakeTurbulence(0.01f, 0.01f, 2, 0.f);
+    auto shader = SkShaders::MakeTurbulence(0.01f, 0.01f, 2, 0.f);
 
     auto shaderIF = SkImageFilters::Shader(shader, SkImageFilters::Dither::kNo);
     auto directCompose = SkImageFilters::ColorFilter(cf, shaderIF);
diff --git a/gm/constcolorprocessor.cpp b/gm/constcolorprocessor.cpp
index 32c10ac..1aeb0be 100644
--- a/gm/constcolorprocessor.cpp
+++ b/gm/constcolorprocessor.cpp
@@ -32,6 +32,7 @@
 #include "src/gpu/ganesh/GrColor.h"
 #include "src/gpu/ganesh/GrFPArgs.h"
 #include "src/gpu/ganesh/GrFragmentProcessor.h"
+#include "src/gpu/ganesh/GrFragmentProcessors.h"
 #include "src/gpu/ganesh/GrPaint.h"
 #include "src/gpu/ganesh/SkGr.h"
 #include "src/gpu/ganesh/SurfaceDrawContext.h"
@@ -115,7 +116,7 @@
                     GrColorInfo colorInfo;
                     SkSurfaceProps props;
                     GrFPArgs args(rContext, &colorInfo, props);
-                    baseFP = as_SB(fShader)->asRootFragmentProcessor(args, SkMatrix::I());
+                    baseFP = GrFragmentProcessors::Make(fShader.get(), args, SkMatrix::I());
                 } else {
                     baseFP = GrFragmentProcessor::MakeColor(
                             SkPMColor4f::FromBytes_RGBA(kPaintColors[paintType]));
diff --git a/gm/imagefiltersclipped.cpp b/gm/imagefiltersclipped.cpp
index 8a8140d..8ef27a3 100644
--- a/gm/imagefiltersclipped.cpp
+++ b/gm/imagefiltersclipped.cpp
@@ -136,8 +136,8 @@
         }
         canvas->restore();
 
-        sk_sp<SkImageFilter> rectFilter(SkImageFilters::Shader(
-                SkPerlinNoiseShader::MakeFractalNoise(0.1f, 0.05f, 1, 0)));
+        sk_sp<SkImageFilter> rectFilter(
+                SkImageFilters::Shader(SkShaders::MakeFractalNoise(0.1f, 0.05f, 1, 0)));
         canvas->translate(std::size(filters)*(r.width() + margin), 0);
         for (int xOffset = 0; xOffset < 80; xOffset += 16) {
             bounds.fLeft = SkIntToScalar(xOffset);
diff --git a/gm/imagefiltersscaled.cpp b/gm/imagefiltersscaled.cpp
index df40f19..ad8247d 100644
--- a/gm/imagefiltersscaled.cpp
+++ b/gm/imagefiltersscaled.cpp
@@ -97,19 +97,28 @@
         resizeMatrix.setScale(RESIZE_FACTOR, RESIZE_FACTOR);
 
         sk_sp<SkImageFilter> filters[] = {
-            SkImageFilters::Blur(SkIntToScalar(4), SkIntToScalar(4), nullptr),
-            SkImageFilters::DropShadow(5, 10, 3, 3, SK_ColorYELLOW, nullptr),
-            SkImageFilters::DisplacementMap(SkColorChannel::kR, SkColorChannel::kR, 12,
-                                            std::move(gradient), checkerboard),
-            SkImageFilters::Dilate(1, 1, checkerboard),
-            SkImageFilters::Erode(1, 1, checkerboard),
-            SkImageFilters::Offset(SkIntToScalar(32), 0, nullptr),
-            SkImageFilters::MatrixTransform(resizeMatrix, SkSamplingOptions(), nullptr),
-            SkImageFilters::Shader(SkPerlinNoiseShader::MakeFractalNoise(
-                   SkDoubleToScalar(0.1), SkDoubleToScalar(0.05), 1, 0)),
-            SkImageFilters::PointLitDiffuse(pointLocation, white, surfaceScale, kd, nullptr),
-            SkImageFilters::SpotLitDiffuse(spotLocation, spotTarget, spotExponent,
-                                          cutoffAngle, white, surfaceScale, kd, nullptr),
+                SkImageFilters::Blur(SkIntToScalar(4), SkIntToScalar(4), nullptr),
+                SkImageFilters::DropShadow(5, 10, 3, 3, SK_ColorYELLOW, nullptr),
+                SkImageFilters::DisplacementMap(SkColorChannel::kR,
+                                                SkColorChannel::kR,
+                                                12,
+                                                std::move(gradient),
+                                                checkerboard),
+                SkImageFilters::Dilate(1, 1, checkerboard),
+                SkImageFilters::Erode(1, 1, checkerboard),
+                SkImageFilters::Offset(SkIntToScalar(32), 0, nullptr),
+                SkImageFilters::MatrixTransform(resizeMatrix, SkSamplingOptions(), nullptr),
+                SkImageFilters::Shader(SkShaders::MakeFractalNoise(
+                        SkDoubleToScalar(0.1), SkDoubleToScalar(0.05), 1, 0)),
+                SkImageFilters::PointLitDiffuse(pointLocation, white, surfaceScale, kd, nullptr),
+                SkImageFilters::SpotLitDiffuse(spotLocation,
+                                               spotTarget,
+                                               spotExponent,
+                                               cutoffAngle,
+                                               white,
+                                               surfaceScale,
+                                               kd,
+                                               nullptr),
         };
 
         SkVector scales[] = {
diff --git a/gm/perlinnoise.cpp b/gm/perlinnoise.cpp
index 0b8c48c..18879db 100644
--- a/gm/perlinnoise.cpp
+++ b/gm/perlinnoise.cpp
@@ -35,10 +35,16 @@
                              bool stitchTiles,
                              SkISize size) {
     return (type == Type::kFractalNoise)
-        ? SkPerlinNoiseShader::MakeFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves,
-                                                seed, stitchTiles ? &size : nullptr)
-        : SkPerlinNoiseShader::MakeTurbulence(baseFrequencyX, baseFrequencyY, numOctaves,
-                                              seed, stitchTiles ? &size : nullptr);
+                   ? SkShaders::MakeFractalNoise(baseFrequencyX,
+                                                 baseFrequencyY,
+                                                 numOctaves,
+                                                 seed,
+                                                 stitchTiles ? &size : nullptr)
+                   : SkShaders::MakeTurbulence(baseFrequencyX,
+                                               baseFrequencyY,
+                                               numOctaves,
+                                               seed,
+                                               stitchTiles ? &size : nullptr);
 }
 
 class PerlinNoiseGM : public skiagm::GM {
diff --git a/gn/core.gni b/gn/core.gni
index 89edcc9..de72d62 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -144,6 +144,8 @@
 #  //src/opts:private_hdrs
 #  //src/shaders:shader_hdrs
 #  //src/shaders:shader_srcs
+#  //src/shaders:sksl_hdrs
+#  //src/shaders:sksl_srcs
 #  //src/text:text_hdrs
 #  //src/text:text_srcs
 skia_core_sources = [
@@ -627,22 +629,32 @@
   "$_src/opts/SkVM_opts.h",
   "$_src/shaders/SkBitmapProcShader.cpp",
   "$_src/shaders/SkBitmapProcShader.h",
+  "$_src/shaders/SkBlendShader.cpp",
+  "$_src/shaders/SkBlendShader.h",
   "$_src/shaders/SkColorFilterShader.cpp",
   "$_src/shaders/SkColorFilterShader.h",
   "$_src/shaders/SkColorShader.cpp",
-  "$_src/shaders/SkComposeShader.cpp",
+  "$_src/shaders/SkColorShader.h",
   "$_src/shaders/SkCoordClampShader.cpp",
+  "$_src/shaders/SkCoordClampShader.h",
   "$_src/shaders/SkEmptyShader.cpp",
+  "$_src/shaders/SkEmptyShader.h",
   "$_src/shaders/SkGainmapShader.cpp",
   "$_src/shaders/SkImageShader.cpp",
   "$_src/shaders/SkImageShader.h",
   "$_src/shaders/SkLocalMatrixShader.cpp",
   "$_src/shaders/SkLocalMatrixShader.h",
-  "$_src/shaders/SkPerlinNoiseShader.cpp",
+  "$_src/shaders/SkPerlinNoiseShaderImpl.cpp",
+  "$_src/shaders/SkPerlinNoiseShaderImpl.h",
+  "$_src/shaders/SkRuntimeShader.cpp",
+  "$_src/shaders/SkRuntimeShader.h",
   "$_src/shaders/SkShader.cpp",
+  "$_src/shaders/SkShaderBase.cpp",
   "$_src/shaders/SkShaderBase.h",
   "$_src/shaders/SkTransformShader.cpp",
   "$_src/shaders/SkTransformShader.h",
+  "$_src/shaders/SkTriColorShader.cpp",
+  "$_src/shaders/SkTriColorShader.h",
   "$_src/text/GlyphRun.cpp",
   "$_src/text/GlyphRun.h",
   "$_src/text/StrikeForGPU.cpp",
diff --git a/gn/effects.gni b/gn/effects.gni
index ea41a34..5236594 100644
--- a/gn/effects.gni
+++ b/gn/effects.gni
@@ -65,14 +65,16 @@
   "$_src/effects/SkTableMaskFilter.cpp",
   "$_src/effects/SkTrimPE.h",
   "$_src/effects/SkTrimPathEffect.cpp",
-  "$_src/shaders/gradients/SkGradientShader.cpp",
-  "$_src/shaders/gradients/SkGradientShaderBase.cpp",
-  "$_src/shaders/gradients/SkGradientShaderBase.h",
+  "$_src/shaders/gradients/SkConicalGradient.cpp",
+  "$_src/shaders/gradients/SkConicalGradient.h",
+  "$_src/shaders/gradients/SkGradientBaseShader.cpp",
+  "$_src/shaders/gradients/SkGradientBaseShader.h",
   "$_src/shaders/gradients/SkLinearGradient.cpp",
   "$_src/shaders/gradients/SkLinearGradient.h",
   "$_src/shaders/gradients/SkRadialGradient.cpp",
+  "$_src/shaders/gradients/SkRadialGradient.h",
   "$_src/shaders/gradients/SkSweepGradient.cpp",
-  "$_src/shaders/gradients/SkTwoPointConicalGradient.cpp",
+  "$_src/shaders/gradients/SkSweepGradient.h",
 ]
 
 # List generated by Bazel rules:
diff --git a/gn/gpu.gni b/gn/gpu.gni
index 5dd77b8..74bda32 100644
--- a/gn/gpu.gni
+++ b/gn/gpu.gni
@@ -386,6 +386,8 @@
   "$_src/gpu/ganesh/effects/GrModulateAtlasCoverageEffect.h",
   "$_src/gpu/ganesh/effects/GrOvalEffect.cpp",
   "$_src/gpu/ganesh/effects/GrOvalEffect.h",
+  "$_src/gpu/ganesh/effects/GrPerlinNoise2Effect.cpp",
+  "$_src/gpu/ganesh/effects/GrPerlinNoise2Effect.h",
   "$_src/gpu/ganesh/effects/GrPorterDuffXferProcessor.cpp",
   "$_src/gpu/ganesh/effects/GrPorterDuffXferProcessor.h",
   "$_src/gpu/ganesh/effects/GrRRectEffect.cpp",
diff --git a/include/effects/SkPerlinNoiseShader.h b/include/effects/SkPerlinNoiseShader.h
index 2fd20d7..7ef2f1f 100644
--- a/include/effects/SkPerlinNoiseShader.h
+++ b/include/effects/SkPerlinNoiseShader.h
@@ -50,38 +50,4 @@
                                       const SkISize* tileSize = nullptr);
 }  // namespace SkShaders
 
-// TODO(kjlubick) Remove this class when clients have been migrated
-class SK_API SkPerlinNoiseShader {
-public:
-    /**
-     *  This will construct Perlin noise of the given type (Fractal Noise or Turbulence).
-     *
-     *  Both base frequencies (X and Y) have a usual range of (0..1) and must be non-negative.
-     *
-     *  The number of octaves provided should be fairly small, with a limit of 255 enforced.
-     *  Each octave doubles the frequency, so 10 octaves would produce noise from
-     *  baseFrequency * 1, * 2, * 4, ..., * 512, which quickly yields insignificantly small
-     *  periods and resembles regular unstructured noise rather than Perlin noise.
-     *
-     *  If tileSize isn't NULL or an empty size, the tileSize parameter will be used to modify
-     *  the frequencies so that the noise will be tileable for the given tile size. If tileSize
-     *  is NULL or an empty size, the frequencies will be used as is without modification.
-     */
-    static sk_sp<SkShader> MakeFractalNoise(SkScalar baseFrequencyX, SkScalar baseFrequencyY,
-                                            int numOctaves, SkScalar seed,
-                                            const SkISize* tileSize = nullptr) {
-        return SkShaders::MakeFractalNoise(baseFrequencyX, baseFrequencyY,
-                                           numOctaves, seed, tileSize);
-    }
-    static sk_sp<SkShader> MakeTurbulence(SkScalar baseFrequencyX, SkScalar baseFrequencyY,
-                                          int numOctaves, SkScalar seed,
-                                          const SkISize* tileSize = nullptr) {
-        return SkShaders::MakeTurbulence(baseFrequencyX, baseFrequencyY,
-                                           numOctaves, seed, tileSize);
-    }
-
-private:
-    SkPerlinNoiseShader() = delete;
-};
-
 #endif
diff --git a/include/effects/SkRuntimeEffect.h b/include/effects/SkRuntimeEffect.h
index 41918ed..291a70f 100644
--- a/include/effects/SkRuntimeEffect.h
+++ b/include/effects/SkRuntimeEffect.h
@@ -324,7 +324,7 @@
     friend class GrGLSLSkSLFP;         //
 #endif
 
-    friend class SkRTShader;            // fBaseProgram, fMain, fSampleUsages, getRPProgram()
+    friend class SkRuntimeShader;       // fBaseProgram, fMain, fSampleUsages, getRPProgram()
     friend class SkRuntimeBlender;      //
     friend class SkRuntimeColorFilter;  //
 
diff --git a/include/private/SkGainmapShader.h b/include/private/SkGainmapShader.h
index f490ab9..bc53112 100644
--- a/include/private/SkGainmapShader.h
+++ b/include/private/SkGainmapShader.h
@@ -9,6 +9,7 @@
 #define SkGainmapShader_DEFINED
 
 #include "include/core/SkRefCnt.h"
+#include "include/private/base/SkAPI.h"
 
 class SkColorSpace;
 class SkShader;
diff --git a/modules/canvaskit/canvaskit_bindings.cpp b/modules/canvaskit/canvaskit_bindings.cpp
index a34b548..69ba375 100644
--- a/modules/canvaskit/canvaskit_bindings.cpp
+++ b/modules/canvaskit/canvaskit_bindings.cpp
@@ -1935,8 +1935,7 @@
                                                 int tileW, int tileH)->sk_sp<SkShader> {
             // if tileSize is empty (e.g. tileW <= 0 or tileH <= 0, it will be ignored.
             SkISize tileSize = SkISize::Make(tileW, tileH);
-            return SkPerlinNoiseShader::MakeFractalNoise(baseFreqX, baseFreqY,
-                                                         numOctaves, seed, &tileSize);
+            return SkShaders::MakeFractalNoise(baseFreqX, baseFreqY, numOctaves, seed, &tileSize);
         }))
          // Here and in other gradient functions, cPtr is a pointer to an array of data
          // representing colors. whether this is an array of SkColor or SkColor4f is indicated
@@ -2015,8 +2014,7 @@
                                                 int tileW, int tileH)->sk_sp<SkShader> {
             // if tileSize is empty (e.g. tileW <= 0 or tileH <= 0, it will be ignored.
             SkISize tileSize = SkISize::Make(tileW, tileH);
-            return SkPerlinNoiseShader::MakeTurbulence(baseFreqX, baseFreqY,
-                                                       numOctaves, seed, &tileSize);
+            return SkShaders::MakeTurbulence(baseFreqX, baseFreqY, numOctaves, seed, &tileSize);
         }))
         .class_function("_MakeTwoPointConicalGradient", optional_override([](
                                          WASMPointerF32 fourFloatsPtr,
@@ -2037,11 +2035,17 @@
                                                             colors, colorSpace, positions, count, mode,
                                                             flags, &localMatrix);
             } else if (colorType == SkColorType::kRGBA_8888_SkColorType) {
-               const SkColor* colors  = reinterpret_cast<const SkColor*>(cPtr);
-               return SkGradientShader::MakeTwoPointConical(startAndEnd[0], startRadius,
-                                                            startAndEnd[1], endRadius,
-                                                            colors, positions, count, mode,
-                                                            flags, &localMatrix);
+                const SkColor* colors = reinterpret_cast<const SkColor*>(cPtr);
+                return SkGradientShader::MakeTwoPointConical(startAndEnd[0],
+                                                             startRadius,
+                                                             startAndEnd[1],
+                                                             endRadius,
+                                                             colors,
+                                                             positions,
+                                                             count,
+                                                             mode,
+                                                             flags,
+                                                             &localMatrix);
             }
             SkDebugf("%d is not an accepted colorType\n", colorType);
             return nullptr;
diff --git a/modules/svg/src/SkSVGFeTurbulence.cpp b/modules/svg/src/SkSVGFeTurbulence.cpp
index 885ef4a..8357e6e 100644
--- a/modules/svg/src/SkSVGFeTurbulence.cpp
+++ b/modules/svg/src/SkSVGFeTurbulence.cpp
@@ -65,11 +65,11 @@
     sk_sp<SkShader> shader;
     switch (fTurbulenceType.fType) {
         case SkSVGFeTurbulenceType::Type::kTurbulence:
-            shader = SkPerlinNoiseShader::MakeTurbulence(
+            shader = SkShaders::MakeTurbulence(
                     fBaseFrequency.freqX(), fBaseFrequency.freqY(), fNumOctaves, fSeed, tileSize);
             break;
         case SkSVGFeTurbulenceType::Type::kFractalNoise:
-            shader = SkPerlinNoiseShader::MakeFractalNoise(
+            shader = SkShaders::MakeFractalNoise(
                     fBaseFrequency.freqX(), fBaseFrequency.freqY(), fNumOctaves, fSeed, tileSize);
             break;
     }
diff --git a/public.bzl b/public.bzl
index 3a2873e..3fdfef4 100644
--- a/public.bzl
+++ b/public.bzl
@@ -374,7 +374,7 @@
     "src/core/SkBitmapCache.h",
     "src/core/SkBitmapDevice.cpp",
     "src/core/SkBitmapDevice.h",
-    "src/core/SkBitmapProcState.h",
+    "src/core/SkBitmapProcState.h",  # needed for src/opts/SkBitmapProcState_opts.h
     "src/core/SkBlendMode.cpp",
     "src/core/SkBlendModeBlender.cpp",
     "src/core/SkBlendModeBlender.h",
@@ -1076,6 +1076,8 @@
     "src/gpu/ganesh/effects/GrModulateAtlasCoverageEffect.h",
     "src/gpu/ganesh/effects/GrOvalEffect.cpp",
     "src/gpu/ganesh/effects/GrOvalEffect.h",
+    "src/gpu/ganesh/effects/GrPerlinNoise2Effect.cpp",
+    "src/gpu/ganesh/effects/GrPerlinNoise2Effect.h",
     "src/gpu/ganesh/effects/GrPorterDuffXferProcessor.cpp",
     "src/gpu/ganesh/effects/GrPorterDuffXferProcessor.h",
     "src/gpu/ganesh/effects/GrRRectEffect.cpp",
@@ -1426,33 +1428,44 @@
     "src/sfnt/SkPanose.h",
     "src/sfnt/SkSFNTHeader.h",
     "src/sfnt/SkTTCFHeader.h",
-    "src/shaders/SkBitmapProcShader.h",
+    "src/shaders/SkBlendShader.cpp",
+    "src/shaders/SkBlendShader.h",
     "src/shaders/SkColorFilterShader.cpp",
     "src/shaders/SkColorFilterShader.h",
     "src/shaders/SkColorShader.cpp",
-    "src/shaders/SkComposeShader.cpp",
+    "src/shaders/SkColorShader.h",
     "src/shaders/SkCoordClampShader.cpp",
+    "src/shaders/SkCoordClampShader.h",
     "src/shaders/SkEmptyShader.cpp",
+    "src/shaders/SkEmptyShader.h",
     "src/shaders/SkGainmapShader.cpp",
     "src/shaders/SkImageShader.cpp",
     "src/shaders/SkImageShader.h",
     "src/shaders/SkLocalMatrixShader.cpp",
     "src/shaders/SkLocalMatrixShader.h",
-    "src/shaders/SkPerlinNoiseShader.cpp",
+    "src/shaders/SkPerlinNoiseShaderImpl.cpp",
+    "src/shaders/SkPerlinNoiseShaderImpl.h",
     "src/shaders/SkPictureShader.cpp",
     "src/shaders/SkPictureShader.h",
-    "src/shaders/SkShader.cpp",
+    "src/shaders/SkRuntimeShader.cpp",
+    "src/shaders/SkRuntimeShader.h",
+    "src/shaders/SkShaderBase.cpp",
     "src/shaders/SkShaderBase.h",
+    "src/shaders/SkShader.cpp",
     "src/shaders/SkTransformShader.cpp",
     "src/shaders/SkTransformShader.h",
-    "src/shaders/gradients/SkGradientShader.cpp",
-    "src/shaders/gradients/SkGradientShaderBase.cpp",
-    "src/shaders/gradients/SkGradientShaderBase.h",
+    "src/shaders/SkTriColorShader.cpp",
+    "src/shaders/SkTriColorShader.h",
+    "src/shaders/gradients/SkConicalGradient.cpp",
+    "src/shaders/gradients/SkConicalGradient.h",
+    "src/shaders/gradients/SkGradientBaseShader.cpp",
+    "src/shaders/gradients/SkGradientBaseShader.h",
     "src/shaders/gradients/SkLinearGradient.cpp",
     "src/shaders/gradients/SkLinearGradient.h",
     "src/shaders/gradients/SkRadialGradient.cpp",
+    "src/shaders/gradients/SkRadialGradient.h",
     "src/shaders/gradients/SkSweepGradient.cpp",
-    "src/shaders/gradients/SkTwoPointConicalGradient.cpp",
+    "src/shaders/gradients/SkSweepGradient.h",
     "src/sksl/GLSL.std.450.h",
     "src/sksl/SkSLAnalysis.cpp",
     "src/sksl/SkSLAnalysis.h",
diff --git a/src/BUILD.bazel b/src/BUILD.bazel
index 4ead2a0..c196107 100644
--- a/src/BUILD.bazel
+++ b/src/BUILD.bazel
@@ -159,6 +159,7 @@
         "src/codec/SkPixmapUtilsPriv.h",
         "src/core/SkAdvancedTypefaceMetrics.h",
         "src/core/SkColorSpacePriv.h",
+        "src/core/SkCoreBlitters.h",
         "src/core/SkDrawProcs.h",
         "src/core/SkMatrixPriv.h",
         "src/core/SkPathPriv.h",
diff --git a/src/android/SkAndroidFrameworkUtils.cpp b/src/android/SkAndroidFrameworkUtils.cpp
index 39b941a..a70c8f7 100644
--- a/src/android/SkAndroidFrameworkUtils.cpp
+++ b/src/android/SkAndroidFrameworkUtils.cpp
@@ -8,6 +8,7 @@
 #include "include/android/SkAndroidFrameworkUtils.h"
 #include "include/core/SkCanvas.h"
 #include "include/utils/SkPaintFilterCanvas.h"
+#include "src/base/SkTLazy.h"
 #include "src/core/SkDevice.h"
 #include "src/image/SkSurface_Base.h"
 #include "src/shaders/SkShaderBase.h"
diff --git a/src/core/SkCoreBlitters.h b/src/core/SkCoreBlitters.h
index 17f3c70..f00e76e 100644
--- a/src/core/SkCoreBlitters.h
+++ b/src/core/SkCoreBlitters.h
@@ -8,14 +8,25 @@
 #ifndef SkCoreBlitters_DEFINED
 #define SkCoreBlitters_DEFINED
 
+#include "include/core/SkColor.h"
 #include "include/core/SkPaint.h"
+#include "include/core/SkPixmap.h"
+#include "include/core/SkRefCnt.h"
+#include "include/private/base/SkAssert.h"
+#include "include/private/base/SkCPUTypes.h"
 #include "src/core/SkBlitRow.h"
 #include "src/core/SkBlitter.h"
-#include "src/core/SkBlitter_A8.h"
-#include "src/shaders/SkBitmapProcShader.h"
 #include "src/shaders/SkShaderBase.h"
 
+#include <cstdint>
+
+class SkArenaAlloc;
+class SkMatrix;
+class SkRasterPipeline;
+class SkShader;
 class SkSurfaceProps;
+struct SkIRect;
+struct SkMask;
 
 class SkRasterBlitter : public SkBlitter {
 public:
diff --git a/src/core/SkDraw_atlas.cpp b/src/core/SkDraw_atlas.cpp
index 4d82a46..0fd201b 100644
--- a/src/core/SkDraw_atlas.cpp
+++ b/src/core/SkDraw_atlas.cpp
@@ -36,6 +36,7 @@
 #include "src/core/SkScan.h"
 #include "src/core/SkSurfacePriv.h"
 #include "src/core/SkVMBlitter.h"
+#include "src/shaders/SkColorShader.h"
 #include "src/shaders/SkShaderBase.h"
 #include "src/shaders/SkTransformShader.h"
 
@@ -44,7 +45,6 @@
 #include <utility>
 
 class SkBlitter;
-class SkColorSpace;
 enum class SkBlendMode;
 
 #if defined(SK_ENABLE_SKVM)
@@ -77,48 +77,6 @@
     ctx->rgba[3] = SkScalarRoundToInt(rgba[3]*255); ctx->a = rgba[3];
 }
 
-class UpdatableColorShader : public SkShaderBase {
-public:
-    explicit UpdatableColorShader(SkColorSpace* cs)
-        : fSteps{sk_srgb_singleton(), kUnpremul_SkAlphaType, cs, kUnpremul_SkAlphaType} {}
-#if defined(SK_ENABLE_SKVM)
-    skvm::Color program(skvm::Builder* builder,
-                        skvm::Coord device,
-                        skvm::Coord local,
-                        skvm::Color paint,
-                        const MatrixRec&,
-                        const SkColorInfo& dst,
-                        skvm::Uniforms* uniforms,
-                        SkArenaAlloc* alloc) const override {
-        skvm::Uniform color = uniforms->pushPtr(fValues);
-        skvm::F32 r = builder->arrayF(color, 0);
-        skvm::F32 g = builder->arrayF(color, 1);
-        skvm::F32 b = builder->arrayF(color, 2);
-        skvm::F32 a = builder->arrayF(color, 3);
-
-        return {r, g, b, a};
-    }
-#endif
-
-    void updateColor(SkColor c) const {
-        SkColor4f c4 = SkColor4f::FromColor(c);
-        fSteps.apply(c4.vec());
-        auto cp4 = c4.premul();
-        fValues[0] = cp4.fR;
-        fValues[1] = cp4.fG;
-        fValues[2] = cp4.fB;
-        fValues[3] = cp4.fA;
-    }
-
-private:
-    // For serialization.  This will never be called.
-    Factory getFactory() const override { return nullptr; }
-    const char* getTypeName() const override { return nullptr; }
-
-    SkColorSpaceXformSteps fSteps;
-    mutable float fValues[4];
-};
-
 void SkDraw::drawAtlas(const SkRSXform xform[],
                        const SkRect textures[],
                        const SkColor colors[],
@@ -203,10 +161,10 @@
     };
 
     if (!rpblit()) {
-        UpdatableColorShader* colorShader = nullptr;
+        SkUpdatableColorShader* colorShader = nullptr;
         sk_sp<SkShader> shader;
         if (colors) {
-            colorShader = alloc.make<UpdatableColorShader>(fDst.colorSpace());
+            colorShader = alloc.make<SkUpdatableColorShader>(fDst.colorSpace());
             shader = SkShaders::Blend(std::move(blender),
                                       sk_ref_sp(colorShader),
                                       sk_ref_sp(transformShader));
diff --git a/src/core/SkDraw_vertices.cpp b/src/core/SkDraw_vertices.cpp
index 4c7b036..4e48f65 100644
--- a/src/core/SkDraw_vertices.cpp
+++ b/src/core/SkDraw_vertices.cpp
@@ -29,16 +29,12 @@
 #include "include/private/base/SkTo.h"
 #include "src/base/SkArenaAlloc.h"
 #include "src/base/SkTLazy.h"
-#include "src/base/SkVx.h"
 #include "src/core/SkBlenderBase.h"
 #include "src/core/SkConvertPixels.h"
 #include "src/core/SkCoreBlitters.h"
 #include "src/core/SkDraw.h"
-#include "src/core/SkEffectPriv.h"
 #include "src/core/SkMatrixProvider.h"
 #include "src/core/SkRasterClip.h"
-#include "src/core/SkRasterPipeline.h"
-#include "src/core/SkRasterPipelineOpList.h"
 #include "src/core/SkScan.h"
 #include "src/core/SkSurfacePriv.h"
 #include "src/core/SkVMBlitter.h"
@@ -46,6 +42,7 @@
 #include "src/core/SkVerticesPriv.h"
 #include "src/shaders/SkShaderBase.h"
 #include "src/shaders/SkTransformShader.h"
+#include "src/shaders/SkTriColorShader.h"
 
 #include <cstddef>
 #include <cstdint>
@@ -58,41 +55,6 @@
 #include "src/core/SkVM.h"
 #endif
 
-struct Matrix43 {
-    float fMat[12];    // column major
-
-    skvx::float4 map(float x, float y) const {
-        return skvx::float4::Load(&fMat[0]) * x +
-               skvx::float4::Load(&fMat[4]) * y +
-               skvx::float4::Load(&fMat[8]);
-    }
-
-    // Pass a by value, so we don't have to worry about aliasing with this
-    void setConcat(const Matrix43 a, const SkMatrix& b) {
-        SkASSERT(!b.hasPerspective());
-
-        fMat[ 0] = a.dot(0, b.getScaleX(), b.getSkewY());
-        fMat[ 1] = a.dot(1, b.getScaleX(), b.getSkewY());
-        fMat[ 2] = a.dot(2, b.getScaleX(), b.getSkewY());
-        fMat[ 3] = a.dot(3, b.getScaleX(), b.getSkewY());
-
-        fMat[ 4] = a.dot(0, b.getSkewX(), b.getScaleY());
-        fMat[ 5] = a.dot(1, b.getSkewX(), b.getScaleY());
-        fMat[ 6] = a.dot(2, b.getSkewX(), b.getScaleY());
-        fMat[ 7] = a.dot(3, b.getSkewX(), b.getScaleY());
-
-        fMat[ 8] = a.dot(0, b.getTranslateX(), b.getTranslateY()) + a.fMat[ 8];
-        fMat[ 9] = a.dot(1, b.getTranslateX(), b.getTranslateY()) + a.fMat[ 9];
-        fMat[10] = a.dot(2, b.getTranslateX(), b.getTranslateY()) + a.fMat[10];
-        fMat[11] = a.dot(3, b.getTranslateX(), b.getTranslateY()) + a.fMat[11];
-    }
-
-private:
-    float dot(int index, float x, float y) const {
-        return fMat[index + 0] * x + fMat[index + 4] * y;
-    }
-};
-
 static bool SK_WARN_UNUSED_RESULT
 texture_to_matrix(const VertState& state, const SkPoint verts[], const SkPoint texs[],
                   SkMatrix* matrix) {
@@ -107,129 +69,6 @@
     return matrix->setPolyToPoly(src, dst, 3);
 }
 
-class SkTriColorShader : public SkShaderBase {
-public:
-    SkTriColorShader(bool isOpaque, bool usePersp) : fIsOpaque(isOpaque), fUsePersp(usePersp) {}
-
-    // This gets called for each triangle, without re-calling appendStages.
-    bool update(const SkMatrix& ctmInv, const SkPoint pts[], const SkPMColor4f colors[],
-                int index0, int index1, int index2);
-
-protected:
-    bool appendStages(const SkStageRec& rec, const MatrixRec&) const override {
-        rec.fPipeline->append(SkRasterPipelineOp::seed_shader);
-        if (fUsePersp) {
-            rec.fPipeline->append(SkRasterPipelineOp::matrix_perspective, &fM33);
-        }
-        rec.fPipeline->append(SkRasterPipelineOp::matrix_4x3, &fM43);
-        return true;
-    }
-
-#if defined(SK_ENABLE_SKVM)
-    skvm::Color program(skvm::Builder*,
-                        skvm::Coord,
-                        skvm::Coord,
-                        skvm::Color,
-                        const MatrixRec&,
-                        const SkColorInfo&,
-                        skvm::Uniforms*,
-                        SkArenaAlloc*) const override;
-#endif
-
-private:
-    bool isOpaque() const override { return fIsOpaque; }
-    // For serialization.  This will never be called.
-    Factory getFactory() const override { return nullptr; }
-    const char* getTypeName() const override { return nullptr; }
-
-    // If fUsePersp, we need both of these matrices,
-    // otherwise we can combine them, and only use fM43
-
-    Matrix43 fM43;
-    SkMatrix fM33;
-    const bool fIsOpaque;
-    const bool fUsePersp;   // controls our stages, and what we do in update()
-#if defined(SK_ENABLE_SKVM)
-    mutable skvm::Uniform fColorMatrix;
-    mutable skvm::Uniform fCoordMatrix;
-#endif
-
-    using INHERITED = SkShaderBase;
-};
-
-#if defined(SK_ENABLE_SKVM)
-skvm::Color SkTriColorShader::program(skvm::Builder* b,
-                                      skvm::Coord device,
-                                      skvm::Coord local,
-                                      skvm::Color,
-                                      const MatrixRec&,
-                                      const SkColorInfo&,
-                                      skvm::Uniforms* uniforms,
-                                      SkArenaAlloc* alloc) const {
-    fColorMatrix = uniforms->pushPtr(&fM43);
-
-    skvm::F32 x = local.x,
-              y = local.y;
-
-    if (fUsePersp) {
-        fCoordMatrix = uniforms->pushPtr(&fM33);
-        auto dot = [&, x, y](int row) {
-            return b->mad(x, b->arrayF(fCoordMatrix, row),
-                             b->mad(y, b->arrayF(fCoordMatrix, row + 3),
-                                       b->arrayF(fCoordMatrix, row + 6)));
-        };
-
-        x = dot(0);
-        y = dot(1);
-        x = x * (1.0f / dot(2));
-        y = y * (1.0f / dot(2));
-    }
-
-    auto colorDot = [&, x, y](int row) {
-        return b->mad(x, b->arrayF(fColorMatrix, row),
-                         b->mad(y, b->arrayF(fColorMatrix, row + 4),
-                                   b->arrayF(fColorMatrix, row + 8)));
-    };
-
-    skvm::Color color;
-    color.r = colorDot(0);
-    color.g = colorDot(1);
-    color.b = colorDot(2);
-    color.a = colorDot(3);
-    return color;
-}
-#endif
-
-bool SkTriColorShader::update(const SkMatrix& ctmInv, const SkPoint pts[],
-                              const SkPMColor4f colors[], int index0, int index1, int index2) {
-    SkMatrix m, im;
-    m.reset();
-    m.set(0, pts[index1].fX - pts[index0].fX);
-    m.set(1, pts[index2].fX - pts[index0].fX);
-    m.set(2, pts[index0].fX);
-    m.set(3, pts[index1].fY - pts[index0].fY);
-    m.set(4, pts[index2].fY - pts[index0].fY);
-    m.set(5, pts[index0].fY);
-    if (!m.invert(&im)) {
-        return false;
-    }
-
-    fM33.setConcat(im, ctmInv);
-
-    auto c0 = skvx::float4::Load(colors[index0].vec()),
-         c1 = skvx::float4::Load(colors[index1].vec()),
-         c2 = skvx::float4::Load(colors[index2].vec());
-
-    (c1 - c0).store(&fM43.fMat[0]);
-    (c2 - c0).store(&fM43.fMat[4]);
-    c0.store(&fM43.fMat[8]);
-
-    if (!fUsePersp) {
-        fM43.setConcat(fM43, fM33);
-    }
-    return true;
-}
-
 // Convert the SkColors into float colors. The conversion depends on some conditions:
 // - If the pixmap has a dst colorspace, we have to be "color-correct".
 //   Do we map into dst-colorspace before or after we interpolate?
diff --git a/src/core/SkRasterPipelineBlitter.cpp b/src/core/SkRasterPipelineBlitter.cpp
index a0b55ac..cb8d32b 100644
--- a/src/core/SkRasterPipelineBlitter.cpp
+++ b/src/core/SkRasterPipelineBlitter.cpp
@@ -17,6 +17,8 @@
 #include "src/core/SkBlitter.h"
 #include "src/core/SkColorSpacePriv.h"
 #include "src/core/SkColorSpaceXformSteps.h"
+#include "src/core/SkEffectPriv.h"
+#include "src/core/SkMask.h"
 #include "src/core/SkMatrixProvider.h"
 #include "src/core/SkOpts.h"
 #include "src/core/SkRasterPipeline.h"
diff --git a/src/core/SkRuntimeBlender.cpp b/src/core/SkRuntimeBlender.cpp
index e2a3912..c4c0850 100644
--- a/src/core/SkRuntimeBlender.cpp
+++ b/src/core/SkRuntimeBlender.cpp
@@ -81,7 +81,7 @@
                 /*alwaysCopyIntoAlloc=*/false,
                 rec.fDstCS,
                 rec.fAlloc);
-        SkShaderBase::MatrixRec matrix(SkMatrix::I());
+        SkShaders::MatrixRec matrix(SkMatrix::I());
         matrix.markCTMApplied();
         RuntimeEffectRPCallbacks callbacks(rec, matrix, fChildren, fEffect->fSampleUsages);
         bool success = program->appendStages(rec.fPipeline, rec.fAlloc, &callbacks, uniforms);
@@ -110,7 +110,7 @@
                                                                         colorInfo.colorSpace());
     SkASSERT(inputs);
 
-    SkShaderBase::MatrixRec mRec(SkMatrix::I());
+    SkShaders::MatrixRec mRec(SkMatrix::I());
     mRec.markTotalMatrixInvalid();
     RuntimeEffectVMCallbacks callbacks(p, uniforms, alloc, fChildren, mRec, src, colorInfo);
     std::vector<skvm::Val> uniform = SkRuntimeEffectPriv::MakeSkVMUniforms(p,
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index d409c64..fb611b1 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -25,7 +25,6 @@
 #include "include/private/base/SkTArray.h"
 #include "src/base/SkArenaAlloc.h"
 #include "src/base/SkNoDestructor.h"
-#include "src/base/SkTLazy.h"
 #include "src/core/SkBlenderBase.h"
 #include "src/core/SkChecksum.h"
 #include "src/core/SkColorSpacePriv.h"
@@ -33,7 +32,6 @@
 #include "src/core/SkEffectPriv.h"
 #include "src/core/SkFilterColorProgram.h"
 #include "src/core/SkLRUCache.h"
-#include "src/core/SkPicturePriv.h"
 #include "src/core/SkRasterPipeline.h"
 #include "src/core/SkRasterPipelineOpList.h"
 #include "src/core/SkReadBuffer.h"
@@ -44,6 +42,7 @@
 #include "src/effects/colorfilters/SkColorFilterBase.h"
 #include "src/effects/colorfilters/SkRuntimeColorFilter.h"
 #include "src/shaders/SkLocalMatrixShader.h"
+#include "src/shaders/SkRuntimeShader.h"
 #include "src/shaders/SkShaderBase.h"
 #include "src/sksl/SkSLAnalysis.h"
 #include "src/sksl/SkSLBuiltinTypes.h"
@@ -66,7 +65,6 @@
 #include "src/sksl/tracing/SkSLDebugTracePriv.h"
 
 #include <algorithm>
-#include <tuple>
 
 class SkColorSpace;
 struct SkIPoint;
@@ -77,10 +75,6 @@
 #include "include/gpu/GrTypes.h"
 #include "include/gpu/ganesh/SkSurfaceGanesh.h"
 #include "src/gpu/ganesh/GrCaps.h"
-#include "src/gpu/ganesh/GrColorInfo.h"
-#include "src/gpu/ganesh/GrFPArgs.h"
-#include "src/gpu/ganesh/GrFragmentProcessor.h"
-#include "src/gpu/ganesh/GrFragmentProcessors.h"
 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
 #endif
 
@@ -293,7 +287,7 @@
             return as_SB(shader)->appendStages(fStage, fMatrix);
         }
         // For a non-passthrough sample, we need to explicitly mark the total-matrix as invalid.
-        SkShaderBase::MatrixRec nonPassthroughMatrix = fMatrix;
+        SkShaders::MatrixRec nonPassthroughMatrix = fMatrix;
         nonPassthroughMatrix.markTotalMatrixInvalid();
         return as_SB(shader)->appendStages(fStage, nonPassthroughMatrix);
     }
@@ -384,14 +378,6 @@
     SkASSERT(flattenable_is_valid_as_child(fChild.get()));
 }
 
-static sk_sp<SkSL::DebugTracePriv> make_debug_trace(SkRuntimeEffect* effect,
-                                                    const SkIPoint& coord) {
-    auto debugTrace = sk_make_sp<SkSL::DebugTracePriv>();
-    debugTrace->setSource(effect->source());
-    debugTrace->setTraceCoord(coord);
-    return debugTrace;
-}
-
 static ChildType child_type(const SkSL::Type& type) {
     switch (type.typeKind()) {
         case SkSL::Type::TypeKind::kBlender:     return ChildType::kBlender;
@@ -947,248 +933,6 @@
 
 #endif  // defined(SK_ENABLE_SKVM)
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-using UniformsCallback = SkRuntimeEffectPriv::UniformsCallback;
-
-class SkRTShader : public SkShaderBase {
-public:
-    SkRTShader(sk_sp<SkRuntimeEffect> effect,
-               sk_sp<SkSL::DebugTracePriv> debugTrace,
-               sk_sp<const SkData> uniforms,
-               SkSpan<SkRuntimeEffect::ChildPtr> children)
-            : fEffect(std::move(effect))
-            , fDebugTrace(std::move(debugTrace))
-            , fUniformData(std::move(uniforms))
-            , fChildren(children.begin(), children.end()) {}
-
-    SkRTShader(sk_sp<SkRuntimeEffect> effect,
-               sk_sp<SkSL::DebugTracePriv> debugTrace,
-               UniformsCallback uniformsCallback,
-               SkSpan<SkRuntimeEffect::ChildPtr> children)
-            : fEffect(std::move(effect))
-            , fDebugTrace(std::move(debugTrace))
-            , fUniformsCallback(std::move(uniformsCallback))
-            , fChildren(children.begin(), children.end()) {}
-
-    SkRuntimeEffect::TracedShader makeTracedClone(const SkIPoint& coord) {
-        sk_sp<SkRuntimeEffect> unoptimized = fEffect->makeUnoptimizedClone();
-        sk_sp<SkSL::DebugTracePriv> debugTrace = make_debug_trace(unoptimized.get(), coord);
-        auto debugShader = sk_make_sp<SkRTShader>(
-                unoptimized, debugTrace, this->uniformData(nullptr), SkSpan(fChildren));
-
-        return SkRuntimeEffect::TracedShader{std::move(debugShader), std::move(debugTrace)};
-    }
-
-    bool isOpaque() const override { return fEffect->alwaysOpaque(); }
-
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs& args,
-                                                             const MatrixRec& mRec) const override {
-        if (!SkRuntimeEffectPriv::CanDraw(args.fContext->priv().caps(), fEffect.get())) {
-            return nullptr;
-        }
-
-        sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
-                fEffect->uniforms(),
-                this->uniformData(args.fDstColorInfo->colorSpace()),
-                args.fDstColorInfo->colorSpace());
-        SkASSERT(uniforms);
-
-        bool success;
-        std::unique_ptr<GrFragmentProcessor> fp;
-        std::tie(success, fp) = GrFragmentProcessors::make_effect_fp(fEffect,
-                                                                     "runtime_shader",
-                                                                     std::move(uniforms),
-                                                                     /*inputFP=*/nullptr,
-                                                                     /*destColorFP=*/nullptr,
-                                                                     SkSpan(fChildren),
-                                                                     args);
-        if (!success) {
-            return nullptr;
-        }
-
-        std::tie(success, fp) = mRec.apply(std::move(fp));
-        if (!success) {
-            return nullptr;
-        }
-        return fp;
-    }
-#endif
-
-#if defined(SK_GRAPHITE)
-    void addToKey(const skgpu::graphite::KeyContext& keyContext,
-                  skgpu::graphite::PaintParamsKeyBuilder* builder,
-                  skgpu::graphite::PipelineDataGatherer* gatherer) const override {
-        using namespace skgpu::graphite;
-
-        sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
-                fEffect->uniforms(),
-                this->uniformData(keyContext.dstColorInfo().colorSpace()),
-                keyContext.dstColorInfo().colorSpace());
-        SkASSERT(uniforms);
-
-        RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer,
-                                       { fEffect, std::move(uniforms) });
-
-        SkRuntimeEffectPriv::AddChildrenToKey(fChildren, fEffect->children(), keyContext, builder,
-                                              gatherer);
-
-        builder->endBlock();
-    }
-#endif
-
-    bool appendStages(const SkStageRec& rec, const MatrixRec& mRec) const override {
-#ifdef SK_ENABLE_SKSL_IN_RASTER_PIPELINE
-        if (!SkRuntimeEffectPriv::CanDraw(SkCapabilities::RasterBackend().get(), fEffect.get())) {
-            // SkRP has support for many parts of #version 300 already, but for now, we restrict its
-            // usage in runtime effects to just #version 100.
-            return false;
-        }
-        if (const SkSL::RP::Program* program = fEffect->getRPProgram(fDebugTrace.get())) {
-            std::optional<MatrixRec> newMRec = mRec.apply(rec);
-            if (!newMRec.has_value()) {
-                return false;
-            }
-            SkSpan<const float> uniforms = SkRuntimeEffectPriv::UniformsAsSpan(
-                    fEffect->uniforms(),
-                    this->uniformData(rec.fDstCS),
-                    /*alwaysCopyIntoAlloc=*/fUniformData == nullptr,
-                    rec.fDstCS,
-                    rec.fAlloc);
-            RuntimeEffectRPCallbacks callbacks(rec, *newMRec, fChildren, fEffect->fSampleUsages);
-            bool success = program->appendStages(rec.fPipeline, rec.fAlloc, &callbacks, uniforms);
-            return success;
-        }
-#endif
-        return false;
-    }
-
-#if defined(SK_ENABLE_SKVM)
-    skvm::Color program(skvm::Builder* p,
-                        skvm::Coord device,
-                        skvm::Coord local,
-                        skvm::Color paint,
-                        const MatrixRec& mRec,
-                        const SkColorInfo& colorInfo,
-                        skvm::Uniforms* uniforms,
-                        SkArenaAlloc* alloc) const override {
-        if (!SkRuntimeEffectPriv::CanDraw(SkCapabilities::RasterBackend().get(), fEffect.get())) {
-            return {};
-        }
-
-        sk_sp<const SkData> inputs =
-                SkRuntimeEffectPriv::TransformUniforms(fEffect->uniforms(),
-                                                       this->uniformData(colorInfo.colorSpace()),
-                                                       colorInfo.colorSpace());
-        SkASSERT(inputs);
-
-        // Ensure any pending transform is applied before running the runtime shader's code, which
-        // gets to use and manipulate the coordinates.
-        std::optional<MatrixRec> newMRec = mRec.apply(p, &local, uniforms);
-        if (!newMRec.has_value()) {
-            return {};
-        }
-        // We could omit this for children that are only sampled with passthrough coords.
-        newMRec->markTotalMatrixInvalid();
-
-        RuntimeEffectVMCallbacks callbacks(p,
-                                           uniforms,
-                                           alloc,
-                                           fChildren,
-                                           *newMRec,
-                                           paint,
-                                           colorInfo);
-        std::vector<skvm::Val> uniform = SkRuntimeEffectPriv::MakeSkVMUniforms(
-                p, uniforms, fEffect->uniformSize(), *inputs);
-
-        return SkSL::ProgramToSkVM(*fEffect->fBaseProgram, fEffect->fMain, p, fDebugTrace.get(),
-                                   SkSpan(uniform), device, local, paint, paint, &callbacks);
-    }
-#endif
-
-    void flatten(SkWriteBuffer& buffer) const override {
-        buffer.writeString(fEffect->source().c_str());
-        buffer.writeDataAsByteArray(this->uniformData(nullptr).get());
-        SkRuntimeEffectPriv::WriteChildEffects(buffer, fChildren);
-    }
-
-    SkRuntimeEffect* asRuntimeEffect() const override { return fEffect.get(); }
-
-    SK_FLATTENABLE_HOOKS(SkRTShader)
-
-private:
-    enum Flags {
-        kHasLegacyLocalMatrix_Flag = 1 << 1,
-    };
-
-    sk_sp<const SkData> uniformData(const SkColorSpace* dstCS) const {
-        if (fUniformData) {
-            return fUniformData;
-        }
-
-        // We want to invoke the uniforms-callback each time a paint occurs.
-        SkASSERT(fUniformsCallback);
-        sk_sp<const SkData> uniforms = fUniformsCallback({dstCS});
-        SkASSERT(uniforms && uniforms->size() == fEffect->uniformSize());
-        return uniforms;
-    }
-
-    sk_sp<SkRuntimeEffect> fEffect;
-    sk_sp<SkSL::DebugTracePriv> fDebugTrace;
-    sk_sp<const SkData> fUniformData;
-    UniformsCallback fUniformsCallback;
-    std::vector<SkRuntimeEffect::ChildPtr> fChildren;
-};
-
-sk_sp<SkFlattenable> SkRTShader::CreateProc(SkReadBuffer& buffer) {
-    if (!buffer.validate(buffer.allowSkSL())) {
-        return nullptr;
-    }
-
-    SkString sksl;
-    buffer.readString(&sksl);
-    sk_sp<SkData> uniforms = buffer.readByteArrayAsData();
-
-    SkTLazy<SkMatrix> localM;
-    if (buffer.isVersionLT(SkPicturePriv::kNoShaderLocalMatrix)) {
-        uint32_t flags = buffer.read32();
-        if (flags & kHasLegacyLocalMatrix_Flag) {
-            buffer.readMatrix(localM.init());
-        }
-    }
-
-    auto effect = SkMakeCachedRuntimeEffect(SkRuntimeEffect::MakeForShader, std::move(sksl));
-#if !SK_LENIENT_SKSL_DESERIALIZATION
-    if (!buffer.validate(effect != nullptr)) {
-        return nullptr;
-    }
-#endif
-
-    STArray<4, SkRuntimeEffect::ChildPtr> children;
-    if (!SkRuntimeEffectPriv::ReadChildEffects(buffer, effect.get(), &children)) {
-        return nullptr;
-    }
-
-#if SK_LENIENT_SKSL_DESERIALIZATION
-    if (!effect) {
-        // If any children were SkShaders, return the first one. This is a reasonable fallback.
-        for (int i = 0; i < children.size(); i++) {
-            if (children[i].shader()) {
-                SkDebugf("Serialized SkSL failed to compile. Replacing shader with child %d.\n", i);
-                return sk_ref_sp(children[i].shader());
-            }
-        }
-
-        // We don't know what to do, so just return nullptr (but *don't* poison the buffer).
-        SkDebugf("Serialized SkSL failed to compile. Ignoring/dropping SkSL shader.\n");
-        return nullptr;
-    }
-#endif
-
-    return effect->makeShader(std::move(uniforms), SkSpan(children), localM.getMaybeNull());
-}
-
 sk_sp<SkShader> SkRuntimeEffectPriv::MakeDeferredShader(const SkRuntimeEffect* effect,
                                                         UniformsCallback uniformsCallback,
                                                         SkSpan<SkRuntimeEffect::ChildPtr> children,
@@ -1202,11 +946,11 @@
     if (!uniformsCallback) {
         return nullptr;
     }
-    return SkLocalMatrixShader::MakeWrapped<SkRTShader>(localMatrix,
-                                                        sk_ref_sp(effect),
-                                                        /*debugTrace=*/nullptr,
-                                                        std::move(uniformsCallback),
-                                                        children);
+    return SkLocalMatrixShader::MakeWrapped<SkRuntimeShader>(localMatrix,
+                                                             sk_ref_sp(effect),
+                                                             /*debugTrace=*/nullptr,
+                                                             std::move(uniformsCallback),
+                                                             children);
 }
 
 sk_sp<SkShader> SkRuntimeEffect::makeShader(sk_sp<const SkData> uniforms,
@@ -1235,11 +979,11 @@
     if (uniforms->size() != this->uniformSize()) {
         return nullptr;
     }
-    return SkLocalMatrixShader::MakeWrapped<SkRTShader>(localMatrix,
-                                                        sk_ref_sp(this),
-                                                        /*debugTrace=*/nullptr,
-                                                        std::move(uniforms),
-                                                        children);
+    return SkLocalMatrixShader::MakeWrapped<SkRuntimeShader>(localMatrix,
+                                                             sk_ref_sp(this),
+                                                             /*debugTrace=*/nullptr,
+                                                             std::move(uniforms),
+                                                             children);
 }
 
 sk_sp<SkImage> SkRuntimeEffect::makeImage(GrRecordingContext* rContext,
@@ -1340,8 +1084,8 @@
     if (!effect) {
         return TracedShader{nullptr, nullptr};
     }
-    // An SkShader with an attached SkRuntimeEffect must be an SkRTShader.
-    SkRTShader* rtShader = static_cast<SkRTShader*>(shader.get());
+    // An SkShader with an attached SkRuntimeEffect must be an SkRuntimeShader.
+    SkRuntimeShader* rtShader = static_cast<SkRuntimeShader*>(shader.get());
     return rtShader->makeTracedClone(traceCoord);
 }
 
@@ -1384,9 +1128,12 @@
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 void SkRuntimeEffect::RegisterFlattenables() {
-    SK_REGISTER_FLATTENABLE(SkRuntimeColorFilter);
-    SK_REGISTER_FLATTENABLE(SkRTShader);
     SK_REGISTER_FLATTENABLE(SkRuntimeBlender);
+    SK_REGISTER_FLATTENABLE(SkRuntimeColorFilter);
+    SK_REGISTER_FLATTENABLE(SkRuntimeShader);
+
+    // Previous name
+    SkFlattenable::Register("SkRTShader", SkRuntimeShader::CreateProc);
 }
 
 SkRuntimeShaderBuilder::SkRuntimeShaderBuilder(sk_sp<SkRuntimeEffect> effect)
diff --git a/src/core/SkRuntimeEffectPriv.h b/src/core/SkRuntimeEffectPriv.h
index 47d3c4f..16c968c 100644
--- a/src/core/SkRuntimeEffectPriv.h
+++ b/src/core/SkRuntimeEffectPriv.h
@@ -15,7 +15,6 @@
 #include "include/private/base/SkAssert.h"
 #include "include/private/base/SkSpan_impl.h"
 #include "include/private/base/SkTArray.h"
-#include "src/shaders/SkShaderBase.h"
 
 #include <cstddef>
 #include <cstdint>
@@ -31,26 +30,38 @@
 #endif
 
 #ifdef SK_ENABLE_SKVM
+#include "include/core/SkImageInfo.h"
 #include "src/sksl/codegen/SkSLVMCodeGenerator.h"
 #endif
 
 class SkArenaAlloc;
+class SkCapabilities;
 class SkColorSpace;
 class SkData;
 class SkMatrix;
 class SkReadBuffer;
 class SkShader;
 class SkWriteBuffer;
+struct SkColorSpaceXformSteps;
 struct SkStageRec;
 
+namespace SkShaders {
+class MatrixRec;
+}
+
 namespace SkSL {
 class Context;
 class Variable;
 struct Program;
 }
 
-class SkCapabilities;
-struct SkColorSpaceXformSteps;
+#if defined(SK_GRAPHITE)
+namespace skgpu::graphite {
+class KeyContext;
+class PaintParamsKeyBuilder;
+class PipelineDataGatherer;
+}  // namespace skgpu::graphite
+#endif
 
 class SkRuntimeEffectPriv {
 public:
@@ -190,7 +201,7 @@
 class RuntimeEffectRPCallbacks : public SkSL::RP::Callbacks {
 public:
     RuntimeEffectRPCallbacks(const SkStageRec& s,
-                             const SkShaderBase::MatrixRec& m,
+                             const SkShaders::MatrixRec& m,
                              SkSpan<const SkRuntimeEffect::ChildPtr> c,
                              SkSpan<const SkSL::SampleUsage> u)
             : fStage(s), fMatrix(m), fChildren(c), fSampleUsages(u) {}
@@ -209,7 +220,7 @@
     void applyColorSpaceXform(const SkColorSpaceXformSteps& tempXform, const void* color);
 
     const SkStageRec& fStage;
-    const SkShaderBase::MatrixRec& fMatrix;
+    const SkShaders::MatrixRec& fMatrix;
     SkSpan<const SkRuntimeEffect::ChildPtr> fChildren;
     SkSpan<const SkSL::SampleUsage> fSampleUsages;
 };
@@ -222,7 +233,7 @@
                              skvm::Uniforms* uniforms,
                              SkArenaAlloc* alloc,
                              const std::vector<SkRuntimeEffect::ChildPtr>& children,
-                             const SkShaderBase::MatrixRec& mRec,
+                             const SkShaders::MatrixRec& mRec,
                              skvm::Color inColor,
                              const SkColorInfo& colorInfo)
             : fBuilder(builder)
@@ -247,7 +258,7 @@
     skvm::Uniforms* fUniforms;
     SkArenaAlloc* fAlloc;
     const std::vector<SkRuntimeEffect::ChildPtr>& fChildren;
-    const SkShaderBase::MatrixRec& fMRec;
+    const SkShaders::MatrixRec& fMRec;
     const skvm::Color fInColor;
     const SkColorInfo& fColorInfo;
 };
diff --git a/src/core/SkTextBlob.cpp b/src/core/SkTextBlob.cpp
index b1dadfd..fd3d29d 100644
--- a/src/core/SkTextBlob.cpp
+++ b/src/core/SkTextBlob.cpp
@@ -10,6 +10,7 @@
 #include "include/core/SkRSXform.h"
 #include "include/core/SkTypeface.h"
 #include "src/base/SkSafeMath.h"
+#include "src/base/SkTLazy.h"
 #include "src/core/SkFontPriv.h"
 #include "src/core/SkPaintPriv.h"
 #include "src/core/SkReadBuffer.h"
diff --git a/src/core/SkTextBlobTrace.cpp b/src/core/SkTextBlobTrace.cpp
index 0e90c57..cb51825 100644
--- a/src/core/SkTextBlobTrace.cpp
+++ b/src/core/SkTextBlobTrace.cpp
@@ -4,6 +4,7 @@
 #include "src/core/SkTextBlobTrace.h"
 
 #include "include/core/SkTextBlob.h"
+#include "src/base/SkTLazy.h"
 #include "src/core/SkFontPriv.h"
 #include "src/core/SkPtrRecorder.h"
 #include "src/core/SkReadBuffer.h"
diff --git a/src/core/SkVMBlitter.cpp b/src/core/SkVMBlitter.cpp
index a8ffda5..4121fe8 100644
--- a/src/core/SkVMBlitter.cpp
+++ b/src/core/SkVMBlitter.cpp
@@ -5,6 +5,9 @@
  * found in the LICENSE file.
  */
 
+#include "src/core/SkVMBlitter.h"
+
+#include "include/core/SkBlender.h"
 #include "include/private/base/SkMacros.h"
 #include "src/base/SkArenaAlloc.h"
 #include "src/core/SkBlendModePriv.h"
@@ -15,12 +18,13 @@
 #include "src/core/SkCoreBlitters.h"
 #include "src/core/SkImageInfoPriv.h"
 #include "src/core/SkLRUCache.h"
+#include "src/core/SkMask.h"
 #include "src/core/SkMatrixProvider.h"
 #include "src/core/SkPaintPriv.h"
 #include "src/core/SkVM.h"
-#include "src/core/SkVMBlitter.h"
 #include "src/effects/colorfilters/SkColorFilterBase.h"
 #include "src/shaders/SkColorFilterShader.h"
+#include "src/shaders/SkEmptyShader.h"
 
 #include <cinttypes>
 
@@ -62,7 +66,7 @@
         const char* getTypeName() const override { return "NoopColorFilter"; }
     };
 
-    struct SpriteShader : public SkShaderBase {
+    struct SpriteShader : public SkEmptyShader {
         explicit SpriteShader(SkPixmap sprite) : fSprite(sprite) {}
 
         SkPixmap fSprite;
@@ -77,7 +81,7 @@
                             skvm::Coord /*device*/,
                             skvm::Coord /*local*/,
                             skvm::Color /*paint*/,
-                            const MatrixRec&,
+                            const SkShaders::MatrixRec&,
                             const SkColorInfo& dst,
                             skvm::Uniforms* uniforms,
                             SkArenaAlloc*) const override {
@@ -91,7 +95,7 @@
         }
     };
 
-    struct DitherShader : public SkShaderBase {
+    struct DitherShader : public SkEmptyShader {
         explicit DitherShader(sk_sp<SkShader> shader) : fShader(std::move(shader)) {}
 
         sk_sp<SkShader> fShader;
@@ -106,7 +110,7 @@
                             skvm::Coord device,
                             skvm::Coord local,
                             skvm::Color paint,
-                            const MatrixRec& mRec,
+                            const SkShaders::MatrixRec& mRec,
                             const SkColorInfo& dst,
                             skvm::Uniforms* uniforms,
                             SkArenaAlloc* alloc) const override {
diff --git a/src/core/SkVMBlitter.h b/src/core/SkVMBlitter.h
index 9cf0eb7..c1cf7ff 100644
--- a/src/core/SkVMBlitter.h
+++ b/src/core/SkVMBlitter.h
@@ -8,6 +8,8 @@
 #ifndef SkVMBlitter_DEFINED
 #define SkVMBlitter_DEFINED
 
+#include "include/core/SkBlender.h"
+#include "include/core/SkMatrix.h"
 #include "include/core/SkPixmap.h"
 #include "src/base/SkArenaAlloc.h"
 #include "src/base/SkTLazy.h"
diff --git a/src/effects/colorfilters/SkColorFilterBase.h b/src/effects/colorfilters/SkColorFilterBase.h
index 4f6cc23..998654d 100644
--- a/src/effects/colorfilters/SkColorFilterBase.h
+++ b/src/effects/colorfilters/SkColorFilterBase.h
@@ -47,16 +47,6 @@
     M(Table)                    \
     M(WorkingFormat)
 
-#define SK_ALL_COLOR_FILTERS(M) \
-    M(BlendMode)                \
-    M(ColorSpaceXform)          \
-    M(Compose)                  \
-    M(Gaussian)                 \
-    M(Matrix)                   \
-    M(Runtime)                  \
-    M(Table)                    \
-    M(WorkingFormat)
-
 class SkColorFilterBase : public SkColorFilter {
 public:
     SK_WARN_UNUSED_RESULT
diff --git a/src/effects/colorfilters/SkRuntimeColorFilter.cpp b/src/effects/colorfilters/SkRuntimeColorFilter.cpp
index 1270e67..c5d9ae9 100644
--- a/src/effects/colorfilters/SkRuntimeColorFilter.cpp
+++ b/src/effects/colorfilters/SkRuntimeColorFilter.cpp
@@ -90,7 +90,7 @@
                                                     /*alwaysCopyIntoAlloc=*/false,
                                                     rec.fDstCS,
                                                     rec.fAlloc);
-        SkShaderBase::MatrixRec matrix(SkMatrix::I());
+        SkShaders::MatrixRec matrix(SkMatrix::I());
         matrix.markCTMApplied();
         RuntimeEffectRPCallbacks callbacks(rec, matrix, fChildren, fEffect->fSampleUsages);
         bool success = program->appendStages(rec.fPipeline, rec.fAlloc, &callbacks, uniforms);
@@ -112,7 +112,7 @@
             fEffect->uniforms(), fUniforms, colorInfo.colorSpace());
     SkASSERT(inputs);
 
-    SkShaderBase::MatrixRec mRec(SkMatrix::I());
+    SkShaders::MatrixRec mRec(SkMatrix::I());
     mRec.markTotalMatrixInvalid();
     RuntimeEffectVMCallbacks callbacks(p, uniforms, alloc, fChildren, mRec, c, colorInfo);
     std::vector<skvm::Val> uniform =
diff --git a/src/gpu/ganesh/ClipStack.cpp b/src/gpu/ganesh/ClipStack.cpp
index 96a279a..f85afa1 100644
--- a/src/gpu/ganesh/ClipStack.cpp
+++ b/src/gpu/ganesh/ClipStack.cpp
@@ -20,6 +20,7 @@
 #include "src/gpu/ganesh/GrDirectContextPriv.h"
 #include "src/gpu/ganesh/GrFPArgs.h"
 #include "src/gpu/ganesh/GrFragmentProcessor.h"
+#include "src/gpu/ganesh/GrFragmentProcessors.h"
 #include "src/gpu/ganesh/GrProxyProvider.h"
 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
 #include "src/gpu/ganesh/GrSWMaskHelper.h"
@@ -1301,8 +1302,7 @@
         static const GrColorInfo kCoverageColorInfo{GrColorType::kUnknown, kPremul_SkAlphaType,
                                                     nullptr};
         GrFPArgs args(rContext, &kCoverageColorInfo, sdc->surfaceProps());
-        clipFP = as_SB(cs.shader())->asRootFragmentProcessor(args,
-                                                             fMatrixProvider->localToDevice());
+        clipFP = GrFragmentProcessors::Make(cs.shader(), args, fMatrixProvider->localToDevice());
         if (clipFP) {
             // The initial input is the coverage from the geometry processor, so this ensures it
             // is multiplied properly with the alpha of the clip shader.
diff --git a/src/gpu/ganesh/Device_drawTexture.cpp b/src/gpu/ganesh/Device_drawTexture.cpp
index daeaadb..db0a759 100644
--- a/src/gpu/ganesh/Device_drawTexture.cpp
+++ b/src/gpu/ganesh/Device_drawTexture.cpp
@@ -361,7 +361,8 @@
             std::move(fp), image->imageInfo().colorInfo(), sdc->colorInfo());
     if (image->isAlphaOnly()) {
         if (const auto* shader = as_SB(paint.getShader())) {
-            auto shaderFP = shader->asRootFragmentProcessor(
+            auto shaderFP = GrFragmentProcessors::Make(
+                    shader,
                     GrFPArgs(rContext, &sdc->colorInfo(), sdc->surfaceProps()),
                     matrixProvider.localToDevice());
             if (!shaderFP) {
diff --git a/src/gpu/ganesh/GrFragmentProcessors.cpp b/src/gpu/ganesh/GrFragmentProcessors.cpp
index cd2d5ee..1af581e 100644
--- a/src/gpu/ganesh/GrFragmentProcessors.cpp
+++ b/src/gpu/ganesh/GrFragmentProcessors.cpp
@@ -11,15 +11,27 @@
 #include "include/core/SkBlendMode.h"
 #include "include/core/SkColor.h"
 #include "include/core/SkColorSpace.h"
+#include "include/core/SkColorType.h"
 #include "include/core/SkData.h"
+#include "include/core/SkImage.h"
+#include "include/core/SkImageInfo.h"
 #include "include/core/SkMatrix.h"
+#include "include/core/SkPicture.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkRect.h"
 #include "include/core/SkRefCnt.h"
+#include "include/core/SkSize.h"
 #include "include/effects/SkRuntimeEffect.h"
+#include "include/gpu/GpuTypes.h"
 #include "include/gpu/GrRecordingContext.h"
+#include "include/gpu/GrTypes.h"
+#include "include/gpu/ganesh/SkSurfaceGanesh.h"
 #include "include/private/SkColorData.h"
 #include "include/private/base/SkAssert.h"
 #include "include/private/base/SkDebug.h"
 #include "include/private/base/SkTArray.h"
+#include "include/private/gpu/ganesh/GrTypesPriv.h"
+#include "src/base/SkTLazy.h"
 #include "src/core/SkBlendModeBlender.h"
 #include "src/core/SkBlenderBase.h"
 #include "src/core/SkColorSpacePriv.h"
@@ -37,22 +49,58 @@
 #include "src/effects/colorfilters/SkRuntimeColorFilter.h"
 #include "src/effects/colorfilters/SkTableColorFilter.h"
 #include "src/effects/colorfilters/SkWorkingFormatColorFilter.h"
+#include "src/gpu/ResourceKey.h"
+#include "src/gpu/Swizzle.h"
 #include "src/gpu/ganesh/GrCaps.h"
 #include "src/gpu/ganesh/GrColorInfo.h"
 #include "src/gpu/ganesh/GrColorSpaceXform.h"
 #include "src/gpu/ganesh/GrFPArgs.h"
 #include "src/gpu/ganesh/GrFragmentProcessor.h"
+#include "src/gpu/ganesh/GrProxyProvider.h"
 #include "src/gpu/ganesh/GrRecordingContextPriv.h"
+#include "src/gpu/ganesh/GrSamplerState.h"
+#include "src/gpu/ganesh/GrShaderCaps.h"
+#include "src/gpu/ganesh/GrSurfaceProxy.h"
+#include "src/gpu/ganesh/GrSurfaceProxyView.h"
+#include "src/gpu/ganesh/GrTextureProxy.h"
+#include "src/gpu/ganesh/SkGr.h"
 #include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h"
 #include "src/gpu/ganesh/effects/GrColorTableEffect.h"
+#include "src/gpu/ganesh/effects/GrMatrixEffect.h"
+#include "src/gpu/ganesh/effects/GrPerlinNoise2Effect.h"
 #include "src/gpu/ganesh/effects/GrSkSLFP.h"
+#include "src/gpu/ganesh/effects/GrTextureEffect.h"
+#include "src/gpu/ganesh/gradients/GrGradientShader.h"
+#include "src/gpu/ganesh/image/GrImageUtils.h"
+#include "src/shaders/SkBlendShader.h"
+#include "src/shaders/SkColorFilterShader.h"
+#include "src/shaders/SkColorShader.h"
+#include "src/shaders/SkCoordClampShader.h"
+#include "src/shaders/SkEmptyShader.h"
+#include "src/shaders/SkImageShader.h"
+#include "src/shaders/SkLocalMatrixShader.h"
+#include "src/shaders/SkPerlinNoiseShaderImpl.h"
+#include "src/shaders/SkPictureShader.h"
+#include "src/shaders/SkRuntimeShader.h"
 #include "src/shaders/SkShaderBase.h"
+#include "src/shaders/SkTransformShader.h"
+#include "src/shaders/SkTriColorShader.h"
+#include "src/shaders/gradients/SkConicalGradient.h"
+#include "src/shaders/gradients/SkGradientBaseShader.h"
+#include "src/shaders/gradients/SkLinearGradient.h"
+#include "src/shaders/gradients/SkRadialGradient.h"
+#include "src/shaders/gradients/SkSweepGradient.h"
 
+#include <cstdint>
+#include <cstring>
 #include <memory>
 #include <optional>
 #include <utility>
 #include <vector>
 
+class SkBitmap;
+enum class SkTileMode;
+
 namespace GrFragmentProcessors {
 static std::unique_ptr<GrFragmentProcessor>
         make_fp_from_shader_mask_filter(const SkMaskFilterBase* maskfilter,
@@ -60,7 +108,7 @@
                                         const SkMatrix& ctm) {
     SkASSERT(maskfilter);
     auto shaderMF = static_cast<const SkShaderMaskFilterImpl*>(maskfilter);
-    auto fp = as_SB(shaderMF->shader())->asFragmentProcessor(args, SkShaderBase::MatrixRec(ctm));
+    auto fp = Make(shaderMF->shader().get(), args, ctm);
     return GrFragmentProcessor::MulInputByChildAlpha(std::move(fp));
 }
 
@@ -83,6 +131,23 @@
     SkUNREACHABLE;
 }
 
+bool IsSupported(const SkMaskFilter* maskfilter) {
+    if (!maskfilter) {
+        return false;
+    }
+    auto mfb = as_MFB(maskfilter);
+    switch (mfb->type()) {
+        case SkMaskFilterBase::Type::kShader:
+            return true;
+        case SkMaskFilterBase::Type::kBlur:
+        case SkMaskFilterBase::Type::kEmboss:
+        case SkMaskFilterBase::Type::kSDF:
+        case SkMaskFilterBase::Type::kTable:
+            return false;
+    }
+    SkUNREACHABLE;
+}
+
 using ChildType = SkRuntimeEffect::ChildType;
 
 GrFPResult make_effect_fp(sk_sp<SkRuntimeEffect> effect,
@@ -97,9 +162,9 @@
         std::optional<ChildType> type = child.type();
         if (type == ChildType::kShader) {
             // Convert a SkShader into a child FP.
-            SkShaderBase::MatrixRec mRec(SkMatrix::I());
+            SkShaders::MatrixRec mRec(SkMatrix::I());
             mRec.markTotalMatrixInvalid();
-            auto childFP = as_SB(child.shader())->asFragmentProcessor(childArgs, mRec);
+            auto childFP = Make(child.shader(), childArgs, mRec);
             if (!childFP) {
                 return GrFPFailure(std::move(inputFP));
             }
@@ -418,20 +483,598 @@
     SkUNREACHABLE;
 }
 
-bool IsSupported(const SkMaskFilter* maskfilter) {
-    if (!maskfilter) {
-        return false;
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkBlendShader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    auto fpA = Make(shader->dst().get(), args, mRec);
+    auto fpB = Make(shader->src().get(), args, mRec);
+    if (!fpA || !fpB) {
+        // This is unexpected. Both src and dst shaders should be valid. Just fail.
+        SkDEBUGFAIL("Both src and dst shaders in blend should be valid but are not.");
+        return nullptr;
     }
-    auto mfb = as_MFB(maskfilter);
-    switch (mfb->type()) {
-        case SkMaskFilterBase::Type::kShader:
-            return true;
-        case SkMaskFilterBase::Type::kBlur:
-        case SkMaskFilterBase::Type::kEmboss:
-        case SkMaskFilterBase::Type::kSDF:
-        case SkMaskFilterBase::Type::kTable:
-            return false;
+    return GrBlendFragmentProcessor::Make(std::move(fpB), std::move(fpA), shader->mode());
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkColorFilterShader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    auto shaderFP = Make(shader->shader().get(), args, mRec);
+    if (!shaderFP) {
+        return nullptr;
+    }
+
+    // TODO I guess, but it shouldn't come up as used today.
+    SkASSERT(shader->alpha() == 1.0f);
+
+    auto [success, fp] = Make(args.fContext,
+                              shader->filter().get(),
+                              std::move(shaderFP),
+                              *args.fDstColorInfo,
+                              args.fSurfaceProps);
+    // If the filter FP could not be created, we still want to return the shader FP, so checking
+    // success can be omitted here.
+    return std::move(fp);
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkColorShader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    return GrFragmentProcessor::MakeColor(SkColorToPMColor4f(shader->color(), *args.fDstColorInfo));
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkColor4Shader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    SkColorSpaceXformSteps steps{shader->colorSpace().get(),
+                                 kUnpremul_SkAlphaType,
+                                 args.fDstColorInfo->colorSpace(),
+                                 kUnpremul_SkAlphaType};
+    SkColor4f color = shader->color();
+    steps.apply(color.vec());
+    return GrFragmentProcessor::MakeColor(color.premul());
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkCoordClampShader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    static const SkRuntimeEffect* effect =
+            SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
+                                "uniform shader c;"
+                                "uniform float4 s;"
+                                "half4 main(float2 p) {"
+                                    "return c.eval(clamp(p, s.LT, s.RB));"
+                                "}");
+
+    auto fp = Make(shader->shader().get(), args, mRec.applied());
+    if (!fp) {
+        return nullptr;
+    }
+
+    GrSkSLFP::OptFlags flags = GrSkSLFP::OptFlags::kNone;
+    if (fp->compatibleWithCoverageAsAlpha()) {
+        flags |= GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
+    }
+    if (fp->preservesOpaqueInput()) {
+        flags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
+    }
+    fp = GrSkSLFP::Make(effect,
+                        "clamp_fp",
+                        /*inputFP=*/nullptr,
+                        flags,
+                        "c",
+                        std::move(fp),
+                        "s",
+                        shader->subset());
+
+    auto [total, ok] = mRec.applyForFragmentProcessor({});
+    if (!ok) {
+        return nullptr;
+    }
+    return GrMatrixEffect::Make(total, std::move(fp));
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkCTMShader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    SkMatrix ctmInv;
+    if (!shader->ctm().invert(&ctmInv)) {
+        return nullptr;
+    }
+
+    auto base = Make(shader->proxyShader().get(), args, shader->ctm());
+    if (!base) {
+        return nullptr;
+    }
+
+    // In order for the shader to be evaluated with the original CTM, we explicitly evaluate it
+    // at sk_FragCoord, and pass that through the inverse of the original CTM. This avoids requiring
+    // local coords for the shader and mapping from the draw's local to device and then back.
+    return GrFragmentProcessor::DeviceSpace(GrMatrixEffect::Make(ctmInv, std::move(base)));
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkEmptyShader* shader,
+                                                           const GrFPArgs&,
+                                                           const SkShaders::MatrixRec&) {
+    return nullptr;
+}
+
+static bool needs_subset(sk_sp<const SkImage> img, const SkRect& subset) {
+    return subset != SkRect::Make(img->dimensions());
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkImageShader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    SkTileMode tileModes[2] = {shader->tileModeX(), shader->tileModeY()};
+    const SkRect shaderSubset = shader->subset();
+    const SkRect* subset = needs_subset(shader->image(), shaderSubset) ? &shaderSubset : nullptr;
+    auto fp = skgpu::ganesh::AsFragmentProcessor(
+            args.fContext, shader->image(), shader->sampling(), tileModes, SkMatrix::I(), subset);
+    if (!fp) {
+        return nullptr;
+    }
+
+    auto [total, ok] = mRec.applyForFragmentProcessor({});
+    if (!ok) {
+        return nullptr;
+    }
+    fp = GrMatrixEffect::Make(total, std::move(fp));
+
+    if (!shader->isRaw()) {
+        fp = GrColorSpaceXformEffect::Make(std::move(fp),
+                                           shader->image()->colorSpace(),
+                                           shader->image()->alphaType(),
+                                           args.fDstColorInfo->colorSpace(),
+                                           kPremul_SkAlphaType);
+
+        if (shader->image()->isAlphaOnly()) {
+            fp = GrBlendFragmentProcessor::Make<SkBlendMode::kDstIn>(std::move(fp), nullptr);
+        }
+    }
+
+    return fp;
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkLocalMatrixShader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    return Make(shader->wrappedShader().get(), args, mRec.concat(shader->localMatrix()));
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkPerlinNoiseShader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    SkASSERT(args.fContext);
+    SkASSERT(shader->numOctaves());
+
+    const SkMatrix& totalMatrix = mRec.totalMatrix();
+
+    // Either we don't stitch tiles, or we have a valid tile size
+    SkASSERT(!shader->stitchTiles() || !shader->tileSize().isEmpty());
+
+    auto paintingData = shader->getPaintingData(totalMatrix);
+    paintingData->generateBitmaps();
+
+    // Like shadeSpan, we start from device space. We will account for that below with a device
+    // space effect.
+
+    auto context = args.fContext;
+
+    const SkBitmap& permutationsBitmap = paintingData->getPermutationsBitmap();
+    const SkBitmap& noiseBitmap = paintingData->getNoiseBitmap();
+
+    auto permutationsView = std::get<0>(GrMakeCachedBitmapProxyView(
+            context,
+            permutationsBitmap,
+            /*label=*/"PerlinNoiseShader_FragmentProcessor_PermutationsView"));
+    auto noiseView = std::get<0>(GrMakeCachedBitmapProxyView(
+            context, noiseBitmap, /*label=*/"PerlinNoiseShader_FragmentProcessor_NoiseView"));
+
+    if (permutationsView && noiseView) {
+        return GrFragmentProcessor::DeviceSpace(
+                GrMatrixEffect::Make(SkMatrix::Translate(1 - totalMatrix.getTranslateX(),
+                                                         1 - totalMatrix.getTranslateY()),
+                                     GrPerlinNoise2Effect::Make(shader->noiseType(),
+                                                                shader->numOctaves(),
+                                                                shader->stitchTiles(),
+                                                                std::move(paintingData),
+                                                                std::move(permutationsView),
+                                                                std::move(noiseView),
+                                                                *context->priv().caps())));
+    }
+    return nullptr;
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkPictureShader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    auto ctx = args.fContext;
+    SkColorType dstColorType = GrColorTypeToSkColorType(args.fDstColorInfo->colorType());
+    if (dstColorType == kUnknown_SkColorType) {
+        dstColorType = kRGBA_8888_SkColorType;
+    }
+    sk_sp<SkColorSpace> dstCS = SkColorSpace::MakeSRGB();
+    if (args.fDstColorInfo->colorSpace()) {
+        dstCS = sk_ref_sp(args.fDstColorInfo->colorSpace());
+    }
+
+    auto info = SkPictureShader::CachedImageInfo::Make(shader->tile(),
+                                                       mRec.totalMatrix(),
+                                                       dstColorType,
+                                                       dstCS.get(),
+                                                       ctx->priv().caps()->maxTextureSize(),
+                                                       args.fSurfaceProps);
+    if (!info.success) {
+        return nullptr;
+    }
+
+    // Gotta be sure the GPU can support our requested colortype (might be FP16)
+    if (!ctx->colorTypeSupportedAsSurface(info.imageInfo.colorType())) {
+        info.imageInfo = info.imageInfo.makeColorType(kRGBA_8888_SkColorType);
+    }
+
+    static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain();
+    skgpu::UniqueKey key;
+    std::tuple keyData = {dstCS->toXYZD50Hash(),
+                          dstCS->transferFnHash(),
+                          static_cast<uint32_t>(dstColorType),
+                          shader->picture()->uniqueID(),
+                          shader->tile(),
+                          info.tileScale,
+                          info.props};
+    skgpu::UniqueKey::Builder builder(
+            &key, kDomain, sizeof(keyData) / sizeof(uint32_t), "Picture Shader Image");
+    memcpy(&builder[0], &keyData, sizeof(keyData));
+    builder.finish();
+
+    GrProxyProvider* provider = ctx->priv().proxyProvider();
+    GrSurfaceProxyView view;
+    if (auto proxy = provider->findOrCreateProxyByUniqueKey(key)) {
+        view = GrSurfaceProxyView(proxy, kTopLeft_GrSurfaceOrigin, skgpu::Swizzle());
+    } else {
+        const int msaaSampleCount = 0;
+        const bool createWithMips = false;
+        auto image = info.makeImage(SkSurfaces::RenderTarget(ctx,
+                                                             skgpu::Budgeted::kYes,
+                                                             info.imageInfo,
+                                                             msaaSampleCount,
+                                                             kTopLeft_GrSurfaceOrigin,
+                                                             &info.props,
+                                                             createWithMips),
+                                    shader->picture().get());
+        if (!image) {
+            return nullptr;
+        }
+
+        auto [v, ct] = skgpu::ganesh::AsView(ctx, image, GrMipmapped::kNo);
+        view = std::move(v);
+        provider->assignUniqueKeyToProxy(key, view.asTextureProxy());
+    }
+
+    const GrSamplerState sampler(static_cast<GrSamplerState::WrapMode>(shader->tileModeX()),
+                                 static_cast<GrSamplerState::WrapMode>(shader->tileModeY()),
+                                 shader->filter());
+    auto fp = GrTextureEffect::Make(
+            std::move(view), kPremul_SkAlphaType, SkMatrix::I(), sampler, *ctx->priv().caps());
+    SkMatrix scale = SkMatrix::Scale(info.tileScale.width(), info.tileScale.height());
+    auto [total, ok] = mRec.applyForFragmentProcessor(scale);
+    if (!ok) {
+        return nullptr;
+    }
+    return GrMatrixEffect::Make(total, std::move(fp));
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkRuntimeShader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    if (!SkRuntimeEffectPriv::CanDraw(args.fContext->priv().caps(), shader->asRuntimeEffect())) {
+        return nullptr;
+    }
+
+    sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
+            shader->asRuntimeEffect()->uniforms(),
+            shader->uniformData(args.fDstColorInfo->colorSpace()),
+            args.fDstColorInfo->colorSpace());
+    SkASSERT(uniforms);
+
+    auto children = shader->children();
+    bool success;
+    std::unique_ptr<GrFragmentProcessor> fp;
+    std::tie(success, fp) = make_effect_fp(shader->effect(),
+                                           "runtime_shader",
+                                           std::move(uniforms),
+                                           /*inputFP=*/nullptr,
+                                           /*destColorFP=*/nullptr,
+                                           SkSpan(children),
+                                           args);
+    if (!success) {
+        return nullptr;
+    }
+
+    auto [total, ok] = mRec.applyForFragmentProcessor({});
+    if (!ok) {
+        return nullptr;
+    }
+    return GrMatrixEffect::Make(total, std::move(fp));
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkTransformShader* shader,
+                                                           const GrFPArgs&,
+                                                           const SkShaders::MatrixRec&) {
+    return nullptr;
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkTriColorShader* shader,
+                                                           const GrFPArgs&,
+                                                           const SkShaders::MatrixRec&) {
+    return nullptr;
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkUpdatableColorShader* shader,
+                                                           const GrFPArgs&,
+                                                           const SkShaders::MatrixRec&) {
+    return nullptr;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////
+
+static std::unique_ptr<GrFragmentProcessor> make_gradient_fp(const SkConicalGradient* shader,
+                                                             const GrFPArgs& args,
+                                                             const SkShaders::MatrixRec& mRec) {
+    // The 2 point conical gradient can reject a pixel so it does change opacity even if the input
+    // was opaque. Thus, all of these layout FPs disable that optimization.
+    std::unique_ptr<GrFragmentProcessor> fp;
+    SkTLazy<SkMatrix> matrix;
+    switch (shader->getType()) {
+        case SkConicalGradient::Type::kStrip: {
+            static const SkRuntimeEffect* kEffect =
+                SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
+                        "uniform half r0_2;"
+                        "half4 main(float2 p) {"
+                            // validation flag, set to negative to discard fragment later.
+                            "half v = 1;"
+                            "float t = r0_2 - p.y * p.y;"
+                            "if (t >= 0) {"
+                                "t = p.x + sqrt(t);"
+                            "} else {"
+                                "v = -1;"
+                            "}"
+                            "return half4(half(t), v, 0, 0);"
+                        "}"
+                    );
+            float r0 = shader->getStartRadius() / shader->getCenterX1();
+            fp = GrSkSLFP::Make(kEffect,
+                                "TwoPointConicalStripLayout",
+                                /*inputFP=*/nullptr,
+                                GrSkSLFP::OptFlags::kNone,
+                                "r0_2",
+                                r0 * r0);
+        } break;
+
+        case SkConicalGradient::Type::kRadial: {
+            static const SkRuntimeEffect* kEffect =
+                SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
+                        "uniform half r0;"
+                        "uniform half lengthScale;"
+                        "half4 main(float2 p) {"
+                            // validation flag, set to negative to discard fragment later
+                            "half v = 1;"
+                            "float t = length(p) * lengthScale - r0;"
+                            "return half4(half(t), v, 0, 0);"
+                        "}"
+                    );
+            float dr = shader->getDiffRadius();
+            float r0 = shader->getStartRadius() / dr;
+            bool isRadiusIncreasing = dr >= 0;
+            fp = GrSkSLFP::Make(kEffect,
+                                "TwoPointConicalRadialLayout",
+                                /*inputFP=*/nullptr,
+                                GrSkSLFP::OptFlags::kNone,
+                                "r0",
+                                r0,
+                                "lengthScale",
+                                isRadiusIncreasing ? 1.0f : -1.0f);
+
+            // GPU radial matrix is different from the original matrix, since we map the diff radius
+            // to have |dr| = 1, so manually compute the final gradient matrix here.
+
+            // Map center to (0, 0)
+            matrix.set(SkMatrix::Translate(-shader->getStartCenter().fX,
+                                           -shader->getStartCenter().fY));
+            // scale |diffRadius| to 1
+            matrix->postScale(1 / dr, 1 / dr);
+        } break;
+
+        case SkConicalGradient::Type::kFocal: {
+            static const SkRuntimeEffect* kEffect =
+                SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
+                        // Optimization flags, all specialized:
+                        "uniform int isRadiusIncreasing;"
+                        "uniform int isFocalOnCircle;"
+                        "uniform int isWellBehaved;"
+                        "uniform int isSwapped;"
+                        "uniform int isNativelyFocal;"
+
+                        "uniform half invR1;"  // 1/r1
+                        "uniform half fx;"     // focalX = r0/(r0-r1)
+
+                        "half4 main(float2 p) {"
+                            "float t = -1;"
+                            "half v = 1;" // validation flag,set to negative to discard fragment later
+
+                            "float x_t = -1;"
+                            "if (bool(isFocalOnCircle)) {"
+                                "x_t = dot(p, p) / p.x;"
+                            "} else if (bool(isWellBehaved)) {"
+                                "x_t = length(p) - p.x * invR1;"
+                            "} else {"
+                                "float temp = p.x * p.x - p.y * p.y;"
+
+                                // Only do sqrt if temp >= 0; this is significantly slower than
+                                // checking temp >= 0 in the if statement that checks r(t) >= 0.
+                                // But GPU may break if we sqrt a negative float. (Although I
+                                // haven't observed that on any devices so far, and the old
+                                // approach also does sqrt negative value without a check.) If
+                                // the performance is really critical, maybe we should just
+                                // compute the area where temp and x_t are always valid and drop
+                                // all these ifs.
+                                "if (temp >= 0) {"
+                                    "if (bool(isSwapped) || !bool(isRadiusIncreasing)) {"
+                                        "x_t = -sqrt(temp) - p.x * invR1;"
+                                    "} else {"
+                                        "x_t = sqrt(temp) - p.x * invR1;"
+                                    "}"
+                                "}"
+                            "}"
+
+                            // The final calculation of t from x_t has lots of static
+                            // optimizations but only do them when x_t is positive (which
+                            // can be assumed true if isWellBehaved is true)
+                            "if (!bool(isWellBehaved)) {"
+                                // This will still calculate t even though it will be ignored
+                                // later in the pipeline to avoid a branch
+                                "if (x_t <= 0.0) {"
+                                    "v = -1;"
+                                "}"
+                            "}"
+                            "if (bool(isRadiusIncreasing)) {"
+                                "if (bool(isNativelyFocal)) {"
+                                    "t = x_t;"
+                                "} else {"
+                                    "t = x_t + fx;"
+                                "}"
+                            "} else {"
+                                "if (bool(isNativelyFocal)) {"
+                                    "t = -x_t;"
+                                "} else {"
+                                    "t = -x_t + fx;"
+                                "}"
+                            "}"
+
+                            "if (bool(isSwapped)) {"
+                                "t = 1 - t;"
+                            "}"
+
+                            "return half4(half(t), v, 0, 0);"
+                        "}"
+                    );
+
+            const SkConicalGradient::FocalData& focalData = shader->getFocalData();
+            bool isRadiusIncreasing = (1 - focalData.fFocalX) > 0,
+                 isFocalOnCircle = focalData.isFocalOnCircle(),
+                 isWellBehaved = focalData.isWellBehaved(), isSwapped = focalData.isSwapped(),
+                 isNativelyFocal = focalData.isNativelyFocal();
+
+            fp = GrSkSLFP::Make(kEffect, "TwoPointConicalFocalLayout", /*inputFP=*/nullptr,
+                                GrSkSLFP::OptFlags::kNone,
+                                "isRadiusIncreasing", GrSkSLFP::Specialize<int>(isRadiusIncreasing),
+                                "isFocalOnCircle",    GrSkSLFP::Specialize<int>(isFocalOnCircle),
+                                "isWellBehaved",      GrSkSLFP::Specialize<int>(isWellBehaved),
+                                "isSwapped",          GrSkSLFP::Specialize<int>(isSwapped),
+                                "isNativelyFocal",    GrSkSLFP::Specialize<int>(isNativelyFocal),
+                                "invR1",              1.0f / focalData.fR1,
+                                "fx",                 focalData.fFocalX);
+        } break;
+    }
+    return GrGradientShader::MakeGradientFP(
+            *shader, args, mRec, std::move(fp), matrix.getMaybeNull());
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_gradient_fp(const SkLinearGradient* shader,
+                                                             const GrFPArgs& args,
+                                                             const SkShaders::MatrixRec& mRec) {
+    return GrGradientShader::MakeLinear(*shader, args, mRec);
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_gradient_fp(const SkRadialGradient* shader,
+                                                             const GrFPArgs& args,
+                                                             const SkShaders::MatrixRec& mRec) {
+    static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(
+            SkRuntimeEffect::MakeForShader,
+            "half4 main(float2 coord) {"
+                "return half4(half(length(coord)), 1, 0, 0);"  // y = 1 for always valid
+            "}");
+    // The radial gradient never rejects a pixel so it doesn't change opacity
+    auto fp = GrSkSLFP::Make(
+            effect, "RadialLayout", /*inputFP=*/nullptr, GrSkSLFP::OptFlags::kPreservesOpaqueInput);
+    return GrGradientShader::MakeGradientFP(*shader, args, mRec, std::move(fp));
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_gradient_fp(const SkSweepGradient* shader,
+                                                             const GrFPArgs& args,
+                                                             const SkShaders::MatrixRec& mRec) {
+    // On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
+    // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in (sqrt(x^2
+    // + y^2) + x) as the second parameter to atan2 in these cases. We let the device handle the
+    // undefined behavior of the second paramenter being 0 instead of doing the divide ourselves and
+    // using atan instead.
+    int useAtanWorkaround =
+            args.fContext->priv().caps()->shaderCaps()->fAtan2ImplementedAsAtanYOverX;
+    static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
+        "uniform half bias;"
+        "uniform half scale;"
+        "uniform int useAtanWorkaround;"  // specialized
+
+        "half4 main(float2 coord) {"
+            "half angle = bool(useAtanWorkaround)"
+                    "? half(2 * atan(-coord.y, length(coord) - coord.x))"
+                    ": half(atan(-coord.y, -coord.x));"
+
+            // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
+            "half t = (angle * 0.1591549430918 + 0.5 + bias) * scale;"
+            "return half4(t, 1, 0, 0);" // y = 1 for always valid
+        "}"
+    );
+
+    // The sweep gradient never rejects a pixel so it doesn't change opacity
+    auto fp = GrSkSLFP::Make(effect, "SweepLayout", /*inputFP=*/nullptr,
+                             GrSkSLFP::OptFlags::kPreservesOpaqueInput,
+                             "bias", shader->tBias(),
+                             "scale", shader->tScale(),
+                             "useAtanWorkaround", GrSkSLFP::Specialize(useAtanWorkaround));
+    return GrGradientShader::MakeGradientFP(*shader, args, mRec, std::move(fp));
+}
+
+static std::unique_ptr<GrFragmentProcessor> make_shader_fp(const SkGradientBaseShader* shader,
+                                                           const GrFPArgs& args,
+                                                           const SkShaders::MatrixRec& mRec) {
+    SkASSERT(shader);
+
+    switch (shader->asGradient()) {
+#define M(type)                               \
+    case SkShaderBase::GradientType::k##type: \
+        return make_gradient_fp(static_cast<const Sk##type##Gradient*>(shader), args, mRec);
+        SK_ALL_GRADIENTS(M)
+#undef M
+        case SkShaderBase::GradientType::kNone:
+            SkDEBUGFAIL("Gradient shader says its type is none");
+            return nullptr;
     }
     SkUNREACHABLE;
 }
+
+std::unique_ptr<GrFragmentProcessor> Make(const SkShader* shader,
+                                          const GrFPArgs& args,
+                                          const SkMatrix& ctm) {
+    return Make(shader, args, SkShaders::MatrixRec(ctm));
+}
+
+std::unique_ptr<GrFragmentProcessor> Make(const SkShader* shader,
+                                          const GrFPArgs& args,
+                                          const SkShaders::MatrixRec& mRec) {
+    if (!shader) {
+        return nullptr;
+    }
+    auto base = as_SB(shader);
+    switch (base->type()) {
+#define M(type)                             \
+    case SkShaderBase::ShaderType::k##type: \
+        return make_shader_fp(static_cast<const Sk##type##Shader*>(base), args, mRec);
+        SK_ALL_SHADERS(M)
+#undef M
+    }
+    SkUNREACHABLE;
+}
+
 }  // namespace GrFragmentProcessors
diff --git a/src/gpu/ganesh/GrFragmentProcessors.h b/src/gpu/ganesh/GrFragmentProcessors.h
index 460c90e..5593569 100644
--- a/src/gpu/ganesh/GrFragmentProcessors.h
+++ b/src/gpu/ganesh/GrFragmentProcessors.h
@@ -24,10 +24,15 @@
 class SkMaskFilter;
 class SkMatrix;
 class SkSurfaceProps;
+class SkShader;
 struct GrFPArgs;
 
 using GrFPResult = std::tuple<bool, std::unique_ptr<GrFragmentProcessor>>;
 
+namespace SkShaders {
+class MatrixRec;
+}
+
 namespace GrFragmentProcessors {
 /**
  * Returns a GrFragmentProcessor that implements this blend for the Ganesh GPU backend.
@@ -58,6 +63,17 @@
 
 bool IsSupported(const SkMaskFilter*);
 
+/**
+ * Call on the root SkShader to produce a GrFragmentProcessor.
+ *
+ * The returned GrFragmentProcessor expects an unpremultiplied input color and produces a
+ * premultiplied output.
+ */
+std::unique_ptr<GrFragmentProcessor> Make(const SkShader*, const GrFPArgs&, const SkMatrix& ctm);
+std::unique_ptr<GrFragmentProcessor> Make(const SkShader*,
+                                          const GrFPArgs&,
+                                          const SkShaders::MatrixRec&);
+
 // TODO(kjlubick, brianosman) remove this after all related effects have been migrated
 GrFPResult make_effect_fp(sk_sp<SkRuntimeEffect> effect,
                           const char* name,
@@ -66,6 +82,6 @@
                           std::unique_ptr<GrFragmentProcessor> destColorFP,
                           SkSpan<const SkRuntimeEffect::ChildPtr> children,
                           const GrFPArgs& childArgs);
-}
+}  // namespace GrFragmentProcessors
 
 #endif
diff --git a/src/gpu/ganesh/GrProcessorUnitTest.h b/src/gpu/ganesh/GrProcessorUnitTest.h
index 79c6c0d..fa23508 100644
--- a/src/gpu/ganesh/GrProcessorUnitTest.h
+++ b/src/gpu/ganesh/GrProcessorUnitTest.h
@@ -20,13 +20,14 @@
 
 #include <tuple>
 
-class SkMatrix;
 class GrCaps;
-class GrProxyProvider;
+class GrFragmentProcessor;
+class GrGeometryProcessor;
 class GrProcessorTestData;
+class GrProxyProvider;
 class GrTexture;
 class GrXPFactory;
-class GrGeometryProcessor;
+class SkMatrix;
 
 namespace GrProcessorUnitTest {
 
diff --git a/src/gpu/ganesh/SkGr.cpp b/src/gpu/ganesh/SkGr.cpp
index f4812ce..c134215 100644
--- a/src/gpu/ganesh/SkGr.cpp
+++ b/src/gpu/ganesh/SkGr.cpp
@@ -373,7 +373,7 @@
             paintFP = std::move(*shaderFP);
         } else {
             if (const SkShaderBase* shader = as_SB(skPaint.getShader())) {
-                paintFP = shader->asFragmentProcessor(fpArgs, SkShaderBase::MatrixRec(ctm));
+                paintFP = GrFragmentProcessors::Make(shader, fpArgs, ctm);
                 if (paintFP == nullptr) {
                     return false;
                 }
diff --git a/src/gpu/ganesh/effects/BUILD.bazel b/src/gpu/ganesh/effects/BUILD.bazel
index c00129c..d9c8224 100644
--- a/src/gpu/ganesh/effects/BUILD.bazel
+++ b/src/gpu/ganesh/effects/BUILD.bazel
@@ -36,6 +36,8 @@
     "GrModulateAtlasCoverageEffect.h",
     "GrOvalEffect.cpp",
     "GrOvalEffect.h",
+    "GrPerlinNoise2Effect.cpp",
+    "GrPerlinNoise2Effect.h",
     "GrPorterDuffXferProcessor.cpp",
     "GrPorterDuffXferProcessor.h",
     "GrRRectEffect.cpp",
diff --git a/src/gpu/ganesh/effects/GrConvexPolyEffect.h b/src/gpu/ganesh/effects/GrConvexPolyEffect.h
index e96ba28..7392959 100644
--- a/src/gpu/ganesh/effects/GrConvexPolyEffect.h
+++ b/src/gpu/ganesh/effects/GrConvexPolyEffect.h
@@ -11,7 +11,6 @@
 #include "include/core/SkScalar.h"
 #include "src/gpu/ganesh/GrFragmentProcessor.h"
 #include "src/gpu/ganesh/GrProcessorUnitTest.h"
-#include "src/shaders/SkShaderBase.h"
 
 #include <array>
 #include <memory>
diff --git a/src/gpu/ganesh/effects/GrPerlinNoise2Effect.cpp b/src/gpu/ganesh/effects/GrPerlinNoise2Effect.cpp
new file mode 100644
index 0000000..394f3a4
--- /dev/null
+++ b/src/gpu/ganesh/effects/GrPerlinNoise2Effect.cpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/gpu/ganesh/effects/GrPerlinNoise2Effect.h"
+
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkSize.h"
+#include "include/effects/SkPerlinNoiseShader.h"
+#include "include/private/gpu/ganesh/GrTypesPriv.h"
+#include "src/base/SkRandom.h"
+#include "src/core/SkSLTypeShared.h"
+#include "src/gpu/KeyBuilder.h"
+#include "src/gpu/ganesh/GrFragmentProcessor.h"
+#include "src/gpu/ganesh/GrFragmentProcessors.h"
+#include "src/gpu/ganesh/GrProcessorUnitTest.h"
+#include "src/gpu/ganesh/GrShaderCaps.h"
+#include "src/gpu/ganesh/GrShaderVar.h"
+#include "src/gpu/ganesh/GrTestUtils.h"
+#include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h"
+#include "src/gpu/ganesh/glsl/GrGLSLProgramDataManager.h"
+#include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h"
+
+#include <cstdint>
+#include <iterator>
+
+class SkShader;
+
+/////////////////////////////////////////////////////////////////////
+GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoise2Effect)
+
+#if GR_TEST_UTILS
+std::unique_ptr<GrFragmentProcessor> GrPerlinNoise2Effect::TestCreate(GrProcessorTestData* d) {
+    int numOctaves = d->fRandom->nextRangeU(2, 10);
+    bool stitchTiles = d->fRandom->nextBool();
+    SkScalar seed = SkIntToScalar(d->fRandom->nextU());
+    SkISize tileSize;
+    tileSize.fWidth = d->fRandom->nextRangeU(4, 4096);
+    tileSize.fHeight = d->fRandom->nextRangeU(4, 4096);
+    SkScalar baseFrequencyX = d->fRandom->nextRangeScalar(0.01f, 0.99f);
+    SkScalar baseFrequencyY = d->fRandom->nextRangeScalar(0.01f, 0.99f);
+
+    sk_sp<SkShader> shader(d->fRandom->nextBool()
+                                   ? SkShaders::MakeFractalNoise(baseFrequencyX,
+                                                                 baseFrequencyY,
+                                                                 numOctaves,
+                                                                 seed,
+                                                                 stitchTiles ? &tileSize : nullptr)
+                                   : SkShaders::MakeTurbulence(baseFrequencyX,
+                                                               baseFrequencyY,
+                                                               numOctaves,
+                                                               seed,
+                                                               stitchTiles ? &tileSize : nullptr));
+
+    GrTest::TestAsFPArgs asFPArgs(d);
+    return GrFragmentProcessors::Make(
+            shader.get(), asFPArgs.args(), GrTest::TestMatrix(d->fRandom));
+}
+#endif
+
+SkString GrPerlinNoise2Effect::Impl::emitHelper(EmitArgs& args) {
+    const GrPerlinNoise2Effect& pne = args.fFp.cast<GrPerlinNoise2Effect>();
+
+    GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+
+    // Add noise function
+    const GrShaderVar gPerlinNoiseArgs[] = {{"chanCoord", SkSLType::kHalf},
+                                            {"noiseVec ", SkSLType::kHalf2}};
+
+    const GrShaderVar gPerlinNoiseStitchArgs[] = {{"chanCoord", SkSLType::kHalf},
+                                                  {"noiseVec", SkSLType::kHalf2},
+                                                  {"stitchData", SkSLType::kHalf2}};
+
+    SkString noiseCode;
+
+    noiseCode.append(
+            "half4 floorVal;"
+            "floorVal.xy = floor(noiseVec);"
+            "floorVal.zw = floorVal.xy + half2(1);"
+            "half2 fractVal = fract(noiseVec);"
+
+            // smooth curve : t^2*(3 - 2*t)
+            "half2 noiseSmooth = fractVal*fractVal*(half2(3) - 2*fractVal);"
+    );
+
+    // Adjust frequencies if we're stitching tiles
+    if (pne.stitchTiles()) {
+        noiseCode.append("floorVal -= step(stitchData.xyxy, floorVal) * stitchData.xyxy;");
+    }
+
+    // NOTE: We need to explicitly pass half4(1) as input color here, because the helper function
+    // can't see fInputColor (which is "_input" in the FP's outer function). skbug.com/10506
+    SkString sampleX = this->invokeChild(0, "half4(1)", args, "half2(floorVal.x, 0.5)");
+    SkString sampleY = this->invokeChild(0, "half4(1)", args, "half2(floorVal.z, 0.5)");
+    noiseCode.appendf("half2 latticeIdx = half2(%s.a, %s.a);", sampleX.c_str(), sampleY.c_str());
+
+    if (args.fShaderCaps->fPerlinNoiseRoundingFix) {
+        // Android rounding for Tegra devices, like, for example: Xoom (Tegra 2), Nexus 7 (Tegra 3).
+        // The issue is that colors aren't accurate enough on Tegra devices. For example, if an
+        // 8 bit value of 124 (or 0.486275 here) is entered, we can get a texture value of
+        // 123.513725 (or 0.484368 here). The following rounding operation prevents these precision
+        // issues from affecting the result of the noise by making sure that we only have multiples
+        // of 1/255. (Note that 1/255 is about 0.003921569, which is the value used here).
+        noiseCode.append(
+                "latticeIdx = floor(latticeIdx * half2(255.0) + half2(0.5)) * half2(0.003921569);");
+    }
+
+    // Get (x,y) coordinates with the permuted x
+    noiseCode.append("half4 bcoords = 256*latticeIdx.xyxy + floorVal.yyww;");
+
+    noiseCode.append("half2 uv;");
+
+    // This is the math to convert the two 16bit integer packed into rgba 8 bit input into a
+    // [-1,1] vector and perform a dot product between that vector and the provided vector.
+    // Save it as a string because we will repeat it 4x.
+    static constexpr const char* inc8bit = "0.00390625";  // 1.0 / 256.0
+    SkString dotLattice =
+            SkStringPrintf("dot((lattice.ga + lattice.rb*%s)*2 - half2(1), fractVal)", inc8bit);
+
+    SkString sampleA = this->invokeChild(1, "half4(1)", args, "half2(bcoords.x, chanCoord)");
+    SkString sampleB = this->invokeChild(1, "half4(1)", args, "half2(bcoords.y, chanCoord)");
+    SkString sampleC = this->invokeChild(1, "half4(1)", args, "half2(bcoords.w, chanCoord)");
+    SkString sampleD = this->invokeChild(1, "half4(1)", args, "half2(bcoords.z, chanCoord)");
+
+    // Compute u, at offset (0,0)
+    noiseCode.appendf("half4 lattice = %s;", sampleA.c_str());
+    noiseCode.appendf("uv.x = %s;", dotLattice.c_str());
+
+    // Compute v, at offset (-1,0)
+    noiseCode.append("fractVal.x -= 1.0;");
+    noiseCode.appendf("lattice = %s;", sampleB.c_str());
+    noiseCode.appendf("uv.y = %s;", dotLattice.c_str());
+
+    // Compute 'a' as a linear interpolation of 'u' and 'v'
+    noiseCode.append("half2 ab;");
+    noiseCode.append("ab.x = mix(uv.x, uv.y, noiseSmooth.x);");
+
+    // Compute v, at offset (-1,-1)
+    noiseCode.append("fractVal.y -= 1.0;");
+    noiseCode.appendf("lattice = %s;", sampleC.c_str());
+    noiseCode.appendf("uv.y = %s;", dotLattice.c_str());
+
+    // Compute u, at offset (0,-1)
+    noiseCode.append("fractVal.x += 1.0;");
+    noiseCode.appendf("lattice = %s;", sampleD.c_str());
+    noiseCode.appendf("uv.x = %s;", dotLattice.c_str());
+
+    // Compute 'b' as a linear interpolation of 'u' and 'v'
+    noiseCode.append("ab.y = mix(uv.x, uv.y, noiseSmooth.x);");
+    // Compute the noise as a linear interpolation of 'a' and 'b'
+    noiseCode.append("return mix(ab.x, ab.y, noiseSmooth.y);");
+
+    SkString noiseFuncName = fragBuilder->getMangledFunctionName("noiseFuncName");
+    if (pne.stitchTiles()) {
+        fragBuilder->emitFunction(SkSLType::kHalf,
+                                  noiseFuncName.c_str(),
+                                  {gPerlinNoiseStitchArgs, std::size(gPerlinNoiseStitchArgs)},
+                                  noiseCode.c_str());
+    } else {
+        fragBuilder->emitFunction(SkSLType::kHalf,
+                                  noiseFuncName.c_str(),
+                                  {gPerlinNoiseArgs, std::size(gPerlinNoiseArgs)},
+                                  noiseCode.c_str());
+    }
+
+    return noiseFuncName;
+}
+
+void GrPerlinNoise2Effect::Impl::emitCode(EmitArgs& args) {
+    SkString noiseFuncName = this->emitHelper(args);
+
+    const GrPerlinNoise2Effect& pne = args.fFp.cast<GrPerlinNoise2Effect>();
+
+    GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
+    GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
+
+    fBaseFrequencyUni = uniformHandler->addUniform(
+            &pne, kFragment_GrShaderFlag, SkSLType::kHalf2, "baseFrequency");
+    const char* baseFrequencyUni = uniformHandler->getUniformCStr(fBaseFrequencyUni);
+
+    const char* stitchDataUni = nullptr;
+    if (pne.stitchTiles()) {
+        fStitchDataUni = uniformHandler->addUniform(
+                &pne, kFragment_GrShaderFlag, SkSLType::kHalf2, "stitchData");
+        stitchDataUni = uniformHandler->getUniformCStr(fStitchDataUni);
+    }
+
+    // There are rounding errors if the floor operation is not performed here
+    fragBuilder->codeAppendf(
+            "half2 noiseVec = half2(floor(%s.xy) * %s);", args.fSampleCoord, baseFrequencyUni);
+
+    // Clear the color accumulator
+    fragBuilder->codeAppendf("half4 color = half4(0);");
+
+    if (pne.stitchTiles()) {
+        fragBuilder->codeAppendf("half2 stitchData = %s;", stitchDataUni);
+    }
+
+    fragBuilder->codeAppendf("half ratio = 1.0;");
+
+    // Loop over all octaves
+    fragBuilder->codeAppendf("for (int octave = 0; octave < %d; ++octave) {", pne.numOctaves());
+    fragBuilder->codeAppendf("color += ");
+    if (pne.type() != SkPerlinNoiseShader::kFractalNoise_Type) {
+        fragBuilder->codeAppend("abs(");
+    }
+
+    // There are 4 lines, put y coords at center of each.
+    static constexpr const char* chanCoordR = "0.5";
+    static constexpr const char* chanCoordG = "1.5";
+    static constexpr const char* chanCoordB = "2.5";
+    static constexpr const char* chanCoordA = "3.5";
+    if (pne.stitchTiles()) {
+        fragBuilder->codeAppendf(
+                "half4(%s(%s, noiseVec, stitchData), %s(%s, noiseVec, stitchData),"
+                      "%s(%s, noiseVec, stitchData), %s(%s, noiseVec, stitchData))",
+                noiseFuncName.c_str(),
+                chanCoordR,
+                noiseFuncName.c_str(),
+                chanCoordG,
+                noiseFuncName.c_str(),
+                chanCoordB,
+                noiseFuncName.c_str(),
+                chanCoordA);
+    } else {
+        fragBuilder->codeAppendf(
+                "half4(%s(%s, noiseVec), %s(%s, noiseVec),"
+                      "%s(%s, noiseVec), %s(%s, noiseVec))",
+                noiseFuncName.c_str(),
+                chanCoordR,
+                noiseFuncName.c_str(),
+                chanCoordG,
+                noiseFuncName.c_str(),
+                chanCoordB,
+                noiseFuncName.c_str(),
+                chanCoordA);
+    }
+    if (pne.type() != SkPerlinNoiseShader::kFractalNoise_Type) {
+        fragBuilder->codeAppend(")");  // end of "abs("
+    }
+    fragBuilder->codeAppend(" * ratio;");
+
+    fragBuilder->codeAppend(
+            "noiseVec *= half2(2.0);"
+            "ratio *= 0.5;");
+
+    if (pne.stitchTiles()) {
+        fragBuilder->codeAppend("stitchData *= half2(2.0);");
+    }
+    fragBuilder->codeAppend("}");  // end of the for loop on octaves
+
+    if (pne.type() == SkPerlinNoiseShader::kFractalNoise_Type) {
+        // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2
+        // by fractalNoise and (turbulenceFunctionResult) by turbulence.
+        fragBuilder->codeAppendf("color = color * half4(0.5) + half4(0.5);");
+    }
+
+    // Clamp values
+    fragBuilder->codeAppendf("color = saturate(color);");
+
+    // Pre-multiply the result
+    fragBuilder->codeAppendf("return half4(color.rgb * color.aaa, color.a);");
+}
+
+void GrPerlinNoise2Effect::Impl::onSetData(const GrGLSLProgramDataManager& pdman,
+                                           const GrFragmentProcessor& processor) {
+    const GrPerlinNoise2Effect& turbulence = processor.cast<GrPerlinNoise2Effect>();
+
+    const SkVector& baseFrequency = turbulence.baseFrequency();
+    pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY);
+
+    if (turbulence.stitchTiles()) {
+        const SkPerlinNoiseShader::StitchData& stitchData = turbulence.stitchData();
+        pdman.set2f(fStitchDataUni,
+                    SkIntToScalar(stitchData.fWidth),
+                    SkIntToScalar(stitchData.fHeight));
+    }
+}
+
+void GrPerlinNoise2Effect::onAddToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const {
+    uint32_t key = fNumOctaves;
+    key = key << 3;  // Make room for next 3 bits
+    switch (fType) {
+        case SkPerlinNoiseShader::kFractalNoise_Type:
+            key |= 0x1;
+            break;
+        case SkPerlinNoiseShader::kTurbulence_Type:
+            key |= 0x2;
+            break;
+        default:
+            // leave key at 0
+            break;
+    }
+    if (fStitchTiles) {
+        key |= 0x4;  // Flip the 3rd bit if tile stitching is on
+    }
+    b->add32(key);
+}
diff --git a/src/gpu/ganesh/effects/GrPerlinNoise2Effect.h b/src/gpu/ganesh/effects/GrPerlinNoise2Effect.h
new file mode 100644
index 0000000..916052c
--- /dev/null
+++ b/src/gpu/ganesh/effects/GrPerlinNoise2Effect.h
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrPerlinNoise2Effect_DEFINED
+#define GrPerlinNoise2Effect_DEFINED
+
+#include "include/core/SkAlphaType.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkSamplingOptions.h"
+#include "include/core/SkString.h"
+#include "include/private/SkSLSampleUsage.h"
+#include "src/gpu/ganesh/GrCaps.h"
+#include "src/gpu/ganesh/GrFragmentProcessor.h"
+#include "src/gpu/ganesh/GrProcessorUnitTest.h"
+#include "src/gpu/ganesh/GrSamplerState.h"
+#include "src/gpu/ganesh/GrSurfaceProxyView.h"
+#include "src/gpu/ganesh/effects/GrTextureEffect.h"
+#include "src/gpu/ganesh/glsl/GrGLSLProgramDataManager.h"
+#include "src/shaders/SkPerlinNoiseShaderImpl.h"
+
+#include <memory>
+#include <utility>
+
+namespace skgpu {
+class KeyBuilder;
+}
+struct GrShaderCaps;
+
+class GrPerlinNoise2Effect : public GrFragmentProcessor {
+public:
+    static std::unique_ptr<GrFragmentProcessor> Make(
+            SkPerlinNoiseShader::Type type,
+            int numOctaves,
+            bool stitchTiles,
+            std::unique_ptr<SkPerlinNoiseShader::PaintingData> paintingData,
+            GrSurfaceProxyView permutationsView,
+            GrSurfaceProxyView noiseView,
+            const GrCaps& caps) {
+        static constexpr GrSamplerState kRepeatXSampler = {GrSamplerState::WrapMode::kRepeat,
+                                                           GrSamplerState::WrapMode::kClamp,
+                                                           GrSamplerState::Filter::kNearest};
+        auto permutationsFP = GrTextureEffect::Make(std::move(permutationsView),
+                                                    kPremul_SkAlphaType,
+                                                    SkMatrix::I(),
+                                                    kRepeatXSampler,
+                                                    caps);
+        auto noiseFP = GrTextureEffect::Make(
+                std::move(noiseView), kPremul_SkAlphaType, SkMatrix::I(), kRepeatXSampler, caps);
+
+        return std::unique_ptr<GrFragmentProcessor>(
+                new GrPerlinNoise2Effect(type,
+                                         numOctaves,
+                                         stitchTiles,
+                                         std::move(paintingData),
+                                         std::move(permutationsFP),
+                                         std::move(noiseFP)));
+    }
+
+    const char* name() const override { return "PerlinNoise"; }
+
+    std::unique_ptr<GrFragmentProcessor> clone() const override {
+        return std::unique_ptr<GrFragmentProcessor>(new GrPerlinNoise2Effect(*this));
+    }
+
+    const SkPerlinNoiseShader::StitchData& stitchData() const {
+        return fPaintingData->fStitchDataInit;
+    }
+
+    SkPerlinNoiseShader::Type type() const { return fType; }
+    bool stitchTiles() const { return fStitchTiles; }
+    const SkVector& baseFrequency() const { return fPaintingData->fBaseFrequency; }
+    int numOctaves() const { return fNumOctaves; }
+
+private:
+    class Impl : public ProgramImpl {
+    public:
+        SkString emitHelper(EmitArgs& args);
+        void emitCode(EmitArgs&) override;
+
+    private:
+        void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
+
+        GrGLSLProgramDataManager::UniformHandle fStitchDataUni;
+        GrGLSLProgramDataManager::UniformHandle fBaseFrequencyUni;
+    };
+
+    std::unique_ptr<ProgramImpl> onMakeProgramImpl() const override {
+        return std::make_unique<Impl>();
+    }
+
+    void onAddToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const override;
+
+    bool onIsEqual(const GrFragmentProcessor& sBase) const override {
+        const GrPerlinNoise2Effect& s = sBase.cast<GrPerlinNoise2Effect>();
+        return fType == s.fType &&
+               fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency &&
+               fNumOctaves == s.fNumOctaves && fStitchTiles == s.fStitchTiles &&
+               fPaintingData->fStitchDataInit == s.fPaintingData->fStitchDataInit;
+    }
+
+    GrPerlinNoise2Effect(SkPerlinNoiseShader::Type type,
+                         int numOctaves,
+                         bool stitchTiles,
+                         std::unique_ptr<SkPerlinNoiseShader::PaintingData> paintingData,
+                         std::unique_ptr<GrFragmentProcessor> permutationsFP,
+                         std::unique_ptr<GrFragmentProcessor> noiseFP)
+            : GrFragmentProcessor(kGrPerlinNoise2Effect_ClassID, kNone_OptimizationFlags)
+            , fType(type)
+            , fNumOctaves(numOctaves)
+            , fStitchTiles(stitchTiles)
+            , fPaintingData(std::move(paintingData)) {
+        this->registerChild(std::move(permutationsFP), SkSL::SampleUsage::Explicit());
+        this->registerChild(std::move(noiseFP), SkSL::SampleUsage::Explicit());
+        this->setUsesSampleCoordsDirectly();
+    }
+
+    GrPerlinNoise2Effect(const GrPerlinNoise2Effect& that)
+            : GrFragmentProcessor(that)
+            , fType(that.fType)
+            , fNumOctaves(that.fNumOctaves)
+            , fStitchTiles(that.fStitchTiles)
+            , fPaintingData(new SkPerlinNoiseShader::PaintingData(*that.fPaintingData)) {}
+
+    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
+
+    SkPerlinNoiseShader::Type fType;
+    int fNumOctaves;
+    bool fStitchTiles;
+
+    std::unique_ptr<SkPerlinNoiseShader::PaintingData> fPaintingData;
+};
+
+#endif
diff --git a/src/gpu/ganesh/effects/GrRRectEffect.cpp b/src/gpu/ganesh/effects/GrRRectEffect.cpp
index 55c157b..ac92f82 100644
--- a/src/gpu/ganesh/effects/GrRRectEffect.cpp
+++ b/src/gpu/ganesh/effects/GrRRectEffect.cpp
@@ -27,7 +27,6 @@
 #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h"
 #include "src/gpu/ganesh/glsl/GrGLSLProgramDataManager.h"
 #include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h"
-#include "src/shaders/SkShaderBase.h"
 
 #include <algorithm>
 #include <cstdint>
diff --git a/src/gpu/ganesh/gradients/GrGradientBitmapCache.cpp b/src/gpu/ganesh/gradients/GrGradientBitmapCache.cpp
index 9717862..0bbd49c 100644
--- a/src/gpu/ganesh/gradients/GrGradientBitmapCache.cpp
+++ b/src/gpu/ganesh/gradients/GrGradientBitmapCache.cpp
@@ -13,7 +13,7 @@
 #include "include/private/base/SkTemplates.h"
 #include "src/base/SkHalf.h"
 #include "src/core/SkRasterPipeline.h"
-#include "src/shaders/gradients/SkGradientShaderBase.h"
+#include "src/shaders/gradients/SkGradientBaseShader.h"
 
 #include <functional>
 
@@ -135,7 +135,7 @@
 
     p.append(SkRasterPipelineOp::seed_shader);
     p.append_matrix(&alloc, SkMatrix::Scale(1.0f / bitmap->width(), 1.0f));
-    SkGradientShaderBase::AppendGradientFillStages(&p, &alloc, colors, positions, count);
+    SkGradientBaseShader::AppendGradientFillStages(&p, &alloc, colors, positions, count);
     p.append_store(bitmap->colorType(), &ctx);
     p.run(0, 0, bitmap->width(), 1);
 }
diff --git a/src/gpu/ganesh/gradients/GrGradientShader.cpp b/src/gpu/ganesh/gradients/GrGradientShader.cpp
index c0b2706..98a2fe7 100644
--- a/src/gpu/ganesh/gradients/GrGradientShader.cpp
+++ b/src/gpu/ganesh/gradients/GrGradientShader.cpp
@@ -450,7 +450,7 @@
     // and removing these stops at the beginning, it makes optimizing the remaining color stops
     // simpler.
 
-    // SkGradientShaderBase guarantees that pos[0] == 0 by adding a default value.
+    // SkGradientBaseShader guarantees that pos[0] == 0 by adding a default value.
     bool bottomHardStop = SkScalarNearlyEqual(positions[0], positions[1]);
     // The same is true for pos[end] == 1
     bool topHardStop = SkScalarNearlyEqual(positions[count - 2], positions[count - 1]);
@@ -733,7 +733,7 @@
     //    Our final goal is to emit premul colors, but under certain conditions we don't need to do
     //    anything to achieve that: i.e. its interpolating already premul colors (inputPremul) or
     //    all the colors have a = 1, in which case premul is a no op. Note that this allOpaque check
-    //    is more permissive than SkGradientShaderBase's isOpaque(), since we can optimize away the
+    //    is more permissive than SkGradientBaseShader's isOpaque(), since we can optimize away the
     //    make-premul op for two point conical gradients (which report false for isOpaque).
     SkAlphaType intermediateAlphaType = inputPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
     SkAlphaType dstAlphaType = kPremul_SkAlphaType;
@@ -752,11 +752,29 @@
 
 namespace GrGradientShader {
 
+/**
+ * Produces an FP that muls its input coords by the inverse of the pending matrix and then
+ * samples the passed FP with those coordinates. 'postInv' is an additional matrix to
+ * post-apply to the inverted pending matrix. If the pending matrix is not invertible the
+ * GrFPResult's bool will be false and the passed FP will be returned to the caller in the
+ * GrFPResult.
+ */
+static GrFPResult apply_matrix(std::unique_ptr<GrFragmentProcessor> fp,
+                               const SkShaders::MatrixRec& rec,
+                               const SkMatrix& postInv) {
+    auto [total, ok] = rec.applyForFragmentProcessor(postInv);
+    if (!ok) {
+        return {false, std::move(fp)};
+    }
+    // GrMatrixEffect returns 'fp' if total worked out to identity.
+    return {true, GrMatrixEffect::Make(total, std::move(fp))};
+}
+
 // Combines the colorizer and layout with an appropriately configured top-level effect based on the
 // gradient's tile mode
-std::unique_ptr<GrFragmentProcessor> MakeGradientFP(const SkGradientShaderBase& shader,
+std::unique_ptr<GrFragmentProcessor> MakeGradientFP(const SkGradientBaseShader& shader,
                                                     const GrFPArgs& args,
-                                                    const SkShaderBase::MatrixRec& mRec,
+                                                    const SkShaders::MatrixRec& mRec,
                                                     std::unique_ptr<GrFragmentProcessor> layout,
                                                     const SkMatrix* overrideMatrix) {
     // No shader is possible if a layout couldn't be created, e.g. a layout-specific Make() returned
@@ -766,12 +784,12 @@
     }
 
     // Some two-point conical gradients use a custom matrix here. Otherwise, use
-    // SkGradientShaderBase's matrix;
+    // SkGradientBaseShader's matrix;
     if (!overrideMatrix) {
         overrideMatrix = &shader.getGradientMatrix();
     }
     bool success;
-    std::tie(success, layout) = mRec.apply(std::move(layout), *overrideMatrix);
+    std::tie(success, layout) = apply_matrix(std::move(layout), mRec, *overrideMatrix);
     if (!success) {
         return nullptr;
     }
@@ -825,7 +843,7 @@
             break;
         case SkTileMode::kClamp:
             // For the clamped mode, the border colors are the first and last colors, corresponding
-            // to t=0 and t=1, because SkGradientShaderBase enforces that by adding color stops as
+            // to t=0 and t=1, because SkGradientBaseShader enforces that by adding color stops as
             // appropriate. If there is a hard stop, this grabs the expected outer colors for the
             // border.
             gradient = make_clamped_gradient(std::move(colorizer), std::move(layout),
@@ -852,7 +870,7 @@
 
 std::unique_ptr<GrFragmentProcessor> MakeLinear(const SkLinearGradient& shader,
                                                 const GrFPArgs& args,
-                                                const SkShaderBase::MatrixRec& mRec) {
+                                                const SkShaders::MatrixRec& mRec) {
     // We add a tiny delta to t. When gradient stops are set up so that a hard stop in a vertically
     // or horizontally oriented gradient falls exactly at a column or row of pixel centers we can
     // get slightly different interpolated t values along the column/row. By adding the delta
diff --git a/src/gpu/ganesh/gradients/GrGradientShader.h b/src/gpu/ganesh/gradients/GrGradientShader.h
index b35c037..ad7d151 100644
--- a/src/gpu/ganesh/gradients/GrGradientShader.h
+++ b/src/gpu/ganesh/gradients/GrGradientShader.h
@@ -10,7 +10,7 @@
 
 #include "src/gpu/ganesh/GrFPArgs.h"
 #include "src/gpu/ganesh/GrFragmentProcessor.h"
-#include "src/shaders/gradients/SkGradientShaderBase.h"
+#include "src/shaders/gradients/SkGradientBaseShader.h"
 #include "src/shaders/gradients/SkLinearGradient.h"
 
 #if GR_TEST_UTILS
@@ -18,15 +18,15 @@
 #endif
 
 namespace GrGradientShader {
-    std::unique_ptr<GrFragmentProcessor> MakeGradientFP(const SkGradientShaderBase& shader,
-                                                        const GrFPArgs& args,
-                                                        const SkShaderBase::MatrixRec&,
-                                                        std::unique_ptr<GrFragmentProcessor> layout,
-                                                        const SkMatrix* overrideMatrix = nullptr);
-
-    std::unique_ptr<GrFragmentProcessor> MakeLinear(const SkLinearGradient& shader,
+std::unique_ptr<GrFragmentProcessor> MakeGradientFP(const SkGradientBaseShader& shader,
                                                     const GrFPArgs& args,
-                                                    const SkShaderBase::MatrixRec&);
+                                                    const SkShaders::MatrixRec&,
+                                                    std::unique_ptr<GrFragmentProcessor> layout,
+                                                    const SkMatrix* overrideMatrix = nullptr);
+
+std::unique_ptr<GrFragmentProcessor> MakeLinear(const SkLinearGradient& shader,
+                                                const GrFPArgs& args,
+                                                const SkShaders::MatrixRec&);
 
 #if GR_TEST_UTILS
     /** Helper struct that stores (and populates) parameters to construct a random gradient.
diff --git a/src/gpu/graphite/KeyHelpers.cpp b/src/gpu/graphite/KeyHelpers.cpp
index 4954fd6..4f0696b 100644
--- a/src/gpu/graphite/KeyHelpers.cpp
+++ b/src/gpu/graphite/KeyHelpers.cpp
@@ -320,10 +320,9 @@
                 add_conical_gradient_uniform_data(dict, codeSnippetID, gradData, gatherer);
             }
             break;
-        case SkShaderBase::GradientType::kColor:
         case SkShaderBase::GradientType::kNone:
         default:
-            SkASSERT(0);
+            SkDEBUGFAIL("Expected a gradient shader, but it wasn't one.");
             break;
     }
 
diff --git a/src/opts/SkRasterPipeline_opts.h b/src/opts/SkRasterPipeline_opts.h
index 0bf1da6..15345b7 100644
--- a/src/opts/SkRasterPipeline_opts.h
+++ b/src/opts/SkRasterPipeline_opts.h
@@ -2185,7 +2185,7 @@
 
 // Skia stores all polar colors with hue in the first component, so this "LCH -> Lab" transform
 // actually takes "HCL". This is also used to do the same polar transform for OkHCL to OkLAB.
-// See similar comments & logic in SkGradientShaderBase.cpp.
+// See similar comments & logic in SkGradientBaseShader.cpp.
 STAGE(css_hcl_to_lab, NoCtx) {
     F H = r,
       C = g,
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index ca3cc12..540974d 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -48,6 +48,7 @@
 #include "src/pdf/SkPDFShader.h"
 #include "src/pdf/SkPDFTypes.h"
 #include "src/pdf/SkPDFUtils.h"
+#include "src/shaders/SkColorShader.h"
 #include "src/text/GlyphRun.h"
 #include "src/utils/SkClipStackUtils.h"
 
@@ -1174,18 +1175,11 @@
     if (shader) {
         // note: we always present the alpha as 1 for the shader, knowing that it will be
         //       accounted for when we create our newGraphicsState (below)
-        if (as_SB(shader)->asGradient() == SkShaderBase::GradientType::kColor) {
+        if (as_SB(shader)->type() == SkShaderBase::ShaderType::kColor) {
+            auto colorShader = static_cast<SkColorShader*>(shader);
             // We don't have to set a shader just for a color.
-            SkShaderBase::GradientInfo gradientInfo;
-            SkColor gradientColor = SK_ColorBLACK;
-            gradientInfo.fColors = &gradientColor;
-            gradientInfo.fColorOffsets = nullptr;
-            gradientInfo.fColorCount = 1;
-            SkAssertResult(as_SB(shader)->asGradient(&gradientInfo) ==
-                           SkShaderBase::GradientType::kColor);
-            color = SkColor4f::FromColor(gradientColor);
-            entry->fColor ={color.fR, color.fG, color.fB, 1};
-
+            color = SkColor4f::FromColor(colorShader->color());
+            entry->fColor = {color.fR, color.fG, color.fB, 1};
         } else {
             // PDF positions patterns relative to the initial transform, so
             // we need to apply the current transform to the shader parameters.
diff --git a/src/pdf/SkPDFGradientShader.cpp b/src/pdf/SkPDFGradientShader.cpp
index 54cb5cd..3ec2f86 100644
--- a/src/pdf/SkPDFGradientShader.cpp
+++ b/src/pdf/SkPDFGradientShader.cpp
@@ -758,7 +758,6 @@
                 transformPoints[1] = transformPoints[0];
                 transformPoints[1].fX += SK_Scalar1;
                 break;
-            case SkShaderBase::GradientType::kColor:
             case SkShaderBase::GradientType::kNone:
             default:
                 return SkPDFIndirectReference();
diff --git a/src/ports/SkGlobalInitialization_default.cpp b/src/ports/SkGlobalInitialization_default.cpp
index 71e9d16..e0f238a 100644
--- a/src/ports/SkGlobalInitialization_default.cpp
+++ b/src/ports/SkGlobalInitialization_default.cpp
@@ -21,11 +21,13 @@
     #include "include/effects/Sk2DPathEffect.h"
     #include "include/effects/SkCornerPathEffect.h"
     #include "include/effects/SkDiscretePathEffect.h"
+    #include "include/effects/SkImageFilters.h"
     #include "include/effects/SkOverdrawColorFilter.h"
     #include "include/effects/SkPerlinNoiseShader.h"
     #include "include/effects/SkShaderMaskFilter.h"
     #include "src/core/SkBlendModeBlender.h"
     #include "src/core/SkImageFilter_Base.h"
+    #include "src/core/SkLocalMatrixImageFilter.h"
     #include "src/core/SkRecordedDrawable.h"
     #include "src/effects/SkDashImpl.h"
     #include "src/effects/SkEmbossMaskFilter.h"
@@ -37,10 +39,7 @@
     #include "src/shaders/SkLocalMatrixShader.h"
     #include "src/shaders/SkPictureShader.h"
     #include "src/shaders/SkShaderBase.h"
-    #include "src/shaders/gradients/SkGradientShaderBase.h"
-
-    #include "include/effects/SkImageFilters.h"
-    #include "src/core/SkLocalMatrixImageFilter.h"
+    #include "src/shaders/gradients/SkGradientBaseShader.h"
 
 #ifdef SK_ENABLE_SKSL
     #include "include/effects/SkRuntimeEffect.h"
@@ -59,18 +58,18 @@
      */
     void SkFlattenable::PrivateInitializer::InitEffects() {
         // Shaders.
+        SkRegisterBlendShaderFlattenable();
         SkRegisterColor4ShaderFlattenable();
         SK_REGISTER_FLATTENABLE(SkColorFilterShader);
         SkRegisterColorShaderFlattenable();
-        SkRegisterComposeShaderFlattenable();
         SkRegisterCoordClampShaderFlattenable();
         SkRegisterEmptyShaderFlattenable();
         SK_REGISTER_FLATTENABLE(SkLocalMatrixShader);
         SK_REGISTER_FLATTENABLE(SkPictureShader);
+        SkRegisterConicalGradientShaderFlattenable();
         SkRegisterLinearGradientShaderFlattenable();
         SkRegisterRadialGradientShaderFlattenable();
         SkRegisterSweepGradientShaderFlattenable();
-        SkRegisterTwoPointConicalGradientShaderFlattenable();
         SkRegisterPerlinNoiseShaderFlattenable();
         SkShaderBase::RegisterFlattenables();
 
diff --git a/src/shaders/BUILD.bazel b/src/shaders/BUILD.bazel
index dca5db4..5db1548 100644
--- a/src/shaders/BUILD.bazel
+++ b/src/shaders/BUILD.bazel
@@ -8,22 +8,30 @@
 SHADER_FILES = [
     "SkBitmapProcShader.cpp",
     "SkBitmapProcShader.h",
-    "SkCoordClampShader.cpp",
+    "SkBlendShader.cpp",
+    "SkBlendShader.h",
     "SkColorFilterShader.cpp",
     "SkColorFilterShader.h",
     "SkColorShader.cpp",
-    "SkComposeShader.cpp",
+    "SkColorShader.h",
+    "SkCoordClampShader.cpp",
+    "SkCoordClampShader.h",
     "SkEmptyShader.cpp",
+    "SkEmptyShader.h",
     "SkGainmapShader.cpp",
     "SkImageShader.cpp",
     "SkImageShader.h",
     "SkLocalMatrixShader.cpp",
     "SkLocalMatrixShader.h",
-    "SkPerlinNoiseShader.cpp",
+    "SkPerlinNoiseShaderImpl.cpp",
+    "SkPerlinNoiseShaderImpl.h",
     "SkShader.cpp",
+    "SkShaderBase.cpp",
     "SkShaderBase.h",
     "SkTransformShader.cpp",
     "SkTransformShader.h",
+    "SkTriColorShader.cpp",
+    "SkTriColorShader.h",
 ]
 
 split_srcs_and_hdrs(
@@ -49,12 +57,25 @@
 )
 
 skia_filegroup(
+    name = "sksl_srcs",
+    srcs = ["SkRuntimeShader.cpp"],
+)
+
+skia_filegroup(
+    name = "sksl_hdrs",
+    srcs = ["SkRuntimeShader.h"],
+)
+
+skia_filegroup(
     name = "srcs",
     srcs = [
         ":shader_srcs",
         ":skpicture_srcs",
         "//src/shaders/gradients:srcs",
-    ],
+    ] + select({
+        "//src/sksl:needs_sksl": [":sksl_srcs"],
+        "//conditions:default": [],
+    }),
     visibility = ["//src:__pkg__"],
 )
 
@@ -64,6 +85,9 @@
         ":shader_hdrs",
         ":skpicture_hdrs",
         "//src/shaders/gradients:private_hdrs",
-    ],
+    ] + select({
+        "//src/sksl:needs_sksl": [":sksl_hdrs"],
+        "//conditions:default": [],
+    }),
     visibility = ["//src:__pkg__"],
 )
diff --git a/src/shaders/SkBitmapProcShader.cpp b/src/shaders/SkBitmapProcShader.cpp
index 159c0c4..dd4df85 100644
--- a/src/shaders/SkBitmapProcShader.cpp
+++ b/src/shaders/SkBitmapProcShader.cpp
@@ -7,9 +7,17 @@
 
 #include "src/shaders/SkBitmapProcShader.h"
 
+#include "include/core/SkColor.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkPixmap.h"
+#include "include/private/base/SkAssert.h"
 #include "src/base/SkArenaAlloc.h"
 #include "src/core/SkBitmapProcState.h"
-#include "src/core/SkPaintPriv.h"
+
+#include <algorithm>
+#include <cstdint>
+
+enum class SkTileMode;
 
 class BitmapProcShaderContext : public SkShaderBase::Context {
 public:
diff --git a/src/shaders/SkBitmapProcShader.h b/src/shaders/SkBitmapProcShader.h
index 763f304..72769739 100644
--- a/src/shaders/SkBitmapProcShader.h
+++ b/src/shaders/SkBitmapProcShader.h
@@ -7,10 +7,12 @@
 #ifndef SkBitmapProcShader_DEFINED
 #define SkBitmapProcShader_DEFINED
 
-#include "src/core/SkImagePriv.h"
+#include "include/core/SkSamplingOptions.h"
 #include "src/shaders/SkShaderBase.h"
 
+class SkArenaAlloc;
 class SkImage_Base;
+enum class SkTileMode;
 
 class SkBitmapProcLegacyShader : public SkShaderBase {
 private:
diff --git a/src/shaders/SkBlendShader.cpp b/src/shaders/SkBlendShader.cpp
new file mode 100644
index 0000000..e5358d3
--- /dev/null
+++ b/src/shaders/SkBlendShader.cpp
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2006 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/shaders/SkBlendShader.h"
+
+#include "include/core/SkBlendMode.h"
+#include "include/core/SkBlender.h"
+#include "include/core/SkData.h"
+#include "include/core/SkFlattenable.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "src/base/SkArenaAlloc.h"
+#include "src/core/SkBlendModePriv.h"
+#include "src/core/SkBlenderBase.h"
+#include "src/core/SkEffectPriv.h"
+#include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpContexts.h"
+#include "src/core/SkRasterPipelineOpList.h"
+#include "src/core/SkReadBuffer.h"
+#include "src/core/SkRuntimeEffectPriv.h"
+#include "src/core/SkWriteBuffer.h"
+#include "src/shaders/SkShaderBase.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/Blend.h"
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif
+
+#include <optional>
+
+sk_sp<SkFlattenable> SkBlendShader::CreateProc(SkReadBuffer& buffer) {
+    sk_sp<SkShader> dst(buffer.readShader());
+    sk_sp<SkShader> src(buffer.readShader());
+    if (!buffer.validate(dst && src)) {
+        return nullptr;
+    }
+
+    unsigned mode = buffer.read32();
+
+    if (mode == kCustom_SkBlendMode) {
+        sk_sp<SkBlender> blender = buffer.readBlender();
+        if (buffer.validate(blender != nullptr)) {
+            return SkShaders::Blend(std::move(blender), std::move(dst), std::move(src));
+        }
+    } else {
+        if (buffer.validate(mode <= (unsigned)SkBlendMode::kLastMode)) {
+            return SkShaders::Blend(static_cast<SkBlendMode>(mode), std::move(dst), std::move(src));
+        }
+    }
+    return nullptr;
+}
+
+void SkBlendShader::flatten(SkWriteBuffer& buffer) const {
+    buffer.writeFlattenable(fDst.get());
+    buffer.writeFlattenable(fSrc.get());
+    buffer.write32((int)fMode);
+}
+
+// Returns the output of e0, and leaves the output of e1 in r,g,b,a
+static float* append_two_shaders(const SkStageRec& rec,
+                                 const SkShaders::MatrixRec& mRec,
+                                 SkShader* s0,
+                                 SkShader* s1) {
+    struct Storage {
+        float fCoords[2 * SkRasterPipeline_kMaxStride];
+        float fRes0[4 * SkRasterPipeline_kMaxStride];
+    };
+    auto storage = rec.fAlloc->make<Storage>();
+
+    // Note we cannot simply apply mRec here and then unconditionally store the coordinates. When
+    // building for Android Framework it would interrupt the backwards local matrix concatenation if
+    // mRec had a pending local matrix and either of the children also had a local matrix.
+    // b/256873449
+    if (mRec.rasterPipelineCoordsAreSeeded()) {
+        rec.fPipeline->append(SkRasterPipelineOp::store_src_rg, storage->fCoords);
+    }
+    if (!as_SB(s0)->appendStages(rec, mRec)) {
+        return nullptr;
+    }
+    rec.fPipeline->append(SkRasterPipelineOp::store_src, storage->fRes0);
+
+    if (mRec.rasterPipelineCoordsAreSeeded()) {
+        rec.fPipeline->append(SkRasterPipelineOp::load_src_rg, storage->fCoords);
+    }
+    if (!as_SB(s1)->appendStages(rec, mRec)) {
+        return nullptr;
+    }
+    return storage->fRes0;
+}
+
+bool SkBlendShader::appendStages(const SkStageRec& rec, const SkShaders::MatrixRec& mRec) const {
+    float* res0 = append_two_shaders(rec, mRec, fDst.get(), fSrc.get());
+    if (!res0) {
+        return false;
+    }
+
+    rec.fPipeline->append(SkRasterPipelineOp::load_dst, res0);
+    SkBlendMode_AppendStages(fMode, rec.fPipeline);
+    return true;
+}
+
+#if defined(SK_ENABLE_SKVM)
+skvm::Color SkBlendShader::program(skvm::Builder* p,
+                                   skvm::Coord device,
+                                   skvm::Coord local,
+                                   skvm::Color paint,
+                                   const SkShaders::MatrixRec& mRec,
+                                   const SkColorInfo& cinfo,
+                                   skvm::Uniforms* uniforms,
+                                   SkArenaAlloc* alloc) const {
+    skvm::Color d, s;
+    if ((d = as_SB(fDst)->program(p, device, local, paint, mRec, cinfo, uniforms, alloc)) &&
+        (s = as_SB(fSrc)->program(p, device, local, paint, mRec, cinfo, uniforms, alloc))) {
+        return p->blend(fMode, s, d);
+    }
+    return {};
+}
+#endif
+
+#if defined(SK_GRAPHITE)
+void SkBlendShader::addToKey(const skgpu::graphite::KeyContext& keyContext,
+                             skgpu::graphite::PaintParamsKeyBuilder* builder,
+                             skgpu::graphite::PipelineDataGatherer* gatherer) const {
+    using namespace skgpu::graphite;
+
+    BlendShaderBlock::BeginBlock(keyContext, builder, gatherer);
+
+    as_SB(fSrc)->addToKey(keyContext, builder, gatherer);
+    as_SB(fDst)->addToKey(keyContext, builder, gatherer);
+
+    SkSpan<const float> porterDuffConstants = skgpu::GetPorterDuffBlendConstants(fMode);
+    if (!porterDuffConstants.empty()) {
+        CoeffBlenderBlock::BeginBlock(keyContext, builder, gatherer, porterDuffConstants);
+        builder->endBlock();
+    } else {
+        BlendModeBlenderBlock::BeginBlock(keyContext, builder, gatherer, fMode);
+        builder->endBlock();
+    }
+
+    builder->endBlock();  // BlendShaderBlock
+}
+#endif
+
+sk_sp<SkShader> SkShaders::Blend(SkBlendMode mode, sk_sp<SkShader> dst, sk_sp<SkShader> src) {
+    if (!src || !dst) {
+        return nullptr;
+    }
+    switch (mode) {
+        case SkBlendMode::kClear:
+            return Color(0);
+        case SkBlendMode::kDst:
+            return dst;
+        case SkBlendMode::kSrc:
+            return src;
+        default:
+            break;
+    }
+    return sk_sp<SkShader>(new SkBlendShader(mode, std::move(dst), std::move(src)));
+}
+
+sk_sp<SkShader> SkShaders::Blend(sk_sp<SkBlender> blender,
+                                 sk_sp<SkShader> dst,
+                                 sk_sp<SkShader> src) {
+    if (!src || !dst) {
+        return nullptr;
+    }
+    if (!blender) {
+        return SkShaders::Blend(SkBlendMode::kSrcOver, std::move(dst), std::move(src));
+    }
+    if (std::optional<SkBlendMode> mode = as_BB(blender)->asBlendMode()) {
+        return sk_make_sp<SkBlendShader>(mode.value(), std::move(dst), std::move(src));
+    }
+
+#ifdef SK_ENABLE_SKSL
+    // This isn't a built-in blend mode; we might as well use a runtime effect to evaluate it.
+    static SkRuntimeEffect* sBlendEffect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
+        "uniform shader s, d;"
+        "uniform blender b;"
+        "half4 main(float2 xy) {"
+            "return b.eval(s.eval(xy), d.eval(xy));"
+        "}"
+    );
+    SkRuntimeEffect::ChildPtr children[] = {std::move(src), std::move(dst), std::move(blender)};
+    return sBlendEffect->makeShader(/*uniforms=*/{}, children);
+#else
+    // We need SkSL to render this blend.
+    return nullptr;
+#endif
+}
+
+void SkRegisterBlendShaderFlattenable() {
+    SK_REGISTER_FLATTENABLE(SkBlendShader);
+    // Previous name
+    SkFlattenable::Register("SkShader_Blend", SkBlendShader::CreateProc);
+}
diff --git a/src/shaders/SkBlendShader.h b/src/shaders/SkBlendShader.h
new file mode 100644
index 0000000..0cea61e
--- /dev/null
+++ b/src/shaders/SkBlendShader.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkBlendShader_DEFINED
+#define SkBlendShader_DEFINED
+
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkShader.h"
+#include "src/shaders/SkShaderBase.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/Blend.h"
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif
+
+#if defined(SK_ENABLE_SKVM)
+#include "src/core/SkVM.h"
+#endif
+
+#include <utility>
+
+class SkReadBuffer;
+class SkWriteBuffer;
+enum class SkBlendMode;
+struct SkStageRec;
+
+class SkBlendShader final : public SkShaderBase {
+public:
+    SkBlendShader(SkBlendMode mode, sk_sp<SkShader> dst, sk_sp<SkShader> src)
+            : fDst(std::move(dst)), fSrc(std::move(src)), fMode(mode) {}
+
+    ShaderType type() const override { return ShaderType::kBlend; }
+
+#if defined(SK_GRAPHITE)
+    void addToKey(const skgpu::graphite::KeyContext&,
+                  skgpu::graphite::PaintParamsKeyBuilder*,
+                  skgpu::graphite::PipelineDataGatherer*) const override;
+#endif
+
+    sk_sp<SkShader> dst() const { return fDst; }
+    sk_sp<SkShader> src() const { return fSrc; }
+    SkBlendMode mode() const { return fMode; }
+
+protected:
+    SkBlendShader(SkReadBuffer&);
+    void flatten(SkWriteBuffer&) const override;
+    bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const override;
+
+#if defined(SK_ENABLE_SKVM)
+    skvm::Color program(skvm::Builder*,
+                        skvm::Coord device,
+                        skvm::Coord local,
+                        skvm::Color paint,
+                        const SkShaders::MatrixRec& mRec,
+                        const SkColorInfo& dst,
+                        skvm::Uniforms*,
+                        SkArenaAlloc*) const override;
+#endif  // defined(SK_ENABLE_SKVM)
+
+private:
+    friend void ::SkRegisterBlendShaderFlattenable();
+    SK_FLATTENABLE_HOOKS(SkBlendShader)
+
+    sk_sp<SkShader> fDst;
+    sk_sp<SkShader> fSrc;
+    SkBlendMode fMode;
+};
+
+#endif
diff --git a/src/shaders/SkColorFilterShader.cpp b/src/shaders/SkColorFilterShader.cpp
index a862a87..4745cea 100644
--- a/src/shaders/SkColorFilterShader.cpp
+++ b/src/shaders/SkColorFilterShader.cpp
@@ -5,27 +5,26 @@
  * found in the LICENSE file.
  */
 
-#include "include/core/SkShader.h"
-#include "include/core/SkString.h"
-#include "src/base/SkArenaAlloc.h"
-#include "src/core/SkRasterPipeline.h"
-#include "src/core/SkReadBuffer.h"
-#include "src/core/SkVM.h"
-#include "src/core/SkWriteBuffer.h"
-#include "src/effects/colorfilters/SkColorFilterBase.h"
 #include "src/shaders/SkColorFilterShader.h"
 
-#if defined(SK_GANESH)
-#include "src/gpu/ganesh/GrFPArgs.h"
-#include "src/gpu/ganesh/GrFragmentProcessor.h"
-#include "src/gpu/ganesh/GrFragmentProcessors.h"
-#endif
+#include "include/core/SkColorFilter.h"
+#include "include/core/SkShader.h"
+#include "include/private/base/SkAssert.h"
+#include "src/base/SkArenaAlloc.h"
+#include "src/core/SkEffectPriv.h"
+#include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpList.h"
+#include "src/core/SkReadBuffer.h"
+#include "src/core/SkWriteBuffer.h"
+#include "src/effects/colorfilters/SkColorFilterBase.h"
 
 #if defined(SK_GRAPHITE)
 #include "src/gpu/graphite/KeyHelpers.h"
 #include "src/gpu/graphite/PaintParamsKey.h"
 #endif
 
+#include <utility>
+
 SkColorFilterShader::SkColorFilterShader(sk_sp<SkShader> shader,
                                          float alpha,
                                          sk_sp<SkColorFilter> filter)
@@ -56,7 +55,8 @@
     buffer.writeFlattenable(fFilter.get());
 }
 
-bool SkColorFilterShader::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
+bool SkColorFilterShader::appendStages(const SkStageRec& rec,
+                                       const SkShaders::MatrixRec& mRec) const {
     if (!as_SB(fShader)->appendStages(rec, mRec)) {
         return false;
     }
@@ -74,7 +74,7 @@
                                          skvm::Coord device,
                                          skvm::Coord local,
                                          skvm::Color paint,
-                                         const MatrixRec& mRec,
+                                         const SkShaders::MatrixRec& mRec,
                                          const SkColorInfo& dst,
                                          skvm::Uniforms* uniforms,
                                          SkArenaAlloc* alloc) const {
@@ -96,29 +96,6 @@
     return fFilter->program(p,c, dst, uniforms,alloc);
 }
 #endif
-#if defined(SK_GANESH)
-/////////////////////////////////////////////////////////////////////
-
-std::unique_ptr<GrFragmentProcessor>
-SkColorFilterShader::asFragmentProcessor(const GrFPArgs& args, const MatrixRec& mRec) const {
-    auto shaderFP = as_SB(fShader)->asFragmentProcessor(args, mRec);
-    if (!shaderFP) {
-        return nullptr;
-    }
-
-    // TODO I guess, but it shouldn't come up as used today.
-    SkASSERT(fAlpha == 1.0f);
-
-    auto [success, fp] = GrFragmentProcessors::Make(args.fContext,
-                                                    fFilter.get(),
-                                                    std::move(shaderFP),
-                                                    *args.fDstColorInfo,
-                                                    args.fSurfaceProps);
-    // If the filter FP could not be created, we still want to return the shader FP, so checking
-    // success can be omitted here.
-    return std::move(fp);
-}
-#endif
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -137,14 +114,4 @@
     builder->endBlock();
 }
 
-#endif // SK_ENABLE_SKSL
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-sk_sp<SkShader> SkShader::makeWithColorFilter(sk_sp<SkColorFilter> filter) const {
-    SkShader* base = const_cast<SkShader*>(this);
-    if (!filter) {
-        return sk_ref_sp(base);
-    }
-    return sk_make_sp<SkColorFilterShader>(sk_ref_sp(base), 1.0f, std::move(filter));
-}
+#endif  // SK_ENABLE_SKSL
diff --git a/src/shaders/SkColorFilterShader.h b/src/shaders/SkColorFilterShader.h
index 34406bf..95e3af2 100644
--- a/src/shaders/SkColorFilterShader.h
+++ b/src/shaders/SkColorFilterShader.h
@@ -8,36 +8,44 @@
 #ifndef SkColorFilterShader_DEFINED
 #define SkColorFilterShader_DEFINED
 
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkShader.h"
 #include "src/effects/colorfilters/SkColorFilterBase.h"
 #include "src/shaders/SkShaderBase.h"
 
-class SkArenaAlloc;
+class SkColorFilter;
+class SkReadBuffer;
+class SkWriteBuffer;
+struct SkStageRec;
 
 class SkColorFilterShader : public SkShaderBase {
 public:
     SkColorFilterShader(sk_sp<SkShader> shader, float alpha, sk_sp<SkColorFilter> filter);
 
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
+    ShaderType type() const override { return ShaderType::kColorFilter; }
+
 #if defined(SK_GRAPHITE)
     void addToKey(const skgpu::graphite::KeyContext&,
                   skgpu::graphite::PaintParamsKeyBuilder*,
                   skgpu::graphite::PipelineDataGatherer*) const override;
 #endif
 
+    sk_sp<SkShader> shader() const { return fShader; }
+    sk_sp<SkColorFilterBase> filter() const { return fFilter; }
+    float alpha() const { return fAlpha; }
+
 private:
     bool isOpaque() const override;
     void flatten(SkWriteBuffer&) const override;
-    bool appendStages(const SkStageRec&, const MatrixRec&) const override;
+    bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const override;
 
 #if defined(SK_ENABLE_SKVM)
     skvm::Color program(skvm::Builder*,
                         skvm::Coord device,
                         skvm::Coord local,
                         skvm::Color paint,
-                        const MatrixRec&,
+                        const SkShaders::MatrixRec&,
                         const SkColorInfo& dst,
                         skvm::Uniforms* uniforms,
                         SkArenaAlloc*) const override;
@@ -47,9 +55,7 @@
 
     sk_sp<SkShader>          fShader;
     sk_sp<SkColorFilterBase> fFilter;
-    float                    fAlpha;
-
-    using INHERITED = SkShaderBase;
+    float fAlpha;
 };
 
 #endif
diff --git a/src/shaders/SkColorShader.cpp b/src/shaders/SkColorShader.cpp
index 829b291..bd611e6 100644
--- a/src/shaders/SkColorShader.cpp
+++ b/src/shaders/SkColorShader.cpp
@@ -5,15 +5,20 @@
  * found in the LICENSE file.
  */
 
+#include "src/shaders/SkColorShader.h"
+
+#include "include/core/SkAlphaType.h"
 #include "include/core/SkColorSpace.h"
+#include "include/core/SkData.h"
 #include "include/core/SkFlattenable.h"
-#include "src/base/SkArenaAlloc.h"
-#include "src/base/SkUtils.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkShader.h"
+#include "include/private/base/SkTPin.h"
 #include "src/core/SkColorSpacePriv.h"
 #include "src/core/SkColorSpaceXformSteps.h"
+#include "src/core/SkEffectPriv.h"
 #include "src/core/SkRasterPipeline.h"
 #include "src/core/SkReadBuffer.h"
-#include "src/core/SkVM.h"
 #include "src/core/SkWriteBuffer.h"
 #include "src/shaders/SkShaderBase.h"
 
@@ -22,100 +27,7 @@
 #include "src/gpu/graphite/PaintParamsKey.h"
 #endif
 
-/** \class SkColorShader
-    A Shader that represents a single color. In general, this effect can be
-    accomplished by just using the color field on the paint, but if an
-    actual shader object is needed, this provides that feature.
-*/
-class SkColorShader : public SkShaderBase {
-public:
-    /** Create a ColorShader that ignores the color in the paint, and uses the
-        specified color. Note: like all shaders, at draw time the paint's alpha
-        will be respected, and is applied to the specified color.
-    */
-    explicit SkColorShader(SkColor c);
-
-    bool isOpaque() const override;
-    bool isConstant() const override { return true; }
-
-    GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override;
-
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
-
-#if defined(SK_GRAPHITE)
-    void addToKey(const skgpu::graphite::KeyContext&,
-                  skgpu::graphite::PaintParamsKeyBuilder*,
-                  skgpu::graphite::PipelineDataGatherer*) const override;
-#endif
-
-private:
-    friend void ::SkRegisterColorShaderFlattenable();
-    SK_FLATTENABLE_HOOKS(SkColorShader)
-
-    void flatten(SkWriteBuffer&) const override;
-
-    bool onAsLuminanceColor(SkColor* lum) const override {
-        *lum = fColor;
-        return true;
-    }
-
-    bool appendStages(const SkStageRec&, const MatrixRec&) const override;
-
-#if defined(SK_ENABLE_SKVM)
-    skvm::Color program(skvm::Builder*,
-                        skvm::Coord device,
-                        skvm::Coord local,
-                        skvm::Color paint,
-                        const MatrixRec&,
-                        const SkColorInfo& dst,
-                        skvm::Uniforms* uniforms,
-                        SkArenaAlloc*) const override;
-#endif
-
-    SkColor fColor;
-};
-
-class SkColor4Shader : public SkShaderBase {
-public:
-    SkColor4Shader(const SkColor4f&, sk_sp<SkColorSpace>);
-
-    bool isOpaque()   const override { return fColor.isOpaque(); }
-    bool isConstant() const override { return true; }
-
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
-#if defined(SK_GRAPHITE)
-    void addToKey(const skgpu::graphite::KeyContext&,
-                  skgpu::graphite::PaintParamsKeyBuilder*,
-                  skgpu::graphite::PipelineDataGatherer*) const override;
-#endif
-
-private:
-    friend void ::SkRegisterColor4ShaderFlattenable();
-    SK_FLATTENABLE_HOOKS(SkColor4Shader)
-
-    void flatten(SkWriteBuffer&) const override;
-    bool appendStages(const SkStageRec&, const MatrixRec&) const override;
-
-#if defined(SK_ENABLE_SKVM)
-    skvm::Color program(skvm::Builder*,
-                        skvm::Coord device,
-                        skvm::Coord local,
-                        skvm::Color paint,
-                        const MatrixRec&,
-                        const SkColorInfo& dst,
-                        skvm::Uniforms* uniforms,
-                        SkArenaAlloc*) const override;
-#endif
-
-    sk_sp<SkColorSpace> fColorSpace;
-    const SkColor4f     fColor;
-};
+#include <utility>
 
 SkColorShader::SkColorShader(SkColor c) : fColor(c) {}
 
@@ -131,21 +43,6 @@
     buffer.writeColor(fColor);
 }
 
-SkShaderBase::GradientType SkColorShader::asGradient(GradientInfo* info,
-                                                     SkMatrix* localMatrix) const {
-    if (info) {
-        if (info->fColors && info->fColorCount >= 1) {
-            info->fColors[0] = fColor;
-        }
-        info->fColorCount = 1;
-        info->fTileMode = SkTileMode::kRepeat;
-    }
-    if (localMatrix) {
-        *localMatrix = SkMatrix::I();
-    }
-    return GradientType::kColor;
-}
-
 SkColor4Shader::SkColor4Shader(const SkColor4f& color, sk_sp<SkColorSpace> space)
     : fColorSpace(std::move(space))
     , fColor({color.fR, color.fG, color.fB, SkTPin(color.fA, 0.0f, 1.0f)})
@@ -173,7 +70,7 @@
     }
 }
 
-bool SkColorShader::appendStages(const SkStageRec& rec, const MatrixRec&) const {
+bool SkColorShader::appendStages(const SkStageRec& rec, const SkShaders::MatrixRec&) const {
     SkColor4f color = SkColor4f::FromColor(fColor);
     SkColorSpaceXformSteps(sk_srgb_singleton(), kUnpremul_SkAlphaType,
                            rec.fDstCS,          kUnpremul_SkAlphaType).apply(color.vec());
@@ -181,7 +78,7 @@
     return true;
 }
 
-bool SkColor4Shader::appendStages(const SkStageRec& rec, const MatrixRec&) const {
+bool SkColor4Shader::appendStages(const SkStageRec& rec, const SkShaders::MatrixRec&) const {
     SkColor4f color = fColor;
     SkColorSpaceXformSteps(fColorSpace.get(), kUnpremul_SkAlphaType,
                            rec.fDstCS,        kUnpremul_SkAlphaType).apply(color.vec());
@@ -194,7 +91,7 @@
                                    skvm::Coord /*device*/,
                                    skvm::Coord /*local*/,
                                    skvm::Color /*paint*/,
-                                   const MatrixRec&,
+                                   const SkShaders::MatrixRec&,
                                    const SkColorInfo& dst,
                                    skvm::Uniforms* uniforms,
                                    SkArenaAlloc*) const {
@@ -208,7 +105,7 @@
                                     skvm::Coord /*device*/,
                                     skvm::Coord /*local*/,
                                     skvm::Color /*paint*/,
-                                    const MatrixRec&,
+                                    const SkShaders::MatrixRec&,
                                     const SkColorInfo& dst,
                                     skvm::Uniforms* uniforms,
                                     SkArenaAlloc*) const {
@@ -219,30 +116,6 @@
 }
 #endif  // defined(SK_ENABLE_SKVM)
 
-#if defined(SK_GANESH)
-
-#include "src/gpu/ganesh/GrColorInfo.h"
-#include "src/gpu/ganesh/GrColorSpaceXform.h"
-#include "src/gpu/ganesh/GrFPArgs.h"
-#include "src/gpu/ganesh/GrFragmentProcessor.h"
-#include "src/gpu/ganesh/SkGr.h"
-
-std::unique_ptr<GrFragmentProcessor> SkColorShader::asFragmentProcessor(const GrFPArgs& args,
-                                                                        const MatrixRec&) const {
-    return GrFragmentProcessor::MakeColor(SkColorToPMColor4f(fColor, *args.fDstColorInfo));
-}
-
-std::unique_ptr<GrFragmentProcessor> SkColor4Shader::asFragmentProcessor(const GrFPArgs& args,
-                                                                         const MatrixRec&) const {
-    SkColorSpaceXformSteps steps{ fColorSpace.get(),                kUnpremul_SkAlphaType,
-                                  args.fDstColorInfo->colorSpace(), kUnpremul_SkAlphaType };
-    SkColor4f color = fColor;
-    steps.apply(color.vec());
-    return GrFragmentProcessor::MakeColor(color.premul());
-}
-
-#endif
-
 #if defined(SK_GRAPHITE)
 void SkColorShader::addToKey(const skgpu::graphite::KeyContext& keyContext,
                              skgpu::graphite::PaintParamsKeyBuilder* builder,
@@ -264,14 +137,39 @@
 }
 #endif
 
-sk_sp<SkShader> SkShaders::Color(SkColor color) { return sk_make_sp<SkColorShader>(color); }
+SkUpdatableColorShader::SkUpdatableColorShader(SkColorSpace* cs)
+        : fSteps{sk_srgb_singleton(), kUnpremul_SkAlphaType, cs, kUnpremul_SkAlphaType} {}
 
-sk_sp<SkShader> SkShaders::Color(const SkColor4f& color, sk_sp<SkColorSpace> space) {
-    if (!SkScalarsAreFinite(color.vec(), 4)) {
-        return nullptr;
-    }
-    return sk_make_sp<SkColor4Shader>(color, std::move(space));
+#if defined(SK_ENABLE_SKVM)
+skvm::Color SkUpdatableColorShader::program(skvm::Builder* builder,
+                                            skvm::Coord device,
+                                            skvm::Coord local,
+                                            skvm::Color paint,
+                                            const SkShaders::MatrixRec&,
+                                            const SkColorInfo& dst,
+                                            skvm::Uniforms* uniforms,
+                                            SkArenaAlloc* alloc) const {
+    skvm::Uniform color = uniforms->pushPtr(fValues);
+    skvm::F32 r = builder->arrayF(color, 0);
+    skvm::F32 g = builder->arrayF(color, 1);
+    skvm::F32 b = builder->arrayF(color, 2);
+    skvm::F32 a = builder->arrayF(color, 3);
+
+    return {r, g, b, a};
 }
+#endif
+
+void SkUpdatableColorShader::updateColor(SkColor c) const {
+    SkColor4f c4 = SkColor4f::FromColor(c);
+    fSteps.apply(c4.vec());
+    auto cp4 = c4.premul();
+    fValues[0] = cp4.fR;
+    fValues[1] = cp4.fG;
+    fValues[2] = cp4.fB;
+    fValues[3] = cp4.fA;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
 
 void SkRegisterColor4ShaderFlattenable() {
     SK_REGISTER_FLATTENABLE(SkColor4Shader);
@@ -280,3 +178,14 @@
 void SkRegisterColorShaderFlattenable() {
     SK_REGISTER_FLATTENABLE(SkColorShader);
 }
+
+namespace SkShaders {
+sk_sp<SkShader> Color(SkColor color) { return sk_make_sp<SkColorShader>(color); }
+
+sk_sp<SkShader> Color(const SkColor4f& color, sk_sp<SkColorSpace> space) {
+    if (!SkScalarsAreFinite(color.vec(), 4)) {
+        return nullptr;
+    }
+    return sk_make_sp<SkColor4Shader>(color, std::move(space));
+}
+}  // namespace SkShaders
diff --git a/src/shaders/SkColorShader.h b/src/shaders/SkColorShader.h
new file mode 100644
index 0000000..c5fedc5
--- /dev/null
+++ b/src/shaders/SkColorShader.h
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkColorShader_DEFINED
+#define SkColorShader_DEFINED
+
+#include "include/core/SkColor.h"
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkRefCnt.h"
+#include "src/core/SkColorSpaceXformSteps.h"
+#include "src/shaders/SkShaderBase.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif
+
+class SkReadBuffer;
+class SkWriteBuffer;
+struct SkStageRec;
+
+/** \class SkColorShader
+    A Shader that represents a single color. In general, this effect can be
+    accomplished by just using the color field on the paint, but if an
+    actual shader object is needed, this provides that feature.
+*/
+class SkColorShader : public SkShaderBase {
+public:
+    /** Create a ColorShader that ignores the color in the paint, and uses the
+        specified color. Note: like all shaders, at draw time the paint's alpha
+        will be respected, and is applied to the specified color.
+    */
+    explicit SkColorShader(SkColor c);
+
+    bool isOpaque() const override;
+    bool isConstant() const override { return true; }
+
+    ShaderType type() const override { return ShaderType::kColor; }
+
+#if defined(SK_GRAPHITE)
+    void addToKey(const skgpu::graphite::KeyContext&,
+                  skgpu::graphite::PaintParamsKeyBuilder*,
+                  skgpu::graphite::PipelineDataGatherer*) const override;
+#endif
+
+    SkColor color() const { return fColor; }
+
+private:
+    friend void ::SkRegisterColorShaderFlattenable();
+    SK_FLATTENABLE_HOOKS(SkColorShader)
+
+    void flatten(SkWriteBuffer&) const override;
+
+    bool onAsLuminanceColor(SkColor* lum) const override {
+        *lum = fColor;
+        return true;
+    }
+
+    bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const override;
+
+#if defined(SK_ENABLE_SKVM)
+    skvm::Color program(skvm::Builder*,
+                        skvm::Coord device,
+                        skvm::Coord local,
+                        skvm::Color paint,
+                        const SkShaders::MatrixRec&,
+                        const SkColorInfo& dst,
+                        skvm::Uniforms* uniforms,
+                        SkArenaAlloc*) const override;
+#endif
+
+    SkColor fColor;
+};
+
+class SkColor4Shader : public SkShaderBase {
+public:
+    SkColor4Shader(const SkColor4f&, sk_sp<SkColorSpace>);
+
+    bool isOpaque() const override { return fColor.isOpaque(); }
+    bool isConstant() const override { return true; }
+
+    ShaderType type() const override { return ShaderType::kColor4; }
+
+#if defined(SK_GRAPHITE)
+    void addToKey(const skgpu::graphite::KeyContext&,
+                  skgpu::graphite::PaintParamsKeyBuilder*,
+                  skgpu::graphite::PipelineDataGatherer*) const override;
+#endif
+
+    sk_sp<SkColorSpace> colorSpace() const { return fColorSpace; }
+    SkColor4f color() const { return fColor; }
+
+private:
+    friend void ::SkRegisterColor4ShaderFlattenable();
+    SK_FLATTENABLE_HOOKS(SkColor4Shader)
+
+    void flatten(SkWriteBuffer&) const override;
+    bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const override;
+
+#if defined(SK_ENABLE_SKVM)
+    skvm::Color program(skvm::Builder*,
+                        skvm::Coord device,
+                        skvm::Coord local,
+                        skvm::Color paint,
+                        const SkShaders::MatrixRec&,
+                        const SkColorInfo& dst,
+                        skvm::Uniforms* uniforms,
+                        SkArenaAlloc*) const override;
+#endif
+
+    sk_sp<SkColorSpace> fColorSpace;
+    const SkColor4f fColor;
+};
+
+class SkUpdatableColorShader : public SkShaderBase {
+public:
+    explicit SkUpdatableColorShader(SkColorSpace* cs);
+#if defined(SK_ENABLE_SKVM)
+    skvm::Color program(skvm::Builder* builder,
+                        skvm::Coord device,
+                        skvm::Coord local,
+                        skvm::Color paint,
+                        const SkShaders::MatrixRec&,
+                        const SkColorInfo& dst,
+                        skvm::Uniforms* uniforms,
+                        SkArenaAlloc* alloc) const override;
+#endif
+
+    ShaderType type() const override { return ShaderType::kUpdatableColor; }
+
+    void updateColor(SkColor c) const;
+
+private:
+    // For serialization.  This will never be called.
+    Factory getFactory() const override { return nullptr; }
+    const char* getTypeName() const override { return nullptr; }
+
+    SkColorSpaceXformSteps fSteps;
+    mutable float fValues[4];
+};
+
+#endif
diff --git a/src/shaders/SkComposeShader.cpp b/src/shaders/SkComposeShader.cpp
deleted file mode 100644
index 767aa5e..0000000
--- a/src/shaders/SkComposeShader.cpp
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright 2006 The Android Open Source Project
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "include/core/SkColorFilter.h"
-#include "include/core/SkFlattenable.h"
-#include "include/core/SkString.h"
-#include "include/effects/SkRuntimeEffect.h"
-#include "include/private/SkColorData.h"
-#include "src/base/SkArenaAlloc.h"
-#include "src/core/SkBlendModePriv.h"
-#include "src/core/SkBlenderBase.h"
-#include "src/core/SkRasterPipeline.h"
-#include "src/core/SkReadBuffer.h"
-#include "src/core/SkRuntimeEffectPriv.h"
-#include "src/core/SkVM.h"
-#include "src/core/SkWriteBuffer.h"
-#include "src/shaders/SkShaderBase.h"
-
-#if defined(SK_GRAPHITE)
-#include "src/gpu/Blend.h"
-#include "src/gpu/graphite/KeyHelpers.h"
-#include "src/gpu/graphite/PaintParamsKey.h"
-#endif
-
-class SkShader_Blend final : public SkShaderBase {
-public:
-    SkShader_Blend(SkBlendMode mode, sk_sp<SkShader> dst, sk_sp<SkShader> src)
-            : fDst(std::move(dst))
-            , fSrc(std::move(src))
-            , fMode(mode) {}
-
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
-
-#if defined(SK_GRAPHITE)
-    void addToKey(const skgpu::graphite::KeyContext&,
-                  skgpu::graphite::PaintParamsKeyBuilder*,
-                  skgpu::graphite::PipelineDataGatherer*) const override;
-#endif
-
-protected:
-    SkShader_Blend(SkReadBuffer&);
-    void flatten(SkWriteBuffer&) const override;
-    bool appendStages(const SkStageRec&, const MatrixRec&) const override;
-
-#if defined(SK_ENABLE_SKVM)
-    skvm::Color program(skvm::Builder*,
-                        skvm::Coord device,
-                        skvm::Coord local,
-                        skvm::Color paint,
-                        const MatrixRec& mRec,
-                        const SkColorInfo& dst,
-                        skvm::Uniforms*,
-                        SkArenaAlloc*) const override;
-#endif  // defined(SK_ENABLE_SKVM)
-
-private:
-    friend void ::SkRegisterComposeShaderFlattenable();
-    SK_FLATTENABLE_HOOKS(SkShader_Blend)
-
-    sk_sp<SkShader>     fDst;
-    sk_sp<SkShader>     fSrc;
-    SkBlendMode         fMode;
-
-    using INHERITED = SkShaderBase;
-};
-
-sk_sp<SkFlattenable> SkShader_Blend::CreateProc(SkReadBuffer& buffer) {
-    sk_sp<SkShader> dst(buffer.readShader());
-    sk_sp<SkShader> src(buffer.readShader());
-    if (!buffer.validate(dst && src)) {
-        return nullptr;
-    }
-
-    unsigned mode = buffer.read32();
-
-    if (mode == kCustom_SkBlendMode) {
-        sk_sp<SkBlender> blender = buffer.readBlender();
-        if (buffer.validate(blender != nullptr)) {
-            return SkShaders::Blend(std::move(blender), std::move(dst), std::move(src));
-        }
-    } else {
-        if (buffer.validate(mode <= (unsigned)SkBlendMode::kLastMode)) {
-            return SkShaders::Blend(static_cast<SkBlendMode>(mode), std::move(dst), std::move(src));
-        }
-    }
-    return nullptr;
-}
-
-void SkShader_Blend::flatten(SkWriteBuffer& buffer) const {
-    buffer.writeFlattenable(fDst.get());
-    buffer.writeFlattenable(fSrc.get());
-    buffer.write32((int)fMode);
-}
-
-// Returns the output of e0, and leaves the output of e1 in r,g,b,a
-static float* append_two_shaders(const SkStageRec& rec,
-                                 const SkShaderBase::MatrixRec& mRec,
-                                 SkShader* s0,
-                                 SkShader* s1) {
-    struct Storage {
-        float   fCoords[2 * SkRasterPipeline_kMaxStride];
-        float   fRes0  [4 * SkRasterPipeline_kMaxStride];
-    };
-    auto storage = rec.fAlloc->make<Storage>();
-
-    // Note we cannot simply apply mRec here and then unconditionally store the coordinates. When
-    // building for Android Framework it would interrupt the backwards local matrix concatenation if
-    // mRec had a pending local matrix and either of the children also had a local matrix.
-    // b/256873449
-    if (mRec.rasterPipelineCoordsAreSeeded()) {
-        rec.fPipeline->append(SkRasterPipelineOp::store_src_rg, storage->fCoords);
-    }
-    if (!as_SB(s0)->appendStages(rec, mRec)) {
-        return nullptr;
-    }
-    rec.fPipeline->append(SkRasterPipelineOp::store_src, storage->fRes0);
-
-    if (mRec.rasterPipelineCoordsAreSeeded()) {
-        rec.fPipeline->append(SkRasterPipelineOp::load_src_rg, storage->fCoords);
-    }
-    if (!as_SB(s1)->appendStages(rec, mRec)) {
-        return nullptr;
-    }
-    return storage->fRes0;
-}
-
-bool SkShader_Blend::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
-    float* res0 = append_two_shaders(rec, mRec, fDst.get(), fSrc.get());
-    if (!res0) {
-        return false;
-    }
-
-    rec.fPipeline->append(SkRasterPipelineOp::load_dst, res0);
-    SkBlendMode_AppendStages(fMode, rec.fPipeline);
-    return true;
-}
-
-#if defined(SK_ENABLE_SKVM)
-skvm::Color SkShader_Blend::program(skvm::Builder* p,
-                                    skvm::Coord device,
-                                    skvm::Coord local,
-                                    skvm::Color paint,
-                                    const MatrixRec& mRec,
-                                    const SkColorInfo& cinfo,
-                                    skvm::Uniforms* uniforms,
-                                    SkArenaAlloc* alloc) const {
-    skvm::Color d,s;
-    if ((d = as_SB(fDst)->program(p, device, local, paint, mRec, cinfo, uniforms, alloc)) &&
-        (s = as_SB(fSrc)->program(p, device, local, paint, mRec, cinfo, uniforms, alloc))) {
-        return p->blend(fMode, s,d);
-    }
-    return {};
-}
-#endif
-
-#if defined(SK_GANESH)
-
-#include "include/gpu/GrRecordingContext.h"
-#include "src/gpu/ganesh/GrFPArgs.h"
-#include "src/gpu/ganesh/GrFragmentProcessor.h"
-#include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h"
-
-std::unique_ptr<GrFragmentProcessor>
-SkShader_Blend::asFragmentProcessor(const GrFPArgs& args, const MatrixRec& mRec) const {
-    auto fpA = as_SB(fDst)->asFragmentProcessor(args, mRec);
-    auto fpB = as_SB(fSrc)->asFragmentProcessor(args, mRec);
-    if (!fpA || !fpB) {
-        // This is unexpected. Both src and dst shaders should be valid. Just fail.
-        return nullptr;
-    }
-    return GrBlendFragmentProcessor::Make(std::move(fpB), std::move(fpA), fMode);
-}
-#endif
-
-#if defined(SK_GRAPHITE)
-void SkShader_Blend::addToKey(const skgpu::graphite::KeyContext& keyContext,
-                              skgpu::graphite::PaintParamsKeyBuilder* builder,
-                              skgpu::graphite::PipelineDataGatherer* gatherer) const {
-    using namespace skgpu::graphite;
-
-    BlendShaderBlock::BeginBlock(keyContext, builder, gatherer);
-
-    as_SB(fSrc)->addToKey(keyContext, builder, gatherer);
-    as_SB(fDst)->addToKey(keyContext, builder, gatherer);
-
-    SkSpan<const float> porterDuffConstants = skgpu::GetPorterDuffBlendConstants(fMode);
-    if (!porterDuffConstants.empty()) {
-        CoeffBlenderBlock::BeginBlock(keyContext, builder, gatherer, porterDuffConstants);
-        builder->endBlock();
-    } else {
-        BlendModeBlenderBlock::BeginBlock(keyContext, builder, gatherer, fMode);
-        builder->endBlock();
-    }
-
-    builder->endBlock();  // BlendShaderBlock
-}
-#endif
-
-sk_sp<SkShader> SkShaders::Blend(SkBlendMode mode, sk_sp<SkShader> dst, sk_sp<SkShader> src) {
-    if (!src || !dst) {
-        return nullptr;
-    }
-    switch (mode) {
-        case SkBlendMode::kClear: return Color(0);
-        case SkBlendMode::kDst:   return dst;
-        case SkBlendMode::kSrc:   return src;
-        default: break;
-    }
-    return sk_sp<SkShader>(new SkShader_Blend(mode, std::move(dst), std::move(src)));
-}
-
-sk_sp<SkShader> SkShaders::Blend(sk_sp<SkBlender> blender,
-                                 sk_sp<SkShader> dst,
-                                 sk_sp<SkShader> src) {
-    if (!src || !dst) {
-        return nullptr;
-    }
-    if (!blender) {
-        return SkShaders::Blend(SkBlendMode::kSrcOver, std::move(dst), std::move(src));
-    }
-    if (std::optional<SkBlendMode> mode = as_BB(blender)->asBlendMode()) {
-        return sk_make_sp<SkShader_Blend>(mode.value(), std::move(dst), std::move(src));
-    }
-
-#ifdef SK_ENABLE_SKSL
-    // This isn't a built-in blend mode; we might as well use a runtime effect to evaluate it.
-    static SkRuntimeEffect* sBlendEffect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
-        "uniform shader s, d;"
-        "uniform blender b;"
-        "half4 main(float2 xy) {"
-            "return b.eval(s.eval(xy), d.eval(xy));"
-        "}"
-    );
-    SkRuntimeEffect::ChildPtr children[] = {std::move(src), std::move(dst), std::move(blender)};
-    return sBlendEffect->makeShader(/*uniforms=*/{}, children);
-#else
-    // We need SkSL to render this blend.
-    return nullptr;
-#endif
-}
-
-void SkRegisterComposeShaderFlattenable() {
-    SK_REGISTER_FLATTENABLE(SkShader_Blend);
-}
diff --git a/src/shaders/SkCoordClampShader.cpp b/src/shaders/SkCoordClampShader.cpp
index a9ee141..9c5336d 100644
--- a/src/shaders/SkCoordClampShader.cpp
+++ b/src/shaders/SkCoordClampShader.cpp
@@ -5,67 +5,32 @@
  * found in the LICENSE file.
  */
 
+#include "src/shaders/SkCoordClampShader.h"
+
 #include "include/core/SkFlattenable.h"
+#include "include/private/base/SkTo.h"
 #include "src/base/SkArenaAlloc.h"
+#include "src/core/SkEffectPriv.h"
 #include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpContexts.h"
+#include "src/core/SkRasterPipelineOpList.h"
 #include "src/core/SkReadBuffer.h"
-#include "src/core/SkRuntimeEffectPriv.h"
-#include "src/core/SkVM.h"
 #include "src/core/SkWriteBuffer.h"
 #include "src/shaders/SkShaderBase.h"
 
-#if defined(SK_GANESH)
-#include "include/effects/SkRuntimeEffect.h"
-#include "include/gpu/GrRecordingContext.h"
-#include "src/gpu/ganesh/GrFPArgs.h"
-#include "src/gpu/ganesh/GrFragmentProcessor.h"
-#include "src/gpu/ganesh/effects/GrSkSLFP.h"
-#endif
-
 #if defined(SK_GRAPHITE)
 #include "src/gpu/graphite/KeyHelpers.h"
 #include "src/gpu/graphite/PaintParamsKey.h"
 #endif // SK_GRAPHITE
 
-class SkShader_CoordClamp final : public SkShaderBase {
-public:
-    SkShader_CoordClamp(sk_sp<SkShader> shader, const SkRect& subset)
-            : fShader(std::move(shader)), fSubset(subset) {}
-
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
-#if defined(SK_GRAPHITE)
-    void addToKey(const skgpu::graphite::KeyContext&,
-                  skgpu::graphite::PaintParamsKeyBuilder*,
-                  skgpu::graphite::PipelineDataGatherer*) const override;
-#endif
-
-protected:
-    SkShader_CoordClamp(SkReadBuffer&);
-    void flatten(SkWriteBuffer&) const override;
-    bool appendStages(const SkStageRec&, const MatrixRec&) const override;
 #if defined(SK_ENABLE_SKVM)
-    skvm::Color program(skvm::Builder*,
-                        skvm::Coord device,
-                        skvm::Coord local,
-                        skvm::Color paint,
-                        const MatrixRec&,
-                        const SkColorInfo& dst,
-                        skvm::Uniforms*,
-                        SkArenaAlloc*) const override;
+#include "src/core/SkRuntimeEffectPriv.h"
+#include "src/core/SkVM.h"
 #endif
 
-private:
-    friend void ::SkRegisterCoordClampShaderFlattenable();
-    SK_FLATTENABLE_HOOKS(SkShader_CoordClamp)
+#include <optional>
 
-    sk_sp<SkShader> fShader;
-    SkRect fSubset;
-};
-
-sk_sp<SkFlattenable> SkShader_CoordClamp::CreateProc(SkReadBuffer& buffer) {
+sk_sp<SkFlattenable> SkCoordClampShader::CreateProc(SkReadBuffer& buffer) {
     sk_sp<SkShader> shader(buffer.readShader());
     SkRect subset = buffer.readRect();
     if (!buffer.validate(SkToBool(shader))) {
@@ -74,13 +39,14 @@
     return SkShaders::CoordClamp(std::move(shader), subset);
 }
 
-void SkShader_CoordClamp::flatten(SkWriteBuffer& buffer) const {
+void SkCoordClampShader::flatten(SkWriteBuffer& buffer) const {
     buffer.writeFlattenable(fShader.get());
     buffer.writeRect(fSubset);
 }
 
-bool SkShader_CoordClamp::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
-    std::optional<MatrixRec> childMRec = mRec.apply(rec);
+bool SkCoordClampShader::appendStages(const SkStageRec& rec,
+                                      const SkShaders::MatrixRec& mRec) const {
+    std::optional<SkShaders::MatrixRec> childMRec = mRec.apply(rec);
     if (!childMRec.has_value()) {
         return false;
     }
@@ -94,15 +60,15 @@
 }
 
 #if defined(SK_ENABLE_SKVM)
-skvm::Color SkShader_CoordClamp::program(skvm::Builder* p,
-                                         skvm::Coord device,
-                                         skvm::Coord local,
-                                         skvm::Color paint,
-                                         const MatrixRec& mRec,
-                                         const SkColorInfo& cinfo,
-                                         skvm::Uniforms* uniforms,
-                                         SkArenaAlloc* alloc) const {
-    std::optional<MatrixRec> childMRec = mRec.apply(p, &local, uniforms);
+skvm::Color SkCoordClampShader::program(skvm::Builder* p,
+                                        skvm::Coord device,
+                                        skvm::Coord local,
+                                        skvm::Color paint,
+                                        const SkShaders::MatrixRec& mRec,
+                                        const SkColorInfo& cinfo,
+                                        skvm::Uniforms* uniforms,
+                                        SkArenaAlloc* alloc) const {
+    std::optional<SkShaders::MatrixRec> childMRec = mRec.apply(p, &local, uniforms);
     if (!childMRec.has_value()) {
         return {};
     }
@@ -120,44 +86,10 @@
 }
 #endif
 
-#if defined(SK_GANESH)
-std::unique_ptr<GrFragmentProcessor> SkShader_CoordClamp::asFragmentProcessor(
-        const GrFPArgs& args, const MatrixRec& mRec) const {
-    static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
-            "uniform shader c;"
-            "uniform float4 s;"
-            "half4 main(float2 p) {"
-                "return c.eval(clamp(p, s.LT, s.RB));"
-            "}");
-
-    auto fp = as_SB(fShader)->asFragmentProcessor(args, mRec.applied());
-    if (!fp) {
-        return nullptr;
-    }
-
-    GrSkSLFP::OptFlags flags = GrSkSLFP::OptFlags::kNone;
-    if (fp->compatibleWithCoverageAsAlpha()) {
-        flags |= GrSkSLFP::OptFlags::kCompatibleWithCoverageAsAlpha;
-    }
-    if (fp->preservesOpaqueInput()) {
-        flags |= GrSkSLFP::OptFlags::kPreservesOpaqueInput;
-    }
-    fp = GrSkSLFP::Make(effect,
-                        "clamp_fp",
-                        /*inputFP=*/nullptr,
-                        flags,
-                        "c", std::move(fp),
-                        "s", fSubset);
-    bool success;
-    std::tie(success, fp) = mRec.apply(std::move(fp));
-    return success ? std::move(fp) : nullptr;
-}
-#endif  // defined(SK_GANESH)
-
 #if defined(SK_GRAPHITE)
-void SkShader_CoordClamp::addToKey(const skgpu::graphite::KeyContext& keyContext,
-                                   skgpu::graphite::PaintParamsKeyBuilder* builder,
-                                   skgpu::graphite::PipelineDataGatherer* gatherer) const {
+void SkCoordClampShader::addToKey(const skgpu::graphite::KeyContext& keyContext,
+                                  skgpu::graphite::PaintParamsKeyBuilder* builder,
+                                  skgpu::graphite::PipelineDataGatherer* gatherer) const {
     using namespace skgpu::graphite;
 
     CoordClampShaderBlock::CoordClampData data(fSubset);
@@ -168,7 +100,12 @@
 }
 #endif // SK_GRAPHITE
 
-void SkRegisterCoordClampShaderFlattenable() { SK_REGISTER_FLATTENABLE(SkShader_CoordClamp); }
+void SkRegisterCoordClampShaderFlattenable() {
+    SK_REGISTER_FLATTENABLE(SkCoordClampShader);
+
+    // Previous name
+    SkFlattenable::Register("SkShader_CoordClamp", SkCoordClampShader::CreateProc);
+}
 
 sk_sp<SkShader> SkShaders::CoordClamp(sk_sp<SkShader> shader, const SkRect& subset) {
     if (!shader) {
@@ -177,5 +114,5 @@
     if (!subset.isSorted()) {
         return nullptr;
     }
-    return sk_make_sp<SkShader_CoordClamp>(std::move(shader), subset);
+    return sk_make_sp<SkCoordClampShader>(std::move(shader), subset);
 }
diff --git a/src/shaders/SkCoordClampShader.h b/src/shaders/SkCoordClampShader.h
new file mode 100644
index 0000000..b44e7af
--- /dev/null
+++ b/src/shaders/SkCoordClampShader.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkCoordClampShader_DEFINED
+#define SkCoordClampShader_DEFINED
+
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkRect.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkShader.h"
+#include "src/shaders/SkShaderBase.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif  // SK_GRAPHITE
+
+#include <utility>
+
+class SkReadBuffer;
+class SkWriteBuffer;
+struct SkStageRec;
+
+class SkCoordClampShader final : public SkShaderBase {
+public:
+    SkCoordClampShader(sk_sp<SkShader> shader, const SkRect& subset)
+            : fShader(std::move(shader)), fSubset(subset) {}
+
+    ShaderType type() const override { return ShaderType::kCoordClamp; }
+
+#if defined(SK_GRAPHITE)
+    void addToKey(const skgpu::graphite::KeyContext&,
+                  skgpu::graphite::PaintParamsKeyBuilder*,
+                  skgpu::graphite::PipelineDataGatherer*) const override;
+#endif
+
+    sk_sp<SkShader> shader() const { return fShader; }
+    SkRect subset() const { return fSubset; }
+
+protected:
+    SkCoordClampShader(SkReadBuffer&);
+    void flatten(SkWriteBuffer&) const override;
+    bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const override;
+#if defined(SK_ENABLE_SKVM)
+    skvm::Color program(skvm::Builder*,
+                        skvm::Coord device,
+                        skvm::Coord local,
+                        skvm::Color paint,
+                        const SkShaders::MatrixRec&,
+                        const SkColorInfo& dst,
+                        skvm::Uniforms*,
+                        SkArenaAlloc*) const override;
+#endif
+
+private:
+    friend void ::SkRegisterCoordClampShaderFlattenable();
+    SK_FLATTENABLE_HOOKS(SkCoordClampShader)
+
+    sk_sp<SkShader> fShader;
+    SkRect fSubset;
+};
+
+#endif
diff --git a/src/shaders/SkEmptyShader.cpp b/src/shaders/SkEmptyShader.cpp
index 9c836b8..7853d3c 100644
--- a/src/shaders/SkEmptyShader.cpp
+++ b/src/shaders/SkEmptyShader.cpp
@@ -5,52 +5,20 @@
  * found in the LICENSE file.
  */
 
-#include "src/shaders/SkShaderBase.h"
+#include "src/shaders/SkEmptyShader.h"
 
 #include "include/core/SkFlattenable.h"
-#include "src/core/SkVM.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkShader.h"
 
-/**
- *  \class SkEmptyShader
- *  A Shader that always draws nothing. Its createContext always returns nullptr.
- */
-class SkEmptyShader : public SkShaderBase {
-public:
-    SkEmptyShader() {}
-
-protected:
-    void flatten(SkWriteBuffer& buffer) const override {
-        // Do nothing.
-        // We just don't want to fall through to SkShader::flatten(),
-        // which will write data we don't care to serialize or decode.
-    }
-
-    bool appendStages(const SkStageRec&, const MatrixRec&) const override { return false; }
-
-#if defined(SK_ENABLE_SKVM)
-    skvm::Color program(skvm::Builder*,
-                        skvm::Coord,
-                        skvm::Coord,
-                        skvm::Color,
-                        const MatrixRec&,
-                        const SkColorInfo&,
-                        skvm::Uniforms*,
-                        SkArenaAlloc*) const override;
-#endif
-
-private:
-    friend void ::SkRegisterEmptyShaderFlattenable();
-    SK_FLATTENABLE_HOOKS(SkEmptyShader)
-
-    using INHERITED = SkShaderBase;
-};
+class SkReadBuffer;
 
 #if defined(SK_ENABLE_SKVM)
 skvm::Color SkEmptyShader::program(skvm::Builder*,
                                    skvm::Coord,
                                    skvm::Coord,
                                    skvm::Color,
-                                   const MatrixRec&,
+                                   const SkShaders::MatrixRec&,
                                    const SkColorInfo&,
                                    skvm::Uniforms*,
                                    SkArenaAlloc*) const {
diff --git a/src/shaders/SkEmptyShader.h b/src/shaders/SkEmptyShader.h
new file mode 100644
index 0000000..ae36c62
--- /dev/null
+++ b/src/shaders/SkEmptyShader.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/shaders/SkShaderBase.h"
+
+#include "include/core/SkFlattenable.h"
+
+#if defined(SK_ENABLE_SKVM)
+#include "src/core/SkVM.h"
+#endif
+
+class SkReadBuffer;
+class SkWriteBuffer;
+struct SkStageRec;
+
+/**
+ *  \class SkEmptyShader
+ *  A Shader that always draws nothing. Its createContext always returns nullptr.
+ */
+class SkEmptyShader : public SkShaderBase {
+public:
+    SkEmptyShader() {}
+
+protected:
+    void flatten(SkWriteBuffer& buffer) const override {
+        // Do nothing.
+        // We just don't want to fall through to SkShader::flatten(),
+        // which will write data we don't care to serialize or decode.
+    }
+
+    bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const override {
+        return false;
+    }
+
+    ShaderType type() const override { return ShaderType::kEmpty; }
+
+#if defined(SK_ENABLE_SKVM)
+    skvm::Color program(skvm::Builder*,
+                        skvm::Coord,
+                        skvm::Coord,
+                        skvm::Color,
+                        const SkShaders::MatrixRec&,
+                        const SkColorInfo&,
+                        skvm::Uniforms*,
+                        SkArenaAlloc*) const override;
+#endif
+
+private:
+    friend void ::SkRegisterEmptyShaderFlattenable();
+    SK_FLATTENABLE_HOOKS(SkEmptyShader)
+};
diff --git a/src/shaders/SkGainmapShader.cpp b/src/shaders/SkGainmapShader.cpp
index 16ccd58..e2cf426 100644
--- a/src/shaders/SkGainmapShader.cpp
+++ b/src/shaders/SkGainmapShader.cpp
@@ -7,14 +7,22 @@
 
 #include "include/private/SkGainmapShader.h"
 
+#include "include/core/SkColor.h"
+#include "include/core/SkColorFilter.h"
 #include "include/core/SkColorSpace.h"
 #include "include/core/SkImage.h"
+#include "include/core/SkMatrix.h"
 #include "include/core/SkShader.h"
+#include "include/core/SkString.h"
 #include "include/effects/SkRuntimeEffect.h"
 #include "include/private/SkGainmapInfo.h"
+#include "include/private/base/SkAssert.h"
+#include "include/private/base/SkFloatingPoint.h"
 #include "src/core/SkColorFilterPriv.h"
 #include "src/core/SkImageInfoPriv.h"
 
+#include <cstdint>
+
 #ifdef SK_ENABLE_SKSL
 static constexpr char gGainmapSKSL[] =
         "uniform shader base;"
diff --git a/src/shaders/SkImageShader.cpp b/src/shaders/SkImageShader.cpp
index bcc13d0..749e33f 100644
--- a/src/shaders/SkImageShader.cpp
+++ b/src/shaders/SkImageShader.cpp
@@ -7,25 +7,36 @@
 
 #include "src/shaders/SkImageShader.h"
 
+#include "include/core/SkAlphaType.h"
+#include "include/core/SkBitmap.h"
+#include "include/core/SkBlendMode.h"
+#include "include/core/SkColorType.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkPaint.h"
+#include "include/core/SkPixmap.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkShader.h"
+#include "include/core/SkTileMode.h"
+#include "include/private/base/SkMath.h"
+#include "modules/skcms/skcms.h"
 #include "src/base/SkArenaAlloc.h"
-#include "src/core/SkColorSpacePriv.h"
 #include "src/core/SkColorSpaceXformSteps.h"
+#include "src/core/SkEffectPriv.h"
 #include "src/core/SkImageInfoPriv.h"
-#include "src/core/SkMatrixPriv.h"
-#include "src/core/SkMatrixProvider.h"
+#include "src/core/SkImagePriv.h"
 #include "src/core/SkMipmapAccessor.h"
-#include "src/core/SkOpts.h"
+#include "src/core/SkPicturePriv.h"
 #include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpContexts.h"
+#include "src/core/SkRasterPipelineOpList.h"
 #include "src/core/SkReadBuffer.h"
-#include "src/core/SkVM.h"
+#include "src/core/SkSamplingPriv.h"
 #include "src/core/SkWriteBuffer.h"
-#include "src/core/SkYUVMath.h"
 #include "src/image/SkImage_Base.h"
-#include "src/shaders/SkBitmapProcShader.h"
 #include "src/shaders/SkLocalMatrixShader.h"
-#include "src/shaders/SkTransformShader.h"
 
 #if defined(SK_GRAPHITE)
+#include "src/core/SkYUVMath.h"
 #include "src/gpu/Blend.h"
 #include "src/gpu/graphite/ImageUtils.h"
 #include "src/gpu/graphite/Image_Graphite.h"
@@ -57,6 +68,20 @@
 }
 #endif
 
+#if defined(SK_ENABLE_SKVM)
+#include "src/core/SkVM.h"
+#endif
+
+#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
+#include "src/shaders/SkBitmapProcShader.h"
+#endif
+
+#include <optional>
+#include <tuple>
+#include <utility>
+
+class SkColorSpace;
+
 SkM44 SkImageShader::CubicResamplerMatrix(float B, float C) {
 #if 0
     constexpr SkM44 kMitchell = SkM44( 1.f/18.f, -9.f/18.f,  15.f/18.f,  -7.f/18.f,
@@ -98,12 +123,11 @@
 #endif
 }
 
-// TODO: currently this only *always* used in asFragmentProcessor(), which is excluded on no-gpu
-// builds. No-gpu builds only use needs_subset() in asserts, so release+no-gpu doesn't use it, which
-// can cause builds to fail if unused warnings are treated as errors.
-[[maybe_unused]] static bool needs_subset(SkImage* img, const SkRect& subset) {
+#if defined(SK_DEBUG)
+static bool needs_subset(SkImage* img, const SkRect& subset) {
     return subset != SkRect::Make(img->dimensions());
 }
+#endif
 
 SkImageShader::SkImageShader(sk_sp<SkImage> img,
                              const SkRect& subset,
@@ -367,48 +391,6 @@
                                                            clampAsIfUnpremul);
 }
 
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-#if defined(SK_GANESH)
-
-#include "src/gpu/ganesh/GrColorInfo.h"
-#include "src/gpu/ganesh/GrFPArgs.h"
-#include "src/gpu/ganesh/effects/GrBlendFragmentProcessor.h"
-#include "src/gpu/ganesh/image/GrImageUtils.h"
-
-std::unique_ptr<GrFragmentProcessor>
-SkImageShader::asFragmentProcessor(const GrFPArgs& args, const MatrixRec& mRec) const {
-    SkTileMode tileModes[2] = {fTileModeX, fTileModeY};
-    const SkRect* subset = needs_subset(fImage.get(), fSubset) ? &fSubset : nullptr;
-    auto fp = skgpu::ganesh::AsFragmentProcessor(
-            args.fContext, fImage, fSampling, tileModes, SkMatrix::I(), subset);
-    if (!fp) {
-        return nullptr;
-    }
-
-    bool success;
-    std::tie(success, fp) = mRec.apply(std::move(fp));
-    if (!success) {
-        return nullptr;
-    }
-
-    if (!fRaw) {
-        fp = GrColorSpaceXformEffect::Make(std::move(fp),
-                                           fImage->colorSpace(),
-                                           fImage->alphaType(),
-                                           args.fDstColorInfo->colorSpace(),
-                                           kPremul_SkAlphaType);
-
-        if (fImage->isAlphaOnly()) {
-            fp = GrBlendFragmentProcessor::Make<SkBlendMode::kDstIn>(std::move(fp), nullptr);
-        }
-    }
-
-    return fp;
-}
-
-#endif
-
 #if defined(SK_GRAPHITE)
 
 void SkImageShader::addToKey(const skgpu::graphite::KeyContext& keyContext,
@@ -554,7 +536,6 @@
 #endif
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
-#include "src/core/SkImagePriv.h"
 
 sk_sp<SkShader> SkMakeBitmapShaderForPaint(const SkPaint& paint, const SkBitmap& src,
                                            SkTileMode tmx, SkTileMode tmy,
@@ -658,7 +639,7 @@
     return SkSamplingOptions(filter, sampling.mipmap);
 }
 
-bool SkImageShader::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
+bool SkImageShader::appendStages(const SkStageRec& rec, const SkShaders::MatrixRec& mRec) const {
     SkASSERT(!needs_subset(fImage.get(), fSubset));  // TODO(skbug.com/12784)
 
     // We only support certain sampling options in stages so far
@@ -943,7 +924,7 @@
                                    skvm::Coord device,
                                    skvm::Coord origLocal,
                                    skvm::Color paint,
-                                   const MatrixRec& mRec,
+                                   const SkShaders::MatrixRec& mRec,
                                    const SkColorInfo& dst,
                                    skvm::Uniforms* uniforms,
                                    SkArenaAlloc* alloc) const {
diff --git a/src/shaders/SkImageShader.h b/src/shaders/SkImageShader.h
index ca60198..8334192 100644
--- a/src/shaders/SkImageShader.h
+++ b/src/shaders/SkImageShader.h
@@ -8,19 +8,29 @@
 #ifndef SkImageShader_DEFINED
 #define SkImageShader_DEFINED
 
+#include "include/core/SkFlattenable.h"
 #include "include/core/SkImage.h"
 #include "include/core/SkM44.h"
-#include "src/shaders/SkBitmapProcShader.h"
+#include "include/core/SkRect.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkSamplingOptions.h"
+#include "include/core/SkTypes.h"
 #include "src/shaders/SkShaderBase.h"
 
-namespace skgpu {
-class Swizzle;
-}
-
+#if defined(SK_GRAPHITE)
 namespace skgpu::graphite {
 class KeyContext;
 enum class ReadSwizzle;
 }
+#endif
+
+class SkArenaAlloc;
+class SkMatrix;
+class SkReadBuffer;
+class SkShader;
+class SkWriteBuffer;
+enum class SkTileMode;
+struct SkStageRec;
 
 class SkImageShader : public SkShaderBase {
 public:
@@ -56,10 +66,8 @@
 
     bool isOpaque() const override;
 
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
+    ShaderType type() const override { return ShaderType::kImage; }
+
 #if defined(SK_GRAPHITE)
     void addToKey(const skgpu::graphite::KeyContext&,
                   skgpu::graphite::PaintParamsKeyBuilder*,
@@ -67,6 +75,13 @@
 #endif
     static SkM44 CubicResamplerMatrix(float B, float C);
 
+    SkTileMode tileModeX() const { return fTileModeX; }
+    SkTileMode tileModeY() const { return fTileModeY; }
+    sk_sp<SkImage> image() const { return fImage; }
+    SkSamplingOptions sampling() const { return fSampling; }
+    SkRect subset() const { return fSubset; }
+    bool isRaw() const { return fRaw; }
+
 private:
     SK_FLATTENABLE_HOOKS(SkImageShader)
 
@@ -76,14 +91,14 @@
 #endif
     SkImage* onIsAImage(SkMatrix*, SkTileMode*) const override;
 
-    bool appendStages(const SkStageRec&, const MatrixRec&) const override;
+    bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const override;
 
 #if defined(SK_ENABLE_SKVM)
     skvm::Color program(skvm::Builder*,
                         skvm::Coord device,
                         skvm::Coord local,
                         skvm::Color paint,
-                        const MatrixRec&,
+                        const SkShaders::MatrixRec&,
                         const SkColorInfo& dst,
                         skvm::Uniforms* uniforms,
                         SkArenaAlloc*) const override;
diff --git a/src/shaders/SkLocalMatrixShader.cpp b/src/shaders/SkLocalMatrixShader.cpp
index 7744bff..f19078b 100644
--- a/src/shaders/SkLocalMatrixShader.cpp
+++ b/src/shaders/SkLocalMatrixShader.cpp
@@ -4,17 +4,11 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
-
-#include "src/base/SkTLazy.h"
-#include "src/core/SkMatrixProvider.h"
-#include "src/core/SkVM.h"
 #include "src/shaders/SkLocalMatrixShader.h"
 
-#if defined(SK_GANESH)
-#include "src/gpu/ganesh/GrFPArgs.h"
-#include "src/gpu/ganesh/GrFragmentProcessor.h"
-#include "src/gpu/ganesh/effects/GrMatrixEffect.h"
-#endif
+#include "src/base/SkTLazy.h"
+#include "src/core/SkReadBuffer.h"
+#include "src/core/SkWriteBuffer.h"
 
 #if defined(SK_GRAPHITE)
 #include "src/gpu/graphite/KeyContext.h"
@@ -22,6 +16,10 @@
 #include "src/gpu/graphite/PaintParamsKey.h"
 #endif
 
+class SkImage;
+enum class SkTileMode;
+struct SkStageRec;
+
 SkShaderBase::GradientType SkLocalMatrixShader::asGradient(GradientInfo* info,
                                                            SkMatrix* localMatrix) const {
     GradientType type = as_SB(fWrappedShader)->asGradient(info, localMatrix);
@@ -31,13 +29,6 @@
     return type;
 }
 
-#if defined(SK_GANESH)
-std::unique_ptr<GrFragmentProcessor> SkLocalMatrixShader::asFragmentProcessor(
-        const GrFPArgs& args, const MatrixRec& mRec) const {
-    return as_SB(fWrappedShader)->asFragmentProcessor(args, mRec.concat(fLocalMatrix));
-}
-#endif
-
 #if defined(SK_GRAPHITE)
 void SkLocalMatrixShader::addToKey(const skgpu::graphite::KeyContext& keyContext,
                                    skgpu::graphite::PaintParamsKeyBuilder* builder,
@@ -97,7 +88,8 @@
     return image;
 }
 
-bool SkLocalMatrixShader::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
+bool SkLocalMatrixShader::appendStages(const SkStageRec& rec,
+                                       const SkShaders::MatrixRec& mRec) const {
     return as_SB(fWrappedShader)->appendStages(rec, mRec.concat(fLocalMatrix));
 }
 
@@ -106,7 +98,7 @@
                                          skvm::Coord device,
                                          skvm::Coord local,
                                          skvm::Color paint,
-                                         const MatrixRec& mRec,
+                                         const SkShaders::MatrixRec& mRec,
                                          const SkColorInfo& dst,
                                          skvm::Uniforms* uniforms,
                                          SkArenaAlloc* alloc) const {
@@ -121,97 +113,30 @@
 }
 #endif
 
-sk_sp<SkShader> SkShader::makeWithLocalMatrix(const SkMatrix& localMatrix) const {
-    if (localMatrix.isIdentity()) {
-        return sk_ref_sp(const_cast<SkShader*>(this));
-    }
-
-    const SkMatrix* lm = &localMatrix;
-
-    sk_sp<SkShader> baseShader;
-    SkMatrix otherLocalMatrix;
-    sk_sp<SkShader> proxy = as_SB(this)->makeAsALocalMatrixShader(&otherLocalMatrix);
-    if (proxy) {
-        otherLocalMatrix = SkShaderBase::ConcatLocalMatrices(localMatrix, otherLocalMatrix);
-        lm = &otherLocalMatrix;
-        baseShader = proxy;
-    } else {
-        baseShader = sk_ref_sp(const_cast<SkShader*>(this));
-    }
-
-    return sk_make_sp<SkLocalMatrixShader>(std::move(baseShader), *lm);
-}
-
 ////////////////////////////////////////////////////////////////////
 
-/**
- *  Replaces the CTM when used. Created to support clipShaders, which have to be evaluated
- *  using the CTM that was present at the time they were specified (which may be different
- *  from the CTM at the time something is drawn through the clip.
- */
-class SkCTMShader final : public SkShaderBase {
-public:
-    SkCTMShader(sk_sp<SkShader> proxy, const SkMatrix& ctm)
-    : fProxyShader(std::move(proxy))
-    , fCTM(ctm)
-    {}
+SkCTMShader::SkCTMShader(sk_sp<SkShader> proxy, const SkMatrix& ctm)
+        : fProxyShader(std::move(proxy)), fCTM(ctm) {}
 
-    GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override {
-        return as_SB(fProxyShader)->asGradient(info, localMatrix);
-    }
+SkShaderBase::GradientType SkCTMShader::asGradient(GradientInfo* info,
+                                                   SkMatrix* localMatrix) const {
+    return as_SB(fProxyShader)->asGradient(info, localMatrix);
+}
 
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
-
-protected:
-    void flatten(SkWriteBuffer&) const override { SkASSERT(false); }
-
-    bool appendStages(const SkStageRec& rec, const MatrixRec&) const override {
-        return as_SB(fProxyShader)->appendRootStages(rec, fCTM);
-    }
+bool SkCTMShader::appendStages(const SkStageRec& rec, const SkShaders::MatrixRec&) const {
+    return as_SB(fProxyShader)->appendRootStages(rec, fCTM);
+}
 
 #if defined(SK_ENABLE_SKVM)
-    skvm::Color program(skvm::Builder* p,
-                        skvm::Coord device,
-                        skvm::Coord local,
-                        skvm::Color paint,
-                        const MatrixRec& mRec,
-                        const SkColorInfo& dst,
-                        skvm::Uniforms* uniforms,
-                        SkArenaAlloc* alloc) const override {
-        return as_SB(fProxyShader)->rootProgram(p, device, paint, fCTM, dst, uniforms, alloc);
-    }
-#endif
-
-private:
-    SK_FLATTENABLE_HOOKS(SkCTMShader)
-
-    sk_sp<SkShader> fProxyShader;
-    SkMatrix        fCTM;
-
-    using INHERITED = SkShaderBase;
-};
-
-
-#if defined(SK_GANESH)
-std::unique_ptr<GrFragmentProcessor> SkCTMShader::asFragmentProcessor(const GrFPArgs& args,
-                                                                      const MatrixRec& mRec) const {
-    SkMatrix ctmInv;
-    if (!fCTM.invert(&ctmInv)) {
-        return nullptr;
-    }
-
-    auto base = as_SB(fProxyShader)->asRootFragmentProcessor(args, fCTM);
-    if (!base) {
-        return nullptr;
-    }
-
-    // In order for the shader to be evaluated with the original CTM, we explicitly evaluate it
-    // at sk_FragCoord, and pass that through the inverse of the original CTM. This avoids requiring
-    // local coords for the shader and mapping from the draw's local to device and then back.
-    return GrFragmentProcessor::DeviceSpace(GrMatrixEffect::Make(ctmInv, std::move(base)));
+skvm::Color SkCTMShader::program(skvm::Builder* p,
+                                 skvm::Coord device,
+                                 skvm::Coord local,
+                                 skvm::Color paint,
+                                 const SkShaders::MatrixRec& mRec,
+                                 const SkColorInfo& dst,
+                                 skvm::Uniforms* uniforms,
+                                 SkArenaAlloc* alloc) const {
+    return as_SB(fProxyShader)->rootProgram(p, device, paint, fCTM, dst, uniforms, alloc);
 }
 #endif
 
@@ -219,7 +144,3 @@
     SkASSERT(false);
     return nullptr;
 }
-
-sk_sp<SkShader> SkShaderBase::makeWithCTM(const SkMatrix& postM) const {
-    return sk_sp<SkShader>(new SkCTMShader(sk_ref_sp(this), postM));
-}
diff --git a/src/shaders/SkLocalMatrixShader.h b/src/shaders/SkLocalMatrixShader.h
index 12b62be..ce007c1 100644
--- a/src/shaders/SkLocalMatrixShader.h
+++ b/src/shaders/SkLocalMatrixShader.h
@@ -8,12 +8,22 @@
 #ifndef SkLocalMatrixShader_DEFINED
 #define SkLocalMatrixShader_DEFINED
 
-#include "src/core/SkReadBuffer.h"
-#include "src/core/SkWriteBuffer.h"
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkShader.h"
+#include "include/core/SkTypes.h"
 #include "src/shaders/SkShaderBase.h"
 
-class GrFragmentProcessor;
+#include <type_traits>
+#include <utility>
+
 class SkArenaAlloc;
+class SkImage;
+class SkReadBuffer;
+class SkWriteBuffer;
+enum class SkTileMode;
+struct SkStageRec;
 
 class SkLocalMatrixShader final : public SkShaderBase {
 public:
@@ -31,11 +41,8 @@
             : fLocalMatrix(localMatrix), fWrappedShader(std::move(wrapped)) {}
 
     GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override;
+    ShaderType type() const override { return ShaderType::kLocalMatrix; }
 
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
 #if defined(SK_GRAPHITE)
     void addToKey(const skgpu::graphite::KeyContext&,
                   skgpu::graphite::PaintParamsKeyBuilder*,
@@ -49,6 +56,9 @@
         return fWrappedShader;
     }
 
+    const SkMatrix& localMatrix() const { return fLocalMatrix; }
+    sk_sp<SkShader> wrappedShader() const { return fWrappedShader; }
+
 protected:
     void flatten(SkWriteBuffer&) const override;
 
@@ -58,14 +68,14 @@
 
     SkImage* onIsAImage(SkMatrix* matrix, SkTileMode* mode) const override;
 
-    bool appendStages(const SkStageRec&, const MatrixRec&) const override;
+    bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const override;
 
 #if defined(SK_ENABLE_SKVM)
     skvm::Color program(skvm::Builder*,
                         skvm::Coord device,
                         skvm::Coord local,
                         skvm::Color paint,
-                        const MatrixRec&,
+                        const SkShaders::MatrixRec&,
                         const SkColorInfo& dst,
                         skvm::Uniforms* uniforms,
                         SkArenaAlloc*) const override;
@@ -76,8 +86,45 @@
 
     SkMatrix fLocalMatrix;
     sk_sp<SkShader> fWrappedShader;
+};
 
-    using INHERITED = SkShaderBase;
+/**
+ *  Replaces the CTM when used. Created to support clipShaders, which have to be evaluated
+ *  using the CTM that was present at the time they were specified (which may be different
+ *  from the CTM at the time something is drawn through the clip.
+ */
+class SkCTMShader final : public SkShaderBase {
+public:
+    SkCTMShader(sk_sp<SkShader> proxy, const SkMatrix& ctm);
+
+    GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override;
+
+    ShaderType type() const override { return ShaderType::kCTM; }
+
+    const SkMatrix& ctm() const { return fCTM; }
+    sk_sp<SkShader> proxyShader() const { return fProxyShader; }
+
+protected:
+    void flatten(SkWriteBuffer&) const override { SkASSERT(false); }
+
+    bool appendStages(const SkStageRec& rec, const SkShaders::MatrixRec&) const override;
+
+#if defined(SK_ENABLE_SKVM)
+    skvm::Color program(skvm::Builder* p,
+                        skvm::Coord device,
+                        skvm::Coord local,
+                        skvm::Color paint,
+                        const SkShaders::MatrixRec& mRec,
+                        const SkColorInfo& dst,
+                        skvm::Uniforms* uniforms,
+                        SkArenaAlloc* alloc) const override;
+#endif
+
+private:
+    SK_FLATTENABLE_HOOKS(SkCTMShader)
+
+    sk_sp<SkShader> fProxyShader;
+    SkMatrix fCTM;
 };
 
 #endif
diff --git a/src/shaders/SkPerlinNoiseShader.cpp b/src/shaders/SkPerlinNoiseShader.cpp
deleted file mode 100644
index 0052c17..0000000
--- a/src/shaders/SkPerlinNoiseShader.cpp
+++ /dev/null
@@ -1,1141 +0,0 @@
-/*
- * Copyright 2013 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "include/effects/SkPerlinNoiseShader.h"
-
-#include "include/core/SkBitmap.h"
-#include "include/core/SkColorFilter.h"
-#include "include/core/SkShader.h"
-#include "include/core/SkString.h"
-#include "include/core/SkUnPreMultiply.h"
-#include "include/private/base/SkTPin.h"
-#include "src/base/SkArenaAlloc.h"
-#include "src/core/SkMatrixProvider.h"
-#include "src/core/SkReadBuffer.h"
-#include "src/core/SkVM.h"
-#include "src/core/SkWriteBuffer.h"
-
-#if defined(SK_GANESH)
-#include "include/gpu/GrRecordingContext.h"
-#include "src/gpu/KeyBuilder.h"
-#include "src/gpu/ganesh/GrFPArgs.h"
-#include "src/gpu/ganesh/GrFragmentProcessor.h"
-#include "src/gpu/ganesh/GrProcessorUnitTest.h"
-#include "src/gpu/ganesh/GrRecordingContextPriv.h"
-#include "src/gpu/ganesh/GrShaderCaps.h"
-#include "src/gpu/ganesh/SkGr.h"
-#include "src/gpu/ganesh/effects/GrMatrixEffect.h"
-#include "src/gpu/ganesh/effects/GrTextureEffect.h"
-#include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h"
-#include "src/gpu/ganesh/glsl/GrGLSLProgramDataManager.h"
-#include "src/gpu/ganesh/glsl/GrGLSLUniformHandler.h"
-#endif // SK_GANESH
-
-#if defined(SK_GRAPHITE)
-#include "src/gpu/graphite/KeyContext.h"
-#include "src/gpu/graphite/KeyHelpers.h"
-#include "src/gpu/graphite/Log.h"
-#include "src/gpu/graphite/PaintParamsKey.h"
-#include "src/gpu/graphite/RecorderPriv.h"
-#include "src/gpu/graphite/TextureProxyView.h"
-#include "src/image/SkImage_Base.h"
-#endif // SK_GRAPHITE
-
-static const int kBlockSize = 256;
-static const int kBlockMask = kBlockSize - 1;
-static const int kPerlinNoise = 4096;
-static const int kRandMaximum = SK_MaxS32; // 2**31 - 1
-
-class SkPerlinNoiseShaderImpl : public SkShaderBase {
-public:
-    struct StitchData {
-        StitchData()
-          : fWidth(0)
-          , fWrapX(0)
-          , fHeight(0)
-          , fWrapY(0)
-        {}
-
-        StitchData(SkScalar w, SkScalar h)
-          : fWidth(std::min(SkScalarRoundToInt(w), SK_MaxS32 - kPerlinNoise))
-          , fWrapX(kPerlinNoise + fWidth)
-          , fHeight(std::min(SkScalarRoundToInt(h), SK_MaxS32 - kPerlinNoise))
-          , fWrapY(kPerlinNoise + fHeight) {}
-
-        bool operator==(const StitchData& other) const {
-            return fWidth == other.fWidth &&
-                   fWrapX == other.fWrapX &&
-                   fHeight == other.fHeight &&
-                   fWrapY == other.fWrapY;
-        }
-
-        int fWidth; // How much to subtract to wrap for stitching.
-        int fWrapX; // Minimum value to wrap.
-        int fHeight;
-        int fWrapY;
-    };
-
-    struct PaintingData {
-        PaintingData(const SkISize& tileSize, SkScalar seed,
-                     SkScalar baseFrequencyX, SkScalar baseFrequencyY,
-                     const SkMatrix& matrix)
-        {
-            SkVector tileVec;
-            matrix.mapVector(SkIntToScalar(tileSize.fWidth), SkIntToScalar(tileSize.fHeight),
-                             &tileVec);
-
-            SkSize scale;
-            if (!matrix.decomposeScale(&scale, nullptr)) {
-                scale.set(SK_ScalarNearlyZero, SK_ScalarNearlyZero);
-            }
-            fBaseFrequency.set(baseFrequencyX * SkScalarInvert(scale.width()),
-                               baseFrequencyY * SkScalarInvert(scale.height()));
-            fTileSize.set(SkScalarRoundToInt(tileVec.fX), SkScalarRoundToInt(tileVec.fY));
-            this->init(seed);
-            if (!fTileSize.isEmpty()) {
-                this->stitch();
-            }
-
-    #if defined(SK_GANESH) || defined(SK_GRAPHITE)
-            SkImageInfo info = SkImageInfo::MakeA8(kBlockSize, 1);
-            fPermutationsBitmap.installPixels(info, fLatticeSelector, info.minRowBytes());
-            fPermutationsBitmap.setImmutable();
-
-            info = SkImageInfo::Make(kBlockSize, 4, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
-            fNoiseBitmap.installPixels(info, fNoise[0][0], info.minRowBytes());
-            fNoiseBitmap.setImmutable();
-    #endif
-        }
-
-    #if defined(SK_GANESH) || defined(SK_GRAPHITE)
-        PaintingData(const PaintingData& that)
-                : fSeed(that.fSeed)
-                , fTileSize(that.fTileSize)
-                , fBaseFrequency(that.fBaseFrequency)
-                , fStitchDataInit(that.fStitchDataInit)
-                , fPermutationsBitmap(that.fPermutationsBitmap)
-                , fNoiseBitmap(that.fNoiseBitmap) {
-            memcpy(fLatticeSelector, that.fLatticeSelector, sizeof(fLatticeSelector));
-            memcpy(fNoise, that.fNoise, sizeof(fNoise));
-            memcpy(fGradient, that.fGradient, sizeof(fGradient));
-        }
-    #endif
-
-        int         fSeed;
-        uint8_t     fLatticeSelector[kBlockSize];
-        uint16_t    fNoise[4][kBlockSize][2];
-        SkPoint     fGradient[4][kBlockSize];
-        SkISize     fTileSize;
-        SkVector    fBaseFrequency;
-        StitchData  fStitchDataInit;
-
-    private:
-
-    #if defined(SK_GANESH) || defined(SK_GRAPHITE)
-        SkBitmap fPermutationsBitmap;
-        SkBitmap fNoiseBitmap;
-    #endif
-
-        inline int random()  {
-            // See https://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement
-            // m = kRandMaximum, 2**31 - 1 (2147483647)
-            static constexpr int kRandAmplitude = 16807; // 7**5; primitive root of m
-            static constexpr int kRandQ = 127773; // m / a
-            static constexpr int kRandR = 2836; // m % a
-
-            int result = kRandAmplitude * (fSeed % kRandQ) - kRandR * (fSeed / kRandQ);
-            if (result <= 0) {
-                result += kRandMaximum;
-            }
-            fSeed = result;
-            return result;
-        }
-
-        // Only called once. Could be part of the constructor.
-        void init(SkScalar seed) {
-            // According to the SVG spec, we must truncate (not round) the seed value.
-            fSeed = SkScalarTruncToInt(seed);
-            // The seed value clamp to the range [1, kRandMaximum - 1].
-            if (fSeed <= 0) {
-                fSeed = -(fSeed % (kRandMaximum - 1)) + 1;
-            }
-            if (fSeed > kRandMaximum - 1) {
-                fSeed = kRandMaximum - 1;
-            }
-            for (int channel = 0; channel < 4; ++channel) {
-                for (int i = 0; i < kBlockSize; ++i) {
-                    fLatticeSelector[i] = i;
-                    fNoise[channel][i][0] = (random() % (2 * kBlockSize));
-                    fNoise[channel][i][1] = (random() % (2 * kBlockSize));
-                }
-            }
-            for (int i = kBlockSize - 1; i > 0; --i) {
-                int k = fLatticeSelector[i];
-                int j = random() % kBlockSize;
-                SkASSERT(j >= 0);
-                SkASSERT(j < kBlockSize);
-                fLatticeSelector[i] = fLatticeSelector[j];
-                fLatticeSelector[j] = k;
-            }
-
-            // Perform the permutations now
-            {
-                // Copy noise data
-                uint16_t noise[4][kBlockSize][2];
-                for (int i = 0; i < kBlockSize; ++i) {
-                    for (int channel = 0; channel < 4; ++channel) {
-                        for (int j = 0; j < 2; ++j) {
-                            noise[channel][i][j] = fNoise[channel][i][j];
-                        }
-                    }
-                }
-                // Do permutations on noise data
-                for (int i = 0; i < kBlockSize; ++i) {
-                    for (int channel = 0; channel < 4; ++channel) {
-                        for (int j = 0; j < 2; ++j) {
-                            fNoise[channel][i][j] = noise[channel][fLatticeSelector[i]][j];
-                        }
-                    }
-                }
-            }
-
-            // Half of the largest possible value for 16 bit unsigned int
-            static constexpr SkScalar kHalfMax16bits = 32767.5f;
-
-            // Compute gradients from permuted noise data
-            static constexpr SkScalar kInvBlockSizef = 1.0 / SkIntToScalar(kBlockSize);
-            for (int channel = 0; channel < 4; ++channel) {
-                for (int i = 0; i < kBlockSize; ++i) {
-                    fGradient[channel][i] = SkPoint::Make(
-                        (fNoise[channel][i][0] - kBlockSize) * kInvBlockSizef,
-                        (fNoise[channel][i][1] - kBlockSize) * kInvBlockSizef);
-                    fGradient[channel][i].normalize();
-                    // Put the normalized gradient back into the noise data
-                    fNoise[channel][i][0] =
-                            SkScalarRoundToInt((fGradient[channel][i].fX + 1) * kHalfMax16bits);
-                    fNoise[channel][i][1] =
-                            SkScalarRoundToInt((fGradient[channel][i].fY + 1) * kHalfMax16bits);
-                }
-            }
-        }
-
-        // Only called once. Could be part of the constructor.
-        void stitch() {
-            SkScalar tileWidth  = SkIntToScalar(fTileSize.width());
-            SkScalar tileHeight = SkIntToScalar(fTileSize.height());
-            SkASSERT(tileWidth > 0 && tileHeight > 0);
-            // When stitching tiled turbulence, the frequencies must be adjusted
-            // so that the tile borders will be continuous.
-            if (fBaseFrequency.fX) {
-                SkScalar lowFrequencx =
-                    SkScalarFloorToScalar(tileWidth * fBaseFrequency.fX) / tileWidth;
-                SkScalar highFrequencx =
-                    SkScalarCeilToScalar(tileWidth * fBaseFrequency.fX) / tileWidth;
-                // BaseFrequency should be non-negative according to the standard.
-                // lowFrequencx can be 0 if fBaseFrequency.fX is very small.
-                if (sk_ieee_float_divide(fBaseFrequency.fX, lowFrequencx) < highFrequencx / fBaseFrequency.fX) {
-                    fBaseFrequency.fX = lowFrequencx;
-                } else {
-                    fBaseFrequency.fX = highFrequencx;
-                }
-            }
-            if (fBaseFrequency.fY) {
-                SkScalar lowFrequency =
-                    SkScalarFloorToScalar(tileHeight * fBaseFrequency.fY) / tileHeight;
-                SkScalar highFrequency =
-                    SkScalarCeilToScalar(tileHeight * fBaseFrequency.fY) / tileHeight;
-                // lowFrequency can be 0 if fBaseFrequency.fY is very small.
-                if (sk_ieee_float_divide(fBaseFrequency.fY, lowFrequency) < highFrequency / fBaseFrequency.fY) {
-                    fBaseFrequency.fY = lowFrequency;
-                } else {
-                    fBaseFrequency.fY = highFrequency;
-                }
-            }
-            fStitchDataInit = StitchData(tileWidth * fBaseFrequency.fX,
-                                         tileHeight * fBaseFrequency.fY);
-        }
-
-    public:
-
-#if defined(SK_GANESH) || defined(SK_GRAPHITE)
-        const SkBitmap& getPermutationsBitmap() const { return fPermutationsBitmap; }
-
-        const SkBitmap& getNoiseBitmap() const { return fNoiseBitmap; }
-#endif
-    };
-
-    /**
-     *  About the noise types : the difference between the first 2 is just minor tweaks to the
-     *  algorithm, they're not 2 entirely different noises. The output looks different, but once the
-     *  noise is generated in the [1, -1] range, the output is brought back in the [0, 1] range by
-     *  doing :
-     *  kFractalNoise_Type : noise * 0.5 + 0.5
-     *  kTurbulence_Type   : abs(noise)
-     *  Very little differs between the 2 types, although you can tell the difference visually.
-     */
-    enum Type {
-        kFractalNoise_Type,
-        kTurbulence_Type,
-        kLast_Type = kTurbulence_Type
-    };
-
-    static const int kMaxOctaves = 255; // numOctaves must be <= 0 and <= kMaxOctaves
-
-    SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::Type type, SkScalar baseFrequencyX,
-                            SkScalar baseFrequencyY, int numOctaves, SkScalar seed,
-                            const SkISize* tileSize);
-
-    class PerlinNoiseShaderContext : public Context {
-    public:
-        PerlinNoiseShaderContext(const SkPerlinNoiseShaderImpl& shader, const ContextRec&);
-
-        void shadeSpan(int x, int y, SkPMColor[], int count) override;
-
-    private:
-        SkPMColor shade(const SkPoint& point, StitchData& stitchData) const;
-        SkScalar calculateTurbulenceValueForPoint(int channel,
-                                                  StitchData& stitchData,
-                                                  const SkPoint& point) const;
-        SkScalar noise2D(int channel,
-                         const StitchData& stitchData,
-                         const SkPoint& noiseVector) const;
-
-        SkMatrix     fMatrix;
-        PaintingData fPaintingData;
-
-        using INHERITED = Context;
-    };
-
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
-#if defined(SK_GRAPHITE)
-    void addToKey(const skgpu::graphite::KeyContext&,
-                  skgpu::graphite::PaintParamsKeyBuilder*,
-                  skgpu::graphite::PipelineDataGatherer*) const override;
-#endif
-#if defined(SK_ENABLE_SKVM)
-    skvm::Color program(skvm::Builder*,
-                        skvm::Coord,
-                        skvm::Coord,
-                        skvm::Color,
-                        const MatrixRec&,
-                        const SkColorInfo&,
-                        skvm::Uniforms*,
-                        SkArenaAlloc*) const override {
-        // Unimplemented
-        return {};
-    }
-#endif
-
-protected:
-    void flatten(SkWriteBuffer&) const override;
-#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
-    Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
-#endif
-
-private:
-    SK_FLATTENABLE_HOOKS(SkPerlinNoiseShaderImpl)
-
-    const SkPerlinNoiseShaderImpl::Type fType;
-    const SkScalar                  fBaseFrequencyX;
-    const SkScalar                  fBaseFrequencyY;
-    const int                       fNumOctaves;
-    const SkScalar                  fSeed;
-    const SkISize                   fTileSize;
-    const bool                      fStitchTiles;
-
-    friend void SkRegisterPerlinNoiseShaderFlattenable();
-
-    using INHERITED = SkShaderBase;
-};
-
-namespace {
-
-// noiseValue is the color component's value (or color)
-// limitValue is the maximum perlin noise array index value allowed
-// newValue is the current noise dimension (either width or height)
-inline int checkNoise(int noiseValue, int limitValue, int newValue) {
-    // If the noise value would bring us out of bounds of the current noise array while we are
-    // stiching noise tiles together, wrap the noise around the current dimension of the noise to
-    // stay within the array bounds in a continuous fashion (so that tiling lines are not visible)
-    if (noiseValue >= limitValue) {
-        noiseValue -= newValue;
-    }
-    return noiseValue;
-}
-
-inline SkScalar smoothCurve(SkScalar t) {
-    return t * t * (3 - 2 * t);
-}
-
-} // end namespace
-
-SkPerlinNoiseShaderImpl::SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::Type type,
-                                                 SkScalar baseFrequencyX,
-                                                 SkScalar baseFrequencyY,
-                                                 int numOctaves,
-                                                 SkScalar seed,
-                                                 const SkISize* tileSize)
-        : fType(type)
-        , fBaseFrequencyX(baseFrequencyX)
-        , fBaseFrequencyY(baseFrequencyY)
-        , fNumOctaves(numOctaves > kMaxOctaves ? kMaxOctaves : numOctaves) //[0,255] octaves allowed
-        , fSeed(seed)
-        , fTileSize(nullptr == tileSize ? SkISize::Make(0, 0) : *tileSize)
-        , fStitchTiles(!fTileSize.isEmpty()) {
-    SkASSERT(numOctaves >= 0 && numOctaves <= kMaxOctaves);
-    SkASSERT(fBaseFrequencyX >= 0);
-    SkASSERT(fBaseFrequencyY >= 0);
-}
-
-sk_sp<SkFlattenable> SkPerlinNoiseShaderImpl::CreateProc(SkReadBuffer& buffer) {
-    Type type = buffer.read32LE(kLast_Type);
-
-    SkScalar freqX = buffer.readScalar();
-    SkScalar freqY = buffer.readScalar();
-    int octaves = buffer.read32LE<int>(kMaxOctaves);
-
-    SkScalar seed = buffer.readScalar();
-    SkISize tileSize;
-    tileSize.fWidth = buffer.readInt();
-    tileSize.fHeight = buffer.readInt();
-
-    switch (type) {
-        case kFractalNoise_Type:
-            return SkShaders::MakeFractalNoise(freqX, freqY, octaves, seed, &tileSize);
-        case kTurbulence_Type:
-            return SkShaders::MakeTurbulence(freqX, freqY, octaves, seed, &tileSize);
-        default:
-            // Really shouldn't get here b.c. of earlier check on type
-            buffer.validate(false);
-            return nullptr;
-    }
-}
-
-void SkPerlinNoiseShaderImpl::flatten(SkWriteBuffer& buffer) const {
-    buffer.writeInt((int) fType);
-    buffer.writeScalar(fBaseFrequencyX);
-    buffer.writeScalar(fBaseFrequencyY);
-    buffer.writeInt(fNumOctaves);
-    buffer.writeScalar(fSeed);
-    buffer.writeInt(fTileSize.fWidth);
-    buffer.writeInt(fTileSize.fHeight);
-}
-
-SkScalar SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::noise2D(
-        int channel, const StitchData& stitchData, const SkPoint& noiseVector) const {
-    struct Noise {
-        int noisePositionIntegerValue;
-        int nextNoisePositionIntegerValue;
-        SkScalar noisePositionFractionValue;
-        Noise(SkScalar component)
-        {
-            SkScalar position = component + kPerlinNoise;
-            noisePositionIntegerValue = SkScalarFloorToInt(position);
-            noisePositionFractionValue = position - SkIntToScalar(noisePositionIntegerValue);
-            nextNoisePositionIntegerValue = noisePositionIntegerValue + 1;
-        }
-    };
-    Noise noiseX(noiseVector.x());
-    Noise noiseY(noiseVector.y());
-    SkScalar u, v;
-    const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast<const SkPerlinNoiseShaderImpl&>(fShader);
-    // If stitching, adjust lattice points accordingly.
-    if (perlinNoiseShader.fStitchTiles) {
-        noiseX.noisePositionIntegerValue =
-            checkNoise(noiseX.noisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth);
-        noiseY.noisePositionIntegerValue =
-            checkNoise(noiseY.noisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight);
-        noiseX.nextNoisePositionIntegerValue =
-            checkNoise(noiseX.nextNoisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth);
-        noiseY.nextNoisePositionIntegerValue =
-            checkNoise(noiseY.nextNoisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight);
-    }
-    noiseX.noisePositionIntegerValue &= kBlockMask;
-    noiseY.noisePositionIntegerValue &= kBlockMask;
-    noiseX.nextNoisePositionIntegerValue &= kBlockMask;
-    noiseY.nextNoisePositionIntegerValue &= kBlockMask;
-    int i = fPaintingData.fLatticeSelector[noiseX.noisePositionIntegerValue];
-    int j = fPaintingData.fLatticeSelector[noiseX.nextNoisePositionIntegerValue];
-    int b00 = (i + noiseY.noisePositionIntegerValue) & kBlockMask;
-    int b10 = (j + noiseY.noisePositionIntegerValue) & kBlockMask;
-    int b01 = (i + noiseY.nextNoisePositionIntegerValue) & kBlockMask;
-    int b11 = (j + noiseY.nextNoisePositionIntegerValue) & kBlockMask;
-    SkScalar sx = smoothCurve(noiseX.noisePositionFractionValue);
-    SkScalar sy = smoothCurve(noiseY.noisePositionFractionValue);
-
-    if (sx < 0 || sy < 0 || sx > 1 || sy > 1) {
-        return 0;  // Check for pathological inputs.
-    }
-
-    // This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement
-    SkPoint fractionValue = SkPoint::Make(noiseX.noisePositionFractionValue,
-                                          noiseY.noisePositionFractionValue); // Offset (0,0)
-    u = fPaintingData.fGradient[channel][b00].dot(fractionValue);
-    fractionValue.fX -= SK_Scalar1; // Offset (-1,0)
-    v = fPaintingData.fGradient[channel][b10].dot(fractionValue);
-    SkScalar a = SkScalarInterp(u, v, sx);
-    fractionValue.fY -= SK_Scalar1; // Offset (-1,-1)
-    v = fPaintingData.fGradient[channel][b11].dot(fractionValue);
-    fractionValue.fX = noiseX.noisePositionFractionValue; // Offset (0,-1)
-    u = fPaintingData.fGradient[channel][b01].dot(fractionValue);
-    SkScalar b = SkScalarInterp(u, v, sx);
-    return SkScalarInterp(a, b, sy);
-}
-
-SkScalar SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::calculateTurbulenceValueForPoint(
-        int channel, StitchData& stitchData, const SkPoint& point) const {
-    const SkPerlinNoiseShaderImpl& perlinNoiseShader = static_cast<const SkPerlinNoiseShaderImpl&>(fShader);
-    if (perlinNoiseShader.fStitchTiles) {
-        stitchData = fPaintingData.fStitchDataInit;
-    }
-    SkScalar turbulenceFunctionResult = 0;
-    SkPoint noiseVector(SkPoint::Make(point.x() * fPaintingData.fBaseFrequency.fX,
-                                      point.y() * fPaintingData.fBaseFrequency.fY));
-    SkScalar ratio = SK_Scalar1;
-    for (int octave = 0; octave < perlinNoiseShader.fNumOctaves; ++octave) {
-        SkScalar noise = noise2D(channel, stitchData, noiseVector);
-        SkScalar numer = (perlinNoiseShader.fType == kFractalNoise_Type) ?
-                            noise : SkScalarAbs(noise);
-        turbulenceFunctionResult += numer / ratio;
-        noiseVector.fX *= 2;
-        noiseVector.fY *= 2;
-        ratio *= 2;
-        if (perlinNoiseShader.fStitchTiles) {
-            stitchData = StitchData(SkIntToScalar(stitchData.fWidth) * 2,
-                                    SkIntToScalar(stitchData.fHeight) * 2);
-        }
-    }
-
-    if (perlinNoiseShader.fType == kFractalNoise_Type) {
-        // For kFractalNoise the result is: noise[-1,1] * 0.5 + 0.5
-        turbulenceFunctionResult = SkScalarHalf(turbulenceFunctionResult + 1);
-    }
-
-    if (channel == 3) { // Scale alpha by paint value
-        turbulenceFunctionResult *= SkIntToScalar(getPaintAlpha()) / 255;
-    }
-
-    // Clamp result
-    return SkTPin(turbulenceFunctionResult, 0.0f, SK_Scalar1);
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-
-SkPMColor SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::shade(
-        const SkPoint& point, StitchData& stitchData) const {
-    SkPoint newPoint;
-    fMatrix.mapPoints(&newPoint, &point, 1);
-    newPoint.fX = SkScalarRoundToScalar(newPoint.fX);
-    newPoint.fY = SkScalarRoundToScalar(newPoint.fY);
-
-    U8CPU rgba[4];
-    for (int channel = 3; channel >= 0; --channel) {
-        SkScalar value;
-        value = calculateTurbulenceValueForPoint(channel, stitchData, newPoint);
-        rgba[channel] = SkScalarFloorToInt(255 * value);
-    }
-    return SkPreMultiplyARGB(rgba[3], rgba[0], rgba[1], rgba[2]);
-}
-
-#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
-SkShaderBase::Context* SkPerlinNoiseShaderImpl::onMakeContext(const ContextRec& rec,
-                                                              SkArenaAlloc* alloc) const {
-    // should we pay attention to rec's device-colorspace?
-    return alloc->make<PerlinNoiseShaderContext>(*this, rec);
-}
-#endif
-
-static inline SkMatrix total_matrix(const SkShaderBase::ContextRec& rec,
-                                    const SkShaderBase& shader) {
-    if (rec.fLocalMatrix) {
-        return SkMatrix::Concat(*rec.fMatrix, *rec.fLocalMatrix);
-    }
-    return *rec.fMatrix;
-}
-
-SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::PerlinNoiseShaderContext(
-        const SkPerlinNoiseShaderImpl& shader, const ContextRec& rec)
-    : INHERITED(shader, rec)
-    , fMatrix(total_matrix(rec, shader)) // used for temp storage, adjusted below
-    , fPaintingData(shader.fTileSize, shader.fSeed, shader.fBaseFrequencyX,
-                    shader.fBaseFrequencyY, fMatrix)
-{
-    // This (1,1) translation is due to WebKit's 1 based coordinates for the noise
-    // (as opposed to 0 based, usually). The same adjustment is in the setData() function.
-    fMatrix.setTranslate(-fMatrix.getTranslateX() + SK_Scalar1,
-                         -fMatrix.getTranslateY() + SK_Scalar1);
-}
-
-void SkPerlinNoiseShaderImpl::PerlinNoiseShaderContext::shadeSpan(
-        int x, int y, SkPMColor result[], int count) {
-    SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y));
-    StitchData stitchData;
-    for (int i = 0; i < count; ++i) {
-        result[i] = shade(point, stitchData);
-        point.fX += SK_Scalar1;
-    }
-}
-
-/////////////////////////////////////////////////////////////////////
-
-#if defined(SK_GANESH)
-
-class GrPerlinNoise2Effect : public GrFragmentProcessor {
-public:
-    static std::unique_ptr<GrFragmentProcessor> Make(
-            SkPerlinNoiseShaderImpl::Type type,
-            int numOctaves,
-            bool stitchTiles,
-            std::unique_ptr<SkPerlinNoiseShaderImpl::PaintingData> paintingData,
-            GrSurfaceProxyView permutationsView,
-            GrSurfaceProxyView noiseView,
-            const GrCaps& caps) {
-        static constexpr GrSamplerState kRepeatXSampler = {GrSamplerState::WrapMode::kRepeat,
-                                                           GrSamplerState::WrapMode::kClamp,
-                                                           GrSamplerState::Filter::kNearest};
-        auto permutationsFP =
-                GrTextureEffect::Make(std::move(permutationsView), kPremul_SkAlphaType,
-                                      SkMatrix::I(), kRepeatXSampler, caps);
-        auto noiseFP = GrTextureEffect::Make(std::move(noiseView), kPremul_SkAlphaType,
-                                             SkMatrix::I(), kRepeatXSampler, caps);
-
-        return std::unique_ptr<GrFragmentProcessor>(
-                new GrPerlinNoise2Effect(type,
-                                         numOctaves,
-                                         stitchTiles,
-                                         std::move(paintingData),
-                                         std::move(permutationsFP),
-                                         std::move(noiseFP)));
-    }
-
-    const char* name() const override { return "PerlinNoise"; }
-
-    std::unique_ptr<GrFragmentProcessor> clone() const override {
-        return std::unique_ptr<GrFragmentProcessor>(new GrPerlinNoise2Effect(*this));
-    }
-
-    const SkPerlinNoiseShaderImpl::StitchData& stitchData() const { return fPaintingData->fStitchDataInit; }
-
-    SkPerlinNoiseShaderImpl::Type type() const { return fType; }
-    bool stitchTiles() const { return fStitchTiles; }
-    const SkVector& baseFrequency() const { return fPaintingData->fBaseFrequency; }
-    int numOctaves() const { return fNumOctaves; }
-
-private:
-    class Impl : public ProgramImpl {
-    public:
-        SkString emitHelper(EmitArgs& args);
-        void emitCode(EmitArgs&) override;
-
-    private:
-        void onSetData(const GrGLSLProgramDataManager&, const GrFragmentProcessor&) override;
-
-        GrGLSLProgramDataManager::UniformHandle fStitchDataUni;
-        GrGLSLProgramDataManager::UniformHandle fBaseFrequencyUni;
-    };
-
-    std::unique_ptr<ProgramImpl> onMakeProgramImpl() const override {
-        return std::make_unique<Impl>();
-    }
-
-    void onAddToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const override;
-
-    bool onIsEqual(const GrFragmentProcessor& sBase) const override {
-        const GrPerlinNoise2Effect& s = sBase.cast<GrPerlinNoise2Effect>();
-        return fType == s.fType &&
-               fPaintingData->fBaseFrequency == s.fPaintingData->fBaseFrequency &&
-               fNumOctaves == s.fNumOctaves &&
-               fStitchTiles == s.fStitchTiles &&
-               fPaintingData->fStitchDataInit == s.fPaintingData->fStitchDataInit;
-    }
-
-    GrPerlinNoise2Effect(SkPerlinNoiseShaderImpl::Type type,
-                         int numOctaves,
-                         bool stitchTiles,
-                         std::unique_ptr<SkPerlinNoiseShaderImpl::PaintingData> paintingData,
-                         std::unique_ptr<GrFragmentProcessor> permutationsFP,
-                         std::unique_ptr<GrFragmentProcessor> noiseFP)
-            : INHERITED(kGrPerlinNoise2Effect_ClassID, kNone_OptimizationFlags)
-            , fType(type)
-            , fNumOctaves(numOctaves)
-            , fStitchTiles(stitchTiles)
-            , fPaintingData(std::move(paintingData)) {
-        this->registerChild(std::move(permutationsFP), SkSL::SampleUsage::Explicit());
-        this->registerChild(std::move(noiseFP), SkSL::SampleUsage::Explicit());
-        this->setUsesSampleCoordsDirectly();
-    }
-
-    GrPerlinNoise2Effect(const GrPerlinNoise2Effect& that)
-            : INHERITED(that)
-            , fType(that.fType)
-            , fNumOctaves(that.fNumOctaves)
-            , fStitchTiles(that.fStitchTiles)
-            , fPaintingData(new SkPerlinNoiseShaderImpl::PaintingData(*that.fPaintingData)) {}
-
-    GR_DECLARE_FRAGMENT_PROCESSOR_TEST
-
-    SkPerlinNoiseShaderImpl::Type       fType;
-    int                                 fNumOctaves;
-    bool                                fStitchTiles;
-
-    std::unique_ptr<SkPerlinNoiseShaderImpl::PaintingData> fPaintingData;
-
-    using INHERITED = GrFragmentProcessor;
-};
-
-/////////////////////////////////////////////////////////////////////
-GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrPerlinNoise2Effect)
-
-#if GR_TEST_UTILS
-std::unique_ptr<GrFragmentProcessor> GrPerlinNoise2Effect::TestCreate(GrProcessorTestData* d) {
-    int      numOctaves = d->fRandom->nextRangeU(2, 10);
-    bool     stitchTiles = d->fRandom->nextBool();
-    SkScalar seed = SkIntToScalar(d->fRandom->nextU());
-    SkISize  tileSize;
-    tileSize.fWidth = d->fRandom->nextRangeU(4, 4096);
-    tileSize.fHeight = d->fRandom->nextRangeU(4, 4096);
-    SkScalar baseFrequencyX = d->fRandom->nextRangeScalar(0.01f, 0.99f);
-    SkScalar baseFrequencyY = d->fRandom->nextRangeScalar(0.01f, 0.99f);
-
-    sk_sp<SkShader> shader(d->fRandom->nextBool() ?
-        SkShaders::MakeFractalNoise(baseFrequencyX, baseFrequencyY, numOctaves, seed,
-                                    stitchTiles ? &tileSize : nullptr) :
-        SkShaders::MakeTurbulence(baseFrequencyX, baseFrequencyY, numOctaves, seed,
-                                  stitchTiles ? &tileSize : nullptr));
-
-    GrTest::TestAsFPArgs asFPArgs(d);
-    return as_SB(shader)->asRootFragmentProcessor(asFPArgs.args(), GrTest::TestMatrix(d->fRandom));
-}
-#endif
-
-SkString GrPerlinNoise2Effect::Impl::emitHelper(EmitArgs& args) {
-    const GrPerlinNoise2Effect& pne = args.fFp.cast<GrPerlinNoise2Effect>();
-
-    GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-
-    // Add noise function
-    const GrShaderVar gPerlinNoiseArgs[] = {{"chanCoord", SkSLType::kHalf },
-                                            {"noiseVec ", SkSLType::kHalf2}};
-
-    const GrShaderVar gPerlinNoiseStitchArgs[] = {{"chanCoord" , SkSLType::kHalf },
-                                                  {"noiseVec"  , SkSLType::kHalf2},
-                                                  {"stitchData", SkSLType::kHalf2}};
-
-    SkString noiseCode;
-
-    noiseCode.append(
-        "half4 floorVal;"
-        "floorVal.xy = floor(noiseVec);"
-        "floorVal.zw = floorVal.xy + half2(1);"
-        "half2 fractVal = fract(noiseVec);"
-
-        // smooth curve : t^2*(3 - 2*t)
-        "half2 noiseSmooth = fractVal*fractVal*(half2(3) - 2*fractVal);"
-    );
-
-    // Adjust frequencies if we're stitching tiles
-    if (pne.stitchTiles()) {
-        noiseCode.append(
-            "floorVal -= step(stitchData.xyxy, floorVal) * stitchData.xyxy;"
-        );
-    }
-
-    // NOTE: We need to explicitly pass half4(1) as input color here, because the helper function
-    // can't see fInputColor (which is "_input" in the FP's outer function). skbug.com/10506
-    SkString sampleX = this->invokeChild(0, "half4(1)", args, "half2(floorVal.x, 0.5)");
-    SkString sampleY = this->invokeChild(0, "half4(1)", args, "half2(floorVal.z, 0.5)");
-    noiseCode.appendf("half2 latticeIdx = half2(%s.a, %s.a);", sampleX.c_str(), sampleY.c_str());
-
-    if (args.fShaderCaps->fPerlinNoiseRoundingFix) {
-        // Android rounding for Tegra devices, like, for example: Xoom (Tegra 2), Nexus 7 (Tegra 3).
-        // The issue is that colors aren't accurate enough on Tegra devices. For example, if an
-        // 8 bit value of 124 (or 0.486275 here) is entered, we can get a texture value of
-        // 123.513725 (or 0.484368 here). The following rounding operation prevents these precision
-        // issues from affecting the result of the noise by making sure that we only have multiples
-        // of 1/255. (Note that 1/255 is about 0.003921569, which is the value used here).
-        noiseCode.append(
-                "latticeIdx = floor(latticeIdx * half2(255.0) + half2(0.5)) * half2(0.003921569);");
-    }
-
-    // Get (x,y) coordinates with the permuted x
-    noiseCode.append("half4 bcoords = 256*latticeIdx.xyxy + floorVal.yyww;");
-
-    noiseCode.append("half2 uv;");
-
-    // This is the math to convert the two 16bit integer packed into rgba 8 bit input into a
-    // [-1,1] vector and perform a dot product between that vector and the provided vector.
-    // Save it as a string because we will repeat it 4x.
-    static constexpr const char* inc8bit = "0.00390625";  // 1.0 / 256.0
-    SkString dotLattice =
-            SkStringPrintf("dot((lattice.ga + lattice.rb*%s)*2 - half2(1), fractVal)", inc8bit);
-
-    SkString sampleA = this->invokeChild(1, "half4(1)", args, "half2(bcoords.x, chanCoord)");
-    SkString sampleB = this->invokeChild(1, "half4(1)", args, "half2(bcoords.y, chanCoord)");
-    SkString sampleC = this->invokeChild(1, "half4(1)", args, "half2(bcoords.w, chanCoord)");
-    SkString sampleD = this->invokeChild(1, "half4(1)", args, "half2(bcoords.z, chanCoord)");
-
-    // Compute u, at offset (0,0)
-    noiseCode.appendf("half4 lattice = %s;", sampleA.c_str());
-    noiseCode.appendf("uv.x = %s;", dotLattice.c_str());
-
-    // Compute v, at offset (-1,0)
-    noiseCode.append("fractVal.x -= 1.0;");
-    noiseCode.appendf("lattice = %s;", sampleB.c_str());
-    noiseCode.appendf("uv.y = %s;", dotLattice.c_str());
-
-    // Compute 'a' as a linear interpolation of 'u' and 'v'
-    noiseCode.append("half2 ab;");
-    noiseCode.append("ab.x = mix(uv.x, uv.y, noiseSmooth.x);");
-
-    // Compute v, at offset (-1,-1)
-    noiseCode.append("fractVal.y -= 1.0;");
-    noiseCode.appendf("lattice = %s;", sampleC.c_str());
-    noiseCode.appendf("uv.y = %s;", dotLattice.c_str());
-
-    // Compute u, at offset (0,-1)
-    noiseCode.append("fractVal.x += 1.0;");
-    noiseCode.appendf("lattice = %s;", sampleD.c_str());
-    noiseCode.appendf("uv.x = %s;", dotLattice.c_str());
-
-    // Compute 'b' as a linear interpolation of 'u' and 'v'
-    noiseCode.append("ab.y = mix(uv.x, uv.y, noiseSmooth.x);");
-    // Compute the noise as a linear interpolation of 'a' and 'b'
-    noiseCode.append("return mix(ab.x, ab.y, noiseSmooth.y);");
-
-    SkString noiseFuncName = fragBuilder->getMangledFunctionName("noiseFuncName");
-    if (pne.stitchTiles()) {
-        fragBuilder->emitFunction(SkSLType::kHalf, noiseFuncName.c_str(),
-                                  {gPerlinNoiseStitchArgs, std::size(gPerlinNoiseStitchArgs)},
-                                  noiseCode.c_str());
-    } else {
-        fragBuilder->emitFunction(SkSLType::kHalf, noiseFuncName.c_str(),
-                                  {gPerlinNoiseArgs, std::size(gPerlinNoiseArgs)},
-                                  noiseCode.c_str());
-    }
-
-    return noiseFuncName;
-}
-
-void GrPerlinNoise2Effect::Impl::emitCode(EmitArgs& args) {
-
-    SkString noiseFuncName = this->emitHelper(args);
-
-    const GrPerlinNoise2Effect& pne = args.fFp.cast<GrPerlinNoise2Effect>();
-
-    GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
-    GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
-
-    fBaseFrequencyUni = uniformHandler->addUniform(&pne, kFragment_GrShaderFlag, SkSLType::kHalf2,
-                                                   "baseFrequency");
-    const char* baseFrequencyUni = uniformHandler->getUniformCStr(fBaseFrequencyUni);
-
-    const char* stitchDataUni = nullptr;
-    if (pne.stitchTiles()) {
-        fStitchDataUni = uniformHandler->addUniform(&pne, kFragment_GrShaderFlag, SkSLType::kHalf2,
-                                                    "stitchData");
-        stitchDataUni = uniformHandler->getUniformCStr(fStitchDataUni);
-    }
-
-    // There are rounding errors if the floor operation is not performed here
-    fragBuilder->codeAppendf("half2 noiseVec = half2(floor(%s.xy) * %s);",
-                             args.fSampleCoord, baseFrequencyUni);
-
-    // Clear the color accumulator
-    fragBuilder->codeAppendf("half4 color = half4(0);");
-
-    if (pne.stitchTiles()) {
-        fragBuilder->codeAppendf("half2 stitchData = %s;", stitchDataUni);
-    }
-
-    fragBuilder->codeAppendf("half ratio = 1.0;");
-
-    // Loop over all octaves
-    fragBuilder->codeAppendf("for (int octave = 0; octave < %d; ++octave) {", pne.numOctaves());
-    fragBuilder->codeAppendf(    "color += ");
-    if (pne.type() != SkPerlinNoiseShaderImpl::kFractalNoise_Type) {
-        fragBuilder->codeAppend("abs(");
-    }
-
-    // There are 4 lines, put y coords at center of each.
-    static constexpr const char* chanCoordR = "0.5";
-    static constexpr const char* chanCoordG = "1.5";
-    static constexpr const char* chanCoordB = "2.5";
-    static constexpr const char* chanCoordA = "3.5";
-    if (pne.stitchTiles()) {
-        fragBuilder->codeAppendf(
-           "half4(%s(%s, noiseVec, stitchData), %s(%s, noiseVec, stitchData),"
-                 "%s(%s, noiseVec, stitchData), %s(%s, noiseVec, stitchData))",
-            noiseFuncName.c_str(), chanCoordR,
-            noiseFuncName.c_str(), chanCoordG,
-            noiseFuncName.c_str(), chanCoordB,
-            noiseFuncName.c_str(), chanCoordA);
-    } else {
-        fragBuilder->codeAppendf(
-            "half4(%s(%s, noiseVec), %s(%s, noiseVec),"
-                  "%s(%s, noiseVec), %s(%s, noiseVec))",
-            noiseFuncName.c_str(), chanCoordR,
-            noiseFuncName.c_str(), chanCoordG,
-            noiseFuncName.c_str(), chanCoordB,
-            noiseFuncName.c_str(), chanCoordA);
-    }
-    if (pne.type() != SkPerlinNoiseShaderImpl::kFractalNoise_Type) {
-        fragBuilder->codeAppend(")");  // end of "abs("
-    }
-    fragBuilder->codeAppend(" * ratio;");
-
-    fragBuilder->codeAppend("noiseVec *= half2(2.0);"
-                            "ratio *= 0.5;");
-
-    if (pne.stitchTiles()) {
-        fragBuilder->codeAppend("stitchData *= half2(2.0);");
-    }
-    fragBuilder->codeAppend("}");  // end of the for loop on octaves
-
-    if (pne.type() == SkPerlinNoiseShaderImpl::kFractalNoise_Type) {
-        // The value of turbulenceFunctionResult comes from ((turbulenceFunctionResult) + 1) / 2
-        // by fractalNoise and (turbulenceFunctionResult) by turbulence.
-        fragBuilder->codeAppendf("color = color * half4(0.5) + half4(0.5);");
-    }
-
-    // Clamp values
-    fragBuilder->codeAppendf("color = saturate(color);");
-
-    // Pre-multiply the result
-    fragBuilder->codeAppendf("return half4(color.rgb * color.aaa, color.a);");
-}
-
-void GrPerlinNoise2Effect::Impl::onSetData(const GrGLSLProgramDataManager& pdman,
-                                           const GrFragmentProcessor& processor) {
-    const GrPerlinNoise2Effect& turbulence = processor.cast<GrPerlinNoise2Effect>();
-
-    const SkVector& baseFrequency = turbulence.baseFrequency();
-    pdman.set2f(fBaseFrequencyUni, baseFrequency.fX, baseFrequency.fY);
-
-    if (turbulence.stitchTiles()) {
-        const SkPerlinNoiseShaderImpl::StitchData& stitchData = turbulence.stitchData();
-        pdman.set2f(fStitchDataUni,
-                    SkIntToScalar(stitchData.fWidth),
-                    SkIntToScalar(stitchData.fHeight));
-    }
-}
-
-void GrPerlinNoise2Effect::onAddToKey(const GrShaderCaps& caps, skgpu::KeyBuilder* b) const {
-    uint32_t key = fNumOctaves;
-    key = key << 3;  // Make room for next 3 bits
-    switch (fType) {
-        case SkPerlinNoiseShaderImpl::kFractalNoise_Type:
-            key |= 0x1;
-            break;
-        case SkPerlinNoiseShaderImpl::kTurbulence_Type:
-            key |= 0x2;
-            break;
-        default:
-            // leave key at 0
-            break;
-    }
-    if (fStitchTiles) {
-        key |= 0x4; // Flip the 3rd bit if tile stitching is on
-    }
-    b->add32(key);
-}
-
-/////////////////////////////////////////////////////////////////////
-
-std::unique_ptr<GrFragmentProcessor> SkPerlinNoiseShaderImpl::asFragmentProcessor(
-        const GrFPArgs& args, const MatrixRec& mRec) const {
-    SkASSERT(args.fContext);
-    SkASSERT(fNumOctaves);
-
-    const SkMatrix& totalMatrix = mRec.totalMatrix();
-
-    // Either we don't stitch tiles, or we have a valid tile size
-    SkASSERT(!fStitchTiles || !fTileSize.isEmpty());
-
-    auto paintingData = std::make_unique<SkPerlinNoiseShaderImpl::PaintingData>(fTileSize,
-                                                                                fSeed,
-                                                                                fBaseFrequencyX,
-                                                                                fBaseFrequencyY,
-                                                                                totalMatrix);
-
-    // Like shadeSpan, we start from device space. We will account for that below with a device
-    // space effect.
-
-    auto context = args.fContext;
-
-    const SkBitmap& permutationsBitmap = paintingData->getPermutationsBitmap();
-    const SkBitmap& noiseBitmap        = paintingData->getNoiseBitmap();
-
-    auto permutationsView = std::get<0>(GrMakeCachedBitmapProxyView(
-            context,
-            permutationsBitmap,
-            /*label=*/"PerlinNoiseShader_FragmentProcessor_PermutationsView"));
-    auto noiseView = std::get<0>(GrMakeCachedBitmapProxyView(
-            context, noiseBitmap, /*label=*/"PerlinNoiseShader_FragmentProcessor_NoiseView"));
-
-    if (permutationsView && noiseView) {
-        return GrFragmentProcessor::DeviceSpace(
-                GrMatrixEffect::Make(SkMatrix::Translate(1 - totalMatrix.getTranslateX(),
-                                                         1 - totalMatrix.getTranslateY()),
-                                     GrPerlinNoise2Effect::Make(fType,
-                                                                fNumOctaves,
-                                                                fStitchTiles,
-                                                                std::move(paintingData),
-                                                                std::move(permutationsView),
-                                                                std::move(noiseView),
-                                                                *context->priv().caps())));
-    }
-    return nullptr;
-}
-
-#endif
-
-#if defined(SK_GRAPHITE)
-
-// If either of these change then the corresponding change must also be made in the SkSL
-// perlin_noise_shader function.
-static_assert((int)SkPerlinNoiseShaderImpl::kFractalNoise_Type ==
-              (int)skgpu::graphite::PerlinNoiseShaderBlock::Type::kFractalNoise);
-static_assert((int)SkPerlinNoiseShaderImpl::kTurbulence_Type ==
-              (int)skgpu::graphite::PerlinNoiseShaderBlock::Type::kTurbulence);
-
-// If kBlockSize changes here then it must also be changed in the SkSL noise_function
-// implementation.
-static_assert(kBlockSize == 256);
-
-void SkPerlinNoiseShaderImpl::addToKey(const skgpu::graphite::KeyContext& keyContext,
-                                       skgpu::graphite::PaintParamsKeyBuilder* builder,
-                                       skgpu::graphite::PipelineDataGatherer* gatherer) const {
-    using namespace skgpu::graphite;
-
-    SkASSERT(fNumOctaves);
-
-    SkMatrix totalMatrix = keyContext.local2Dev().asM33();
-    if (keyContext.localMatrix()) {
-        totalMatrix.preConcat(*keyContext.localMatrix());
-    }
-
-    SkMatrix invTotal;
-    bool result = totalMatrix.invert(&invTotal);
-    if (!result) {
-        SKGPU_LOG_W("Couldn't invert totalMatrix for PerlinNoiseShader");
-
-        SolidColorShaderBlock::BeginBlock(keyContext, builder, gatherer, {1, 0, 0, 1});
-        builder->endBlock();
-        return;
-    }
-
-    auto paintingData = std::make_unique<SkPerlinNoiseShaderImpl::PaintingData>(fTileSize,
-                                                                                fSeed,
-                                                                                fBaseFrequencyX,
-                                                                                fBaseFrequencyY,
-                                                                                totalMatrix);
-
-    sk_sp<TextureProxy> perm =
-            RecorderPriv::CreateCachedProxy(keyContext.recorder(),
-                                            paintingData->getPermutationsBitmap());
-
-    sk_sp<TextureProxy> noise = RecorderPriv::CreateCachedProxy(keyContext.recorder(),
-                                                                paintingData->getNoiseBitmap());
-
-    if (!perm || !noise) {
-        SKGPU_LOG_W("Couldn't create tables for PerlinNoiseShader");
-
-        SolidColorShaderBlock::BeginBlock(keyContext, builder, gatherer, {1, 0, 0, 1});
-        builder->endBlock();
-        return;
-    }
-
-    PerlinNoiseShaderBlock::PerlinNoiseData data(static_cast<PerlinNoiseShaderBlock::Type>(fType),
-                                                 paintingData->fBaseFrequency,
-                                                 fNumOctaves,
-                                                 { paintingData->fStitchDataInit.fWidth,
-                                                   paintingData->fStitchDataInit.fHeight });
-
-    data.fPermutationsProxy = std::move(perm);
-    data.fNoiseProxy = std::move(noise);
-
-    // This (1,1) translation is due to WebKit's 1 based coordinates for the noise
-    // (as opposed to 0 based, usually). Remember: this matrix (shader2World) is going to be
-    // inverted before being applied.
-    SkMatrix shader2Local = SkMatrix::Translate(-1 + totalMatrix.getTranslateX(),
-                                                -1 + totalMatrix.getTranslateY());
-    shader2Local.postConcat(invTotal);
-
-    LocalMatrixShaderBlock::LMShaderData lmShaderData(shader2Local);
-
-    KeyContextWithLocalMatrix newContext(keyContext, shader2Local);
-
-    LocalMatrixShaderBlock::BeginBlock(newContext, builder, gatherer, &lmShaderData);
-        PerlinNoiseShaderBlock::BeginBlock(newContext, builder, gatherer, &data);
-        builder->endBlock();
-    builder->endBlock();
-}
-#endif // SK_GRAPHITE
-
-///////////////////////////////////////////////////////////////////////////////////////////////////
-
-static bool valid_input(SkScalar baseX, SkScalar baseY, int numOctaves, const SkISize* tileSize,
-                        SkScalar seed) {
-    if (!(baseX >= 0 && baseY >= 0)) {
-        return false;
-    }
-    if (!(numOctaves >= 0 && numOctaves <= SkPerlinNoiseShaderImpl::kMaxOctaves)) {
-        return false;
-    }
-    if (tileSize && !(tileSize->width() >= 0 && tileSize->height() >= 0)) {
-        return false;
-    }
-    if (!SkScalarIsFinite(seed)) {
-        return false;
-    }
-    return true;
-}
-
-void SkRegisterPerlinNoiseShaderFlattenable() {
-    SK_REGISTER_FLATTENABLE(SkPerlinNoiseShaderImpl);
-}
-
-namespace SkShaders {
-sk_sp<SkShader> MakeFractalNoise(SkScalar baseFrequencyX, SkScalar baseFrequencyY,
-                                 int numOctaves, SkScalar seed, const SkISize* tileSize) {
-    if (!valid_input(baseFrequencyX, baseFrequencyY, numOctaves, tileSize, seed)) {
-        return nullptr;
-    }
-
-    if (0 == numOctaves) {
-        // For kFractalNoise, w/o any octaves, the entire shader collapses to:
-        //    [0,0,0,0] * 0.5 + 0.5
-        constexpr SkColor4f kTransparentGray = {0.5f, 0.5f, 0.5f, 0.5f};
-
-        return SkShaders::Color(kTransparentGray, /* colorSpace= */ nullptr);
-    }
-
-    return sk_sp<SkShader>(new SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::kFractalNoise_Type,
-                                                       baseFrequencyX, baseFrequencyY, numOctaves,
-                                                       seed, tileSize));
-}
-
-sk_sp<SkShader> MakeTurbulence(SkScalar baseFrequencyX, SkScalar baseFrequencyY,
-                               int numOctaves, SkScalar seed, const SkISize* tileSize) {
-    if (!valid_input(baseFrequencyX, baseFrequencyY, numOctaves, tileSize, seed)) {
-        return nullptr;
-    }
-
-    if (0 == numOctaves) {
-        // For kTurbulence, w/o any octaves, the entire shader collapses to: [0,0,0,0]
-        return SkShaders::Color(SkColors::kTransparent, /* colorSpace= */ nullptr);
-    }
-
-    return sk_sp<SkShader>(new SkPerlinNoiseShaderImpl(SkPerlinNoiseShaderImpl::kTurbulence_Type,
-                                                       baseFrequencyX, baseFrequencyY, numOctaves,
-                                                       seed, tileSize));
-}
-
-}  // namespace SkShaders
diff --git a/src/shaders/SkPerlinNoiseShaderImpl.cpp b/src/shaders/SkPerlinNoiseShaderImpl.cpp
new file mode 100644
index 0000000..374ee56
--- /dev/null
+++ b/src/shaders/SkPerlinNoiseShaderImpl.cpp
@@ -0,0 +1,413 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/shaders/SkPerlinNoiseShaderImpl.h"
+
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkShader.h"
+#include "include/effects/SkPerlinNoiseShader.h"
+#include "include/private/base/SkCPUTypes.h"
+#include "include/private/base/SkTPin.h"
+#include "src/base/SkArenaAlloc.h"
+#include "src/core/SkReadBuffer.h"
+#include "src/core/SkWriteBuffer.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyContext.h"
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/Log.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#include "src/gpu/graphite/RecorderPriv.h"
+#include "src/gpu/graphite/TextureProxyView.h"
+#include "src/image/SkImage_Base.h"
+#endif  // SK_GRAPHITE
+
+namespace {
+
+// noiseValue is the color component's value (or color)
+// limitValue is the maximum perlin noise array index value allowed
+// newValue is the current noise dimension (either width or height)
+inline int checkNoise(int noiseValue, int limitValue, int newValue) {
+    // If the noise value would bring us out of bounds of the current noise array while we are
+    // stiching noise tiles together, wrap the noise around the current dimension of the noise to
+    // stay within the array bounds in a continuous fashion (so that tiling lines are not visible)
+    if (noiseValue >= limitValue) {
+        noiseValue -= newValue;
+    }
+    return noiseValue;
+}
+
+inline SkScalar smoothCurve(SkScalar t) { return t * t * (3 - 2 * t); }
+
+}  // end namespace
+
+SkPerlinNoiseShader::SkPerlinNoiseShader(SkPerlinNoiseShader::Type type,
+                                         SkScalar baseFrequencyX,
+                                         SkScalar baseFrequencyY,
+                                         int numOctaves,
+                                         SkScalar seed,
+                                         const SkISize* tileSize)
+        : fType(type)
+        , fBaseFrequencyX(baseFrequencyX)
+        , fBaseFrequencyY(baseFrequencyY)
+        , fNumOctaves(numOctaves > kMaxOctaves ? kMaxOctaves
+                                               : numOctaves)  //[0,255] octaves allowed
+        , fSeed(seed)
+        , fTileSize(nullptr == tileSize ? SkISize::Make(0, 0) : *tileSize)
+        , fStitchTiles(!fTileSize.isEmpty()) {
+    SkASSERT(numOctaves >= 0 && numOctaves <= kMaxOctaves);
+    SkASSERT(fBaseFrequencyX >= 0);
+    SkASSERT(fBaseFrequencyY >= 0);
+}
+
+sk_sp<SkFlattenable> SkPerlinNoiseShader::CreateProc(SkReadBuffer& buffer) {
+    Type type = buffer.read32LE(kLast_Type);
+
+    SkScalar freqX = buffer.readScalar();
+    SkScalar freqY = buffer.readScalar();
+    int octaves = buffer.read32LE<int>(kMaxOctaves);
+
+    SkScalar seed = buffer.readScalar();
+    SkISize tileSize;
+    tileSize.fWidth = buffer.readInt();
+    tileSize.fHeight = buffer.readInt();
+
+    switch (type) {
+        case kFractalNoise_Type:
+            return SkShaders::MakeFractalNoise(freqX, freqY, octaves, seed, &tileSize);
+        case kTurbulence_Type:
+            return SkShaders::MakeTurbulence(freqX, freqY, octaves, seed, &tileSize);
+        default:
+            // Really shouldn't get here b.c. of earlier check on type
+            buffer.validate(false);
+            return nullptr;
+    }
+}
+
+void SkPerlinNoiseShader::flatten(SkWriteBuffer& buffer) const {
+    buffer.writeInt((int)fType);
+    buffer.writeScalar(fBaseFrequencyX);
+    buffer.writeScalar(fBaseFrequencyY);
+    buffer.writeInt(fNumOctaves);
+    buffer.writeScalar(fSeed);
+    buffer.writeInt(fTileSize.fWidth);
+    buffer.writeInt(fTileSize.fHeight);
+}
+
+SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::noise2D(int channel,
+                                                                const StitchData& stitchData,
+                                                                const SkPoint& noiseVector) const {
+    struct Noise {
+        int noisePositionIntegerValue;
+        int nextNoisePositionIntegerValue;
+        SkScalar noisePositionFractionValue;
+        Noise(SkScalar component) {
+            SkScalar position = component + kPerlinNoise;
+            noisePositionIntegerValue = SkScalarFloorToInt(position);
+            noisePositionFractionValue = position - SkIntToScalar(noisePositionIntegerValue);
+            nextNoisePositionIntegerValue = noisePositionIntegerValue + 1;
+        }
+    };
+    Noise noiseX(noiseVector.x());
+    Noise noiseY(noiseVector.y());
+    SkScalar u, v;
+    const SkPerlinNoiseShader& perlinNoiseShader = static_cast<const SkPerlinNoiseShader&>(fShader);
+    // If stitching, adjust lattice points accordingly.
+    if (perlinNoiseShader.fStitchTiles) {
+        noiseX.noisePositionIntegerValue =
+                checkNoise(noiseX.noisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth);
+        noiseY.noisePositionIntegerValue =
+                checkNoise(noiseY.noisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight);
+        noiseX.nextNoisePositionIntegerValue = checkNoise(
+                noiseX.nextNoisePositionIntegerValue, stitchData.fWrapX, stitchData.fWidth);
+        noiseY.nextNoisePositionIntegerValue = checkNoise(
+                noiseY.nextNoisePositionIntegerValue, stitchData.fWrapY, stitchData.fHeight);
+    }
+    noiseX.noisePositionIntegerValue &= kBlockMask;
+    noiseY.noisePositionIntegerValue &= kBlockMask;
+    noiseX.nextNoisePositionIntegerValue &= kBlockMask;
+    noiseY.nextNoisePositionIntegerValue &= kBlockMask;
+    int i = fPaintingData.fLatticeSelector[noiseX.noisePositionIntegerValue];
+    int j = fPaintingData.fLatticeSelector[noiseX.nextNoisePositionIntegerValue];
+    int b00 = (i + noiseY.noisePositionIntegerValue) & kBlockMask;
+    int b10 = (j + noiseY.noisePositionIntegerValue) & kBlockMask;
+    int b01 = (i + noiseY.nextNoisePositionIntegerValue) & kBlockMask;
+    int b11 = (j + noiseY.nextNoisePositionIntegerValue) & kBlockMask;
+    SkScalar sx = smoothCurve(noiseX.noisePositionFractionValue);
+    SkScalar sy = smoothCurve(noiseY.noisePositionFractionValue);
+
+    if (sx < 0 || sy < 0 || sx > 1 || sy > 1) {
+        return 0;  // Check for pathological inputs.
+    }
+
+    // This is taken 1:1 from SVG spec: http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement
+    SkPoint fractionValue = SkPoint::Make(noiseX.noisePositionFractionValue,
+                                          noiseY.noisePositionFractionValue);  // Offset (0,0)
+    u = fPaintingData.fGradient[channel][b00].dot(fractionValue);
+    fractionValue.fX -= SK_Scalar1;  // Offset (-1,0)
+    v = fPaintingData.fGradient[channel][b10].dot(fractionValue);
+    SkScalar a = SkScalarInterp(u, v, sx);
+    fractionValue.fY -= SK_Scalar1;  // Offset (-1,-1)
+    v = fPaintingData.fGradient[channel][b11].dot(fractionValue);
+    fractionValue.fX = noiseX.noisePositionFractionValue;  // Offset (0,-1)
+    u = fPaintingData.fGradient[channel][b01].dot(fractionValue);
+    SkScalar b = SkScalarInterp(u, v, sx);
+    return SkScalarInterp(a, b, sy);
+}
+
+SkScalar SkPerlinNoiseShader::PerlinNoiseShaderContext::calculateTurbulenceValueForPoint(
+        int channel, StitchData& stitchData, const SkPoint& point) const {
+    const SkPerlinNoiseShader& perlinNoiseShader = static_cast<const SkPerlinNoiseShader&>(fShader);
+    if (perlinNoiseShader.fStitchTiles) {
+        stitchData = fPaintingData.fStitchDataInit;
+    }
+    SkScalar turbulenceFunctionResult = 0;
+    SkPoint noiseVector(SkPoint::Make(point.x() * fPaintingData.fBaseFrequency.fX,
+                                      point.y() * fPaintingData.fBaseFrequency.fY));
+    SkScalar ratio = SK_Scalar1;
+    for (int octave = 0; octave < perlinNoiseShader.fNumOctaves; ++octave) {
+        SkScalar noise = noise2D(channel, stitchData, noiseVector);
+        SkScalar numer =
+                (perlinNoiseShader.fType == kFractalNoise_Type) ? noise : SkScalarAbs(noise);
+        turbulenceFunctionResult += numer / ratio;
+        noiseVector.fX *= 2;
+        noiseVector.fY *= 2;
+        ratio *= 2;
+        if (perlinNoiseShader.fStitchTiles) {
+            stitchData = StitchData(SkIntToScalar(stitchData.fWidth) * 2,
+                                    SkIntToScalar(stitchData.fHeight) * 2);
+        }
+    }
+
+    if (perlinNoiseShader.fType == kFractalNoise_Type) {
+        // For kFractalNoise the result is: noise[-1,1] * 0.5 + 0.5
+        turbulenceFunctionResult = SkScalarHalf(turbulenceFunctionResult + 1);
+    }
+
+    if (channel == 3) {  // Scale alpha by paint value
+        turbulenceFunctionResult *= SkIntToScalar(getPaintAlpha()) / 255;
+    }
+
+    // Clamp result
+    return SkTPin(turbulenceFunctionResult, 0.0f, SK_Scalar1);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+SkPMColor SkPerlinNoiseShader::PerlinNoiseShaderContext::shade(const SkPoint& point,
+                                                               StitchData& stitchData) const {
+    SkPoint newPoint;
+    fMatrix.mapPoints(&newPoint, &point, 1);
+    newPoint.fX = SkScalarRoundToScalar(newPoint.fX);
+    newPoint.fY = SkScalarRoundToScalar(newPoint.fY);
+
+    U8CPU rgba[4];
+    for (int channel = 3; channel >= 0; --channel) {
+        SkScalar value;
+        value = calculateTurbulenceValueForPoint(channel, stitchData, newPoint);
+        rgba[channel] = SkScalarFloorToInt(255 * value);
+    }
+    return SkPreMultiplyARGB(rgba[3], rgba[0], rgba[1], rgba[2]);
+}
+
+#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
+SkShaderBase::Context* SkPerlinNoiseShader::onMakeContext(const ContextRec& rec,
+                                                          SkArenaAlloc* alloc) const {
+    // should we pay attention to rec's device-colorspace?
+    return alloc->make<PerlinNoiseShaderContext>(*this, rec);
+}
+#endif
+
+static inline SkMatrix total_matrix(const SkShaderBase::ContextRec& rec,
+                                    const SkShaderBase& shader) {
+    if (rec.fLocalMatrix) {
+        return SkMatrix::Concat(*rec.fMatrix, *rec.fLocalMatrix);
+    }
+    return *rec.fMatrix;
+}
+
+SkPerlinNoiseShader::PerlinNoiseShaderContext::PerlinNoiseShaderContext(
+        const SkPerlinNoiseShader& shader, const ContextRec& rec)
+        : Context(shader, rec)
+        , fMatrix(total_matrix(rec, shader))  // used for temp storage, adjusted below
+        , fPaintingData(shader.fTileSize,
+                        shader.fSeed,
+                        shader.fBaseFrequencyX,
+                        shader.fBaseFrequencyY,
+                        fMatrix) {
+    // This (1,1) translation is due to WebKit's 1 based coordinates for the noise
+    // (as opposed to 0 based, usually). The same adjustment is in the setData() function.
+    fMatrix.setTranslate(-fMatrix.getTranslateX() + SK_Scalar1,
+                         -fMatrix.getTranslateY() + SK_Scalar1);
+}
+
+void SkPerlinNoiseShader::PerlinNoiseShaderContext::shadeSpan(int x,
+                                                              int y,
+                                                              SkPMColor result[],
+                                                              int count) {
+    SkPoint point = SkPoint::Make(SkIntToScalar(x), SkIntToScalar(y));
+    StitchData stitchData;
+    for (int i = 0; i < count; ++i) {
+        result[i] = shade(point, stitchData);
+        point.fX += SK_Scalar1;
+    }
+}
+
+#if defined(SK_GRAPHITE)
+
+// If either of these change then the corresponding change must also be made in the SkSL
+// perlin_noise_shader function.
+static_assert((int)SkPerlinNoiseShader::kFractalNoise_Type ==
+              (int)skgpu::graphite::PerlinNoiseShaderBlock::Type::kFractalNoise);
+static_assert((int)SkPerlinNoiseShader::kTurbulence_Type ==
+              (int)skgpu::graphite::PerlinNoiseShaderBlock::Type::kTurbulence);
+
+void SkPerlinNoiseShader::addToKey(const skgpu::graphite::KeyContext& keyContext,
+                                   skgpu::graphite::PaintParamsKeyBuilder* builder,
+                                   skgpu::graphite::PipelineDataGatherer* gatherer) const {
+    // If kBlockSize changes here then it must also be changed in the SkSL noise_function
+    // implementation.
+    static_assert(SkPerlinNoiseShader::kBlockSize == 256);
+
+    using namespace skgpu::graphite;
+
+    SkASSERT(fNumOctaves);
+
+    SkMatrix totalMatrix = keyContext.local2Dev().asM33();
+    if (keyContext.localMatrix()) {
+        totalMatrix.preConcat(*keyContext.localMatrix());
+    }
+
+    SkMatrix invTotal;
+    bool result = totalMatrix.invert(&invTotal);
+    if (!result) {
+        SKGPU_LOG_W("Couldn't invert totalMatrix for PerlinNoiseShader");
+
+        SolidColorShaderBlock::BeginBlock(keyContext, builder, gatherer, {1, 0, 0, 1});
+        builder->endBlock();
+        return;
+    }
+
+    auto paintingData = this->getPaintingData(totalMatrix);
+    paintingData->generateBitmaps();
+
+    sk_sp<TextureProxy> perm = RecorderPriv::CreateCachedProxy(
+            keyContext.recorder(), paintingData->getPermutationsBitmap());
+
+    sk_sp<TextureProxy> noise =
+            RecorderPriv::CreateCachedProxy(keyContext.recorder(), paintingData->getNoiseBitmap());
+
+    if (!perm || !noise) {
+        SKGPU_LOG_W("Couldn't create tables for PerlinNoiseShader");
+
+        SolidColorShaderBlock::BeginBlock(keyContext, builder, gatherer, {1, 0, 0, 1});
+        builder->endBlock();
+        return;
+    }
+
+    PerlinNoiseShaderBlock::PerlinNoiseData data(
+            static_cast<PerlinNoiseShaderBlock::Type>(fType),
+            paintingData->fBaseFrequency,
+            fNumOctaves,
+            {paintingData->fStitchDataInit.fWidth, paintingData->fStitchDataInit.fHeight});
+
+    data.fPermutationsProxy = std::move(perm);
+    data.fNoiseProxy = std::move(noise);
+
+    // This (1,1) translation is due to WebKit's 1 based coordinates for the noise
+    // (as opposed to 0 based, usually). Remember: this matrix (shader2World) is going to be
+    // inverted before being applied.
+    SkMatrix shader2Local =
+            SkMatrix::Translate(-1 + totalMatrix.getTranslateX(), -1 + totalMatrix.getTranslateY());
+    shader2Local.postConcat(invTotal);
+
+    LocalMatrixShaderBlock::LMShaderData lmShaderData(shader2Local);
+
+    KeyContextWithLocalMatrix newContext(keyContext, shader2Local);
+
+    LocalMatrixShaderBlock::BeginBlock(newContext, builder, gatherer, &lmShaderData);
+    PerlinNoiseShaderBlock::BeginBlock(newContext, builder, gatherer, &data);
+    builder->endBlock();
+    builder->endBlock();
+}
+#endif  // SK_GRAPHITE
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool valid_input(
+        SkScalar baseX, SkScalar baseY, int numOctaves, const SkISize* tileSize, SkScalar seed) {
+    if (!(baseX >= 0 && baseY >= 0)) {
+        return false;
+    }
+    if (!(numOctaves >= 0 && numOctaves <= SkPerlinNoiseShader::kMaxOctaves)) {
+        return false;
+    }
+    if (tileSize && !(tileSize->width() >= 0 && tileSize->height() >= 0)) {
+        return false;
+    }
+    if (!SkScalarIsFinite(seed)) {
+        return false;
+    }
+    return true;
+}
+
+void SkRegisterPerlinNoiseShaderFlattenable() {
+    SK_REGISTER_FLATTENABLE(SkPerlinNoiseShader);
+    // Previous name
+    SkFlattenable::Register("SkPerlinNoiseShaderImpl", SkPerlinNoiseShader::CreateProc);
+}
+
+namespace SkShaders {
+sk_sp<SkShader> MakeFractalNoise(SkScalar baseFrequencyX,
+                                 SkScalar baseFrequencyY,
+                                 int numOctaves,
+                                 SkScalar seed,
+                                 const SkISize* tileSize) {
+    if (!valid_input(baseFrequencyX, baseFrequencyY, numOctaves, tileSize, seed)) {
+        return nullptr;
+    }
+
+    if (0 == numOctaves) {
+        // For kFractalNoise, w/o any octaves, the entire shader collapses to:
+        //    [0,0,0,0] * 0.5 + 0.5
+        constexpr SkColor4f kTransparentGray = {0.5f, 0.5f, 0.5f, 0.5f};
+
+        return SkShaders::Color(kTransparentGray, /* colorSpace= */ nullptr);
+    }
+
+    return sk_sp<SkShader>(new SkPerlinNoiseShader(SkPerlinNoiseShader::kFractalNoise_Type,
+                                                   baseFrequencyX,
+                                                   baseFrequencyY,
+                                                   numOctaves,
+                                                   seed,
+                                                   tileSize));
+}
+
+sk_sp<SkShader> MakeTurbulence(SkScalar baseFrequencyX,
+                               SkScalar baseFrequencyY,
+                               int numOctaves,
+                               SkScalar seed,
+                               const SkISize* tileSize) {
+    if (!valid_input(baseFrequencyX, baseFrequencyY, numOctaves, tileSize, seed)) {
+        return nullptr;
+    }
+
+    if (0 == numOctaves) {
+        // For kTurbulence, w/o any octaves, the entire shader collapses to: [0,0,0,0]
+        return SkShaders::Color(SkColors::kTransparent, /* colorSpace= */ nullptr);
+    }
+
+    return sk_sp<SkShader>(new SkPerlinNoiseShader(SkPerlinNoiseShader::kTurbulence_Type,
+                                                   baseFrequencyX,
+                                                   baseFrequencyY,
+                                                   numOctaves,
+                                                   seed,
+                                                   tileSize));
+}
+
+}  // namespace SkShaders
diff --git a/src/shaders/SkPerlinNoiseShaderImpl.h b/src/shaders/SkPerlinNoiseShaderImpl.h
new file mode 100644
index 0000000..29175ac
--- /dev/null
+++ b/src/shaders/SkPerlinNoiseShaderImpl.h
@@ -0,0 +1,351 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkPerlinNoiseShaderImpl_DEFINED
+#define SkPerlinNoiseShaderImpl_DEFINED
+
+#include "include/core/SkAlphaType.h"
+#include "include/core/SkBitmap.h"
+#include "include/core/SkColor.h"
+#include "include/core/SkColorType.h"
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkSize.h"
+#include "include/core/SkTypes.h"
+#include "include/private/base/SkFloatingPoint.h"
+#include "include/private/base/SkMath.h"
+#include "src/shaders/SkShaderBase.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+
+class SkArenaAlloc;
+class SkReadBuffer;
+class SkWriteBuffer;
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyContext.h"
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/Log.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#include "src/gpu/graphite/RecorderPriv.h"
+#include "src/gpu/graphite/TextureProxyView.h"
+#include "src/image/SkImage_Base.h"
+#endif  // SK_GRAPHITE
+
+class SkPerlinNoiseShader : public SkShaderBase {
+private:
+    static constexpr int kBlockSize = 256;
+    static constexpr int kBlockMask = kBlockSize - 1;
+    static constexpr int kPerlinNoise = 4096;
+    static constexpr int kRandMaximum = SK_MaxS32;  // 2**31 - 1
+
+public:
+    struct StitchData {
+        StitchData() : fWidth(0), fWrapX(0), fHeight(0), fWrapY(0) {}
+
+        StitchData(SkScalar w, SkScalar h)
+                : fWidth(std::min(SkScalarRoundToInt(w), SK_MaxS32 - kPerlinNoise))
+                , fWrapX(kPerlinNoise + fWidth)
+                , fHeight(std::min(SkScalarRoundToInt(h), SK_MaxS32 - kPerlinNoise))
+                , fWrapY(kPerlinNoise + fHeight) {}
+
+        bool operator==(const StitchData& other) const {
+            return fWidth == other.fWidth && fWrapX == other.fWrapX && fHeight == other.fHeight &&
+                   fWrapY == other.fWrapY;
+        }
+
+        int fWidth;  // How much to subtract to wrap for stitching.
+        int fWrapX;  // Minimum value to wrap.
+        int fHeight;
+        int fWrapY;
+    };
+
+    struct PaintingData {
+        PaintingData(const SkISize& tileSize,
+                     SkScalar seed,
+                     SkScalar baseFrequencyX,
+                     SkScalar baseFrequencyY,
+                     const SkMatrix& matrix) {
+            SkVector tileVec;
+            matrix.mapVector(
+                    SkIntToScalar(tileSize.fWidth), SkIntToScalar(tileSize.fHeight), &tileVec);
+
+            SkSize scale;
+            if (!matrix.decomposeScale(&scale, nullptr)) {
+                scale.set(SK_ScalarNearlyZero, SK_ScalarNearlyZero);
+            }
+            fBaseFrequency.set(baseFrequencyX * SkScalarInvert(scale.width()),
+                               baseFrequencyY * SkScalarInvert(scale.height()));
+            fTileSize.set(SkScalarRoundToInt(tileVec.fX), SkScalarRoundToInt(tileVec.fY));
+            this->init(seed);
+            if (!fTileSize.isEmpty()) {
+                this->stitch();
+            }
+        }
+
+        void generateBitmaps() {
+            SkImageInfo info = SkImageInfo::MakeA8(kBlockSize, 1);
+            fPermutationsBitmap.installPixels(info, fLatticeSelector, info.minRowBytes());
+            fPermutationsBitmap.setImmutable();
+
+            info = SkImageInfo::Make(kBlockSize, 4, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
+            fNoiseBitmap.installPixels(info, fNoise[0][0], info.minRowBytes());
+            fNoiseBitmap.setImmutable();
+        }
+
+        PaintingData(const PaintingData& that)
+                : fSeed(that.fSeed)
+                , fTileSize(that.fTileSize)
+                , fBaseFrequency(that.fBaseFrequency)
+                , fStitchDataInit(that.fStitchDataInit)
+                , fPermutationsBitmap(that.fPermutationsBitmap)
+                , fNoiseBitmap(that.fNoiseBitmap) {
+            memcpy(fLatticeSelector, that.fLatticeSelector, sizeof(fLatticeSelector));
+            memcpy(fNoise, that.fNoise, sizeof(fNoise));
+            memcpy(fGradient, that.fGradient, sizeof(fGradient));
+        }
+
+        int fSeed;
+        uint8_t fLatticeSelector[kBlockSize];
+        uint16_t fNoise[4][kBlockSize][2];
+        SkPoint fGradient[4][kBlockSize];
+        SkISize fTileSize;
+        SkVector fBaseFrequency;
+        StitchData fStitchDataInit;
+
+    private:
+        SkBitmap fPermutationsBitmap;
+        SkBitmap fNoiseBitmap;
+
+        inline int random() {
+            // See https://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement
+            // m = kRandMaximum, 2**31 - 1 (2147483647)
+            static constexpr int kRandAmplitude = 16807;  // 7**5; primitive root of m
+            static constexpr int kRandQ = 127773;         // m / a
+            static constexpr int kRandR = 2836;           // m % a
+
+            int result = kRandAmplitude * (fSeed % kRandQ) - kRandR * (fSeed / kRandQ);
+            if (result <= 0) {
+                result += kRandMaximum;
+            }
+            fSeed = result;
+            return result;
+        }
+
+        // Only called once. Could be part of the constructor.
+        void init(SkScalar seed) {
+            // According to the SVG spec, we must truncate (not round) the seed value.
+            fSeed = SkScalarTruncToInt(seed);
+            // The seed value clamp to the range [1, kRandMaximum - 1].
+            if (fSeed <= 0) {
+                fSeed = -(fSeed % (kRandMaximum - 1)) + 1;
+            }
+            if (fSeed > kRandMaximum - 1) {
+                fSeed = kRandMaximum - 1;
+            }
+            for (int channel = 0; channel < 4; ++channel) {
+                for (int i = 0; i < kBlockSize; ++i) {
+                    fLatticeSelector[i] = i;
+                    fNoise[channel][i][0] = (random() % (2 * kBlockSize));
+                    fNoise[channel][i][1] = (random() % (2 * kBlockSize));
+                }
+            }
+            for (int i = kBlockSize - 1; i > 0; --i) {
+                int k = fLatticeSelector[i];
+                int j = random() % kBlockSize;
+                SkASSERT(j >= 0);
+                SkASSERT(j < kBlockSize);
+                fLatticeSelector[i] = fLatticeSelector[j];
+                fLatticeSelector[j] = k;
+            }
+
+            // Perform the permutations now
+            {
+                // Copy noise data
+                uint16_t noise[4][kBlockSize][2];
+                for (int i = 0; i < kBlockSize; ++i) {
+                    for (int channel = 0; channel < 4; ++channel) {
+                        for (int j = 0; j < 2; ++j) {
+                            noise[channel][i][j] = fNoise[channel][i][j];
+                        }
+                    }
+                }
+                // Do permutations on noise data
+                for (int i = 0; i < kBlockSize; ++i) {
+                    for (int channel = 0; channel < 4; ++channel) {
+                        for (int j = 0; j < 2; ++j) {
+                            fNoise[channel][i][j] = noise[channel][fLatticeSelector[i]][j];
+                        }
+                    }
+                }
+            }
+
+            // Half of the largest possible value for 16 bit unsigned int
+            static constexpr SkScalar kHalfMax16bits = 32767.5f;
+
+            // Compute gradients from permuted noise data
+            static constexpr SkScalar kInvBlockSizef = 1.0 / SkIntToScalar(kBlockSize);
+            for (int channel = 0; channel < 4; ++channel) {
+                for (int i = 0; i < kBlockSize; ++i) {
+                    fGradient[channel][i] =
+                            SkPoint::Make((fNoise[channel][i][0] - kBlockSize) * kInvBlockSizef,
+                                          (fNoise[channel][i][1] - kBlockSize) * kInvBlockSizef);
+                    fGradient[channel][i].normalize();
+                    // Put the normalized gradient back into the noise data
+                    fNoise[channel][i][0] =
+                            SkScalarRoundToInt((fGradient[channel][i].fX + 1) * kHalfMax16bits);
+                    fNoise[channel][i][1] =
+                            SkScalarRoundToInt((fGradient[channel][i].fY + 1) * kHalfMax16bits);
+                }
+            }
+        }
+
+        // Only called once. Could be part of the constructor.
+        void stitch() {
+            SkScalar tileWidth = SkIntToScalar(fTileSize.width());
+            SkScalar tileHeight = SkIntToScalar(fTileSize.height());
+            SkASSERT(tileWidth > 0 && tileHeight > 0);
+            // When stitching tiled turbulence, the frequencies must be adjusted
+            // so that the tile borders will be continuous.
+            if (fBaseFrequency.fX) {
+                SkScalar lowFrequencx =
+                        SkScalarFloorToScalar(tileWidth * fBaseFrequency.fX) / tileWidth;
+                SkScalar highFrequencx =
+                        SkScalarCeilToScalar(tileWidth * fBaseFrequency.fX) / tileWidth;
+                // BaseFrequency should be non-negative according to the standard.
+                // lowFrequencx can be 0 if fBaseFrequency.fX is very small.
+                if (sk_ieee_float_divide(fBaseFrequency.fX, lowFrequencx) <
+                    highFrequencx / fBaseFrequency.fX) {
+                    fBaseFrequency.fX = lowFrequencx;
+                } else {
+                    fBaseFrequency.fX = highFrequencx;
+                }
+            }
+            if (fBaseFrequency.fY) {
+                SkScalar lowFrequency =
+                        SkScalarFloorToScalar(tileHeight * fBaseFrequency.fY) / tileHeight;
+                SkScalar highFrequency =
+                        SkScalarCeilToScalar(tileHeight * fBaseFrequency.fY) / tileHeight;
+                // lowFrequency can be 0 if fBaseFrequency.fY is very small.
+                if (sk_ieee_float_divide(fBaseFrequency.fY, lowFrequency) <
+                    highFrequency / fBaseFrequency.fY) {
+                    fBaseFrequency.fY = lowFrequency;
+                } else {
+                    fBaseFrequency.fY = highFrequency;
+                }
+            }
+            fStitchDataInit =
+                    StitchData(tileWidth * fBaseFrequency.fX, tileHeight * fBaseFrequency.fY);
+        }
+
+    public:
+        const SkBitmap& getPermutationsBitmap() const {
+            SkASSERT(!fPermutationsBitmap.drawsNothing());
+            return fPermutationsBitmap;
+        }
+        const SkBitmap& getNoiseBitmap() const {
+            SkASSERT(!fNoiseBitmap.drawsNothing());
+            return fNoiseBitmap;
+        }
+    };  // struct PaintingData
+
+    /**
+     *  About the noise types : the difference between the first 2 is just minor tweaks to the
+     *  algorithm, they're not 2 entirely different noises. The output looks different, but once the
+     *  noise is generated in the [1, -1] range, the output is brought back in the [0, 1] range by
+     *  doing :
+     *  kFractalNoise_Type : noise * 0.5 + 0.5
+     *  kTurbulence_Type   : abs(noise)
+     *  Very little differs between the 2 types, although you can tell the difference visually.
+     */
+    enum Type { kFractalNoise_Type, kTurbulence_Type, kLast_Type = kTurbulence_Type };
+
+    static const int kMaxOctaves = 255;  // numOctaves must be <= 0 and <= kMaxOctaves
+
+    SkPerlinNoiseShader(SkPerlinNoiseShader::Type type,
+                        SkScalar baseFrequencyX,
+                        SkScalar baseFrequencyY,
+                        int numOctaves,
+                        SkScalar seed,
+                        const SkISize* tileSize);
+
+    ShaderType type() const override { return ShaderType::kPerlinNoise; }
+
+    class PerlinNoiseShaderContext : public Context {
+    public:
+        PerlinNoiseShaderContext(const SkPerlinNoiseShader& shader, const ContextRec&);
+
+        void shadeSpan(int x, int y, SkPMColor[], int count) override;
+
+    private:
+        SkPMColor shade(const SkPoint& point, StitchData& stitchData) const;
+        SkScalar calculateTurbulenceValueForPoint(int channel,
+                                                  StitchData& stitchData,
+                                                  const SkPoint& point) const;
+        SkScalar noise2D(int channel,
+                         const StitchData& stitchData,
+                         const SkPoint& noiseVector) const;
+
+        SkMatrix fMatrix;
+        PaintingData fPaintingData;
+    };
+
+#if defined(SK_GRAPHITE)
+    void addToKey(const skgpu::graphite::KeyContext&,
+                  skgpu::graphite::PaintParamsKeyBuilder*,
+                  skgpu::graphite::PipelineDataGatherer*) const override;
+#endif
+#if defined(SK_ENABLE_SKVM)
+    skvm::Color program(skvm::Builder*,
+                        skvm::Coord,
+                        skvm::Coord,
+                        skvm::Color,
+                        const SkShaders::MatrixRec&,
+                        const SkColorInfo&,
+                        skvm::Uniforms*,
+                        SkArenaAlloc*) const override {
+        // Unimplemented
+        return {};
+    }
+#endif
+
+    SkPerlinNoiseShader::Type noiseType() const { return fType; }
+    int numOctaves() const { return fNumOctaves; }
+    bool stitchTiles() const { return fStitchTiles; }
+    SkISize tileSize() const { return fTileSize; }
+
+    std::unique_ptr<PaintingData> getPaintingData(const SkMatrix& mat) const {
+        return std::make_unique<PaintingData>(
+                fTileSize, fSeed, fBaseFrequencyX, fBaseFrequencyY, mat);
+    }
+
+protected:
+    void flatten(SkWriteBuffer&) const override;
+#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
+    Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override;
+#endif
+
+private:
+    SK_FLATTENABLE_HOOKS(SkPerlinNoiseShader)
+
+    const SkPerlinNoiseShader::Type fType;
+    const SkScalar fBaseFrequencyX;
+    const SkScalar fBaseFrequencyY;
+    const int fNumOctaves;
+    const SkScalar fSeed;
+    const SkISize fTileSize;
+    const bool fStitchTiles;
+
+    friend void SkRegisterPerlinNoiseShaderFlattenable();
+};
+
+#endif
diff --git a/src/shaders/SkPictureShader.cpp b/src/shaders/SkPictureShader.cpp
index e0943bb..18945c4 100644
--- a/src/shaders/SkPictureShader.cpp
+++ b/src/shaders/SkPictureShader.cpp
@@ -7,40 +7,28 @@
 
 #include "src/shaders/SkPictureShader.h"
 
-#include "include/core/SkBitmap.h"
+#include "include/core/SkAlphaType.h"
 #include "include/core/SkCanvas.h"
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkColorType.h"
 #include "include/core/SkImage.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkSamplingOptions.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkShader.h"
+#include "include/core/SkSurface.h"
+#include "include/core/SkTileMode.h"
+#include "include/private/base/SkDebug.h"
 #include "src/base/SkArenaAlloc.h"
+#include "src/core/SkEffectPriv.h"
 #include "src/core/SkImageInfoPriv.h"
-#include "src/core/SkImagePriv.h"
 #include "src/core/SkMatrixPriv.h"
-#include "src/core/SkMatrixProvider.h"
-#include "src/core/SkMatrixUtils.h"
 #include "src/core/SkPicturePriv.h"
-#include "src/core/SkRasterPipeline.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkResourceCache.h"
-#include "src/core/SkVM.h"
-#include "src/shaders/SkBitmapProcShader.h"
-#include "src/shaders/SkImageShader.h"
+#include "src/core/SkWriteBuffer.h"
 #include "src/shaders/SkLocalMatrixShader.h"
 
-#if defined(SK_GANESH)
-#include "include/gpu/GrDirectContext.h"
-#include "include/gpu/GrRecordingContext.h"
-#include "include/gpu/ganesh/SkSurfaceGanesh.h"
-#include "src/gpu/ganesh/GrCaps.h"
-#include "src/gpu/ganesh/GrColorInfo.h"
-#include "src/gpu/ganesh/GrFPArgs.h"
-#include "src/gpu/ganesh/GrFragmentProcessor.h"
-#include "src/gpu/ganesh/GrRecordingContextPriv.h"
-#include "src/gpu/ganesh/SkGr.h"
-#include "src/gpu/ganesh/effects/GrTextureEffect.h"
-#include "src/gpu/ganesh/image/GrImageUtils.h"
-#include "src/image/SkImage_Base.h"
-#include "src/shaders/SkLocalMatrixShader.h"
-#endif
-
 #if defined(SK_GRAPHITE)
 #include "include/gpu/graphite/Surface.h"
 #include "src/gpu/graphite/Caps.h"
@@ -50,6 +38,12 @@
 #include "src/gpu/graphite/RecorderPriv.h"
 #endif
 
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+class SkDiscardableMemory;
+
 sk_sp<SkShader> SkPicture::makeShader(SkTileMode tmx, SkTileMode tmy, SkFilterMode filter,
                                       const SkMatrix* localMatrix, const SkRect* tile) const {
     if (localMatrix && !localMatrix->invert(nullptr)) {
@@ -191,87 +185,79 @@
     return cs ? sk_ref_sp(cs) : SkColorSpace::MakeSRGB();
 }
 
-struct CachedImageInfo {
-    bool           success;
-    SkSize         tileScale;      // Additional scale factors to apply when sampling image.
-    SkMatrix       matrixForDraw;  // Matrix used to produce an image from the picture
-    SkImageInfo    imageInfo;
-    SkSurfaceProps props;
+SkPictureShader::CachedImageInfo SkPictureShader::CachedImageInfo::Make(
+        const SkRect& bounds,
+        const SkMatrix& totalM,
+        SkColorType dstColorType,
+        SkColorSpace* dstColorSpace,
+        const int maxTextureSize,
+        const SkSurfaceProps& propsIn) {
+    SkSurfaceProps props = propsIn.cloneWithPixelGeometry(kUnknown_SkPixelGeometry);
 
-    static CachedImageInfo Make(const SkRect& bounds,
-                                const SkMatrix& totalM,
-                                SkColorType dstColorType,
-                                SkColorSpace* dstColorSpace,
-                                const int maxTextureSize,
-                                const SkSurfaceProps& propsIn) {
-        SkSurfaceProps props = propsIn.cloneWithPixelGeometry(kUnknown_SkPixelGeometry);
-
-        const SkSize scaledSize = [&]() {
-            SkSize size;
-            // Use a rotation-invariant scale
-            if (!totalM.decomposeScale(&size, nullptr)) {
-                SkPoint center = {bounds.centerX(), bounds.centerY()};
-                SkScalar area = SkMatrixPriv::DifferentialAreaScale(totalM, center);
-                if (!SkScalarIsFinite(area) || SkScalarNearlyZero(area)) {
-                    size = {1, 1}; // ill-conditioned matrix
-                } else {
-                    size.fWidth = size.fHeight = SkScalarSqrt(area);
-                }
+    const SkSize scaledSize = [&]() {
+        SkSize size;
+        // Use a rotation-invariant scale
+        if (!totalM.decomposeScale(&size, nullptr)) {
+            SkPoint center = {bounds.centerX(), bounds.centerY()};
+            SkScalar area = SkMatrixPriv::DifferentialAreaScale(totalM, center);
+            if (!SkScalarIsFinite(area) || SkScalarNearlyZero(area)) {
+                size = {1, 1};  // ill-conditioned matrix
+            } else {
+                size.fWidth = size.fHeight = SkScalarSqrt(area);
             }
-            size.fWidth  *= bounds.width();
-            size.fHeight *= bounds.height();
+        }
+        size.fWidth *= bounds.width();
+        size.fHeight *= bounds.height();
 
-            // Clamp the tile size to about 4M pixels
-            static const SkScalar kMaxTileArea = 2048 * 2048;
-            SkScalar tileArea = size.width() * size.height();
-            if (tileArea > kMaxTileArea) {
-                SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea);
-                size.set(size.width() * clampScale, size.height() * clampScale);
-            }
-
-            // Scale down the tile size if larger than maxTextureSize for GPU path
-            // or it should fail on create texture
-            if (maxTextureSize) {
-                if (size.width() > maxTextureSize || size.height() > maxTextureSize) {
-                    SkScalar downScale = maxTextureSize / std::max(size.width(),
-                                                                   size.height());
-                    size.set(SkScalarFloorToScalar(size.width() * downScale),
-                             SkScalarFloorToScalar(size.height() * downScale));
-                }
-            }
-            return size;
-        }();
-
-        const SkISize tileSize = scaledSize.toCeil();
-        if (tileSize.isEmpty()) {
-            return {false, {}, {}, {}, {}};
+        // Clamp the tile size to about 4M pixels
+        static const SkScalar kMaxTileArea = 2048 * 2048;
+        SkScalar tileArea = size.width() * size.height();
+        if (tileArea > kMaxTileArea) {
+            SkScalar clampScale = SkScalarSqrt(kMaxTileArea / tileArea);
+            size.set(size.width() * clampScale, size.height() * clampScale);
         }
 
-        const SkSize tileScale = {
-            tileSize.width() / bounds.width(), tileSize.height() / bounds.height()
-        };
-        auto imgCS = ref_or_srgb(dstColorSpace);
-        const SkColorType imgCT = SkColorTypeMaxBitsPerChannel(dstColorType) <= 8
-                                ? kRGBA_8888_SkColorType
-                                : kRGBA_F16Norm_SkColorType;
-
-        return {true,
-                tileScale,
-                SkMatrix::RectToRect(bounds, SkRect::MakeIWH(tileSize.width(), tileSize.height())),
-                SkImageInfo::Make(tileSize, imgCT, kPremul_SkAlphaType, imgCS),
-                props};
-    }
-
-    sk_sp<SkImage> makeImage(sk_sp<SkSurface> surf, const SkPicture* pict) const {
-        if (!surf) {
-            return nullptr;
+        // Scale down the tile size if larger than maxTextureSize for GPU path
+        // or it should fail on create texture
+        if (maxTextureSize) {
+            if (size.width() > maxTextureSize || size.height() > maxTextureSize) {
+                SkScalar downScale = maxTextureSize / std::max(size.width(), size.height());
+                size.set(SkScalarFloorToScalar(size.width() * downScale),
+                         SkScalarFloorToScalar(size.height() * downScale));
+            }
         }
-        auto canvas = surf->getCanvas();
-        canvas->concat(matrixForDraw);
-        canvas->drawPicture(pict);
-        return surf->makeImageSnapshot();
+        return size;
+    }();
+
+    const SkISize tileSize = scaledSize.toCeil();
+    if (tileSize.isEmpty()) {
+        return {false, {}, {}, {}, {}};
     }
-};
+
+    const SkSize tileScale = {tileSize.width() / bounds.width(),
+                              tileSize.height() / bounds.height()};
+    auto imgCS = ref_or_srgb(dstColorSpace);
+    const SkColorType imgCT = SkColorTypeMaxBitsPerChannel(dstColorType) <= 8
+                                      ? kRGBA_8888_SkColorType
+                                      : kRGBA_F16Norm_SkColorType;
+
+    return {true,
+            tileScale,
+            SkMatrix::RectToRect(bounds, SkRect::MakeIWH(tileSize.width(), tileSize.height())),
+            SkImageInfo::Make(tileSize, imgCT, kPremul_SkAlphaType, imgCS),
+            props};
+}
+
+sk_sp<SkImage> SkPictureShader::CachedImageInfo::makeImage(sk_sp<SkSurface> surf,
+                                                           const SkPicture* pict) const {
+    if (!surf) {
+        return nullptr;
+    }
+    auto canvas = surf->getCanvas();
+    canvas->concat(matrixForDraw);
+    canvas->drawPicture(pict);
+    return surf->makeImageSnapshot();
+}
 
 // Returns a cached image shader, which wraps a single picture tile at the given
 // CTM/local matrix.  Also adjusts the local matrix for tile scaling.
@@ -307,7 +293,7 @@
     return image->makeShader(fTmx, fTmy, SkSamplingOptions(fFilter), &lm);
 }
 
-bool SkPictureShader::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
+bool SkPictureShader::appendStages(const SkStageRec& rec, const SkShaders::MatrixRec& mRec) const {
     // Keep bitmapShader alive by using alloc instead of stack memory
     auto& bitmapShader = *rec.fAlloc->make<sk_sp<SkShader>>();
     // We don't check whether the total local matrix is valid here because we have to assume *some*
@@ -329,7 +315,7 @@
                                      skvm::Coord device,
                                      skvm::Coord local,
                                      skvm::Color paint,
-                                     const MatrixRec& mRec,
+                                     const SkShaders::MatrixRec& mRec,
                                      const SkColorInfo& dst,
                                      skvm::Uniforms* uniforms,
                                      SkArenaAlloc* alloc) const {
@@ -365,92 +351,6 @@
 }
 #endif
 
-/////////////////////////////////////////////////////////////////////////////////////////
-
-#if defined(SK_GANESH)
-
-#include "src/gpu/ganesh/GrProxyProvider.h"
-
-std::unique_ptr<GrFragmentProcessor> SkPictureShader::asFragmentProcessor(
-        const GrFPArgs& args, const MatrixRec& mRec) const {
-    auto ctx = args.fContext;
-    SkColorType dstColorType = GrColorTypeToSkColorType(args.fDstColorInfo->colorType());
-    if (dstColorType == kUnknown_SkColorType) {
-        dstColorType = kRGBA_8888_SkColorType;
-    }
-
-    auto dstCS = ref_or_srgb(args.fDstColorInfo->colorSpace());
-
-    auto info = CachedImageInfo::Make(fTile,
-                                      mRec.totalMatrix(),
-                                      dstColorType,
-                                      dstCS.get(),
-                                      ctx->priv().caps()->maxTextureSize(),
-                                      args.fSurfaceProps);
-    if (!info.success) {
-        return nullptr;
-    }
-
-    // Gotta be sure the GPU can support our requested colortype (might be FP16)
-    if (!ctx->colorTypeSupportedAsSurface(info.imageInfo.colorType())) {
-        info.imageInfo = info.imageInfo.makeColorType(kRGBA_8888_SkColorType);
-    }
-
-    static const skgpu::UniqueKey::Domain kDomain = skgpu::UniqueKey::GenerateDomain();
-    skgpu::UniqueKey key;
-    std::tuple keyData = {
-        dstCS->toXYZD50Hash(),
-        dstCS->transferFnHash(),
-        static_cast<uint32_t>(dstColorType),
-        fPicture->uniqueID(),
-        fTile,
-        info.tileScale,
-        info.props
-    };
-    skgpu::UniqueKey::Builder builder(&key, kDomain, sizeof(keyData)/sizeof(uint32_t),
-                                      "Picture Shader Image");
-    memcpy(&builder[0], &keyData, sizeof(keyData));
-    builder.finish();
-
-    GrProxyProvider* provider = ctx->priv().proxyProvider();
-    GrSurfaceProxyView view;
-    if (auto proxy = provider->findOrCreateProxyByUniqueKey(key)) {
-        view = GrSurfaceProxyView(proxy, kTopLeft_GrSurfaceOrigin, skgpu::Swizzle());
-    } else {
-        const int msaaSampleCount = 0;
-        const bool createWithMips = false;
-        auto image = info.makeImage(SkSurfaces::RenderTarget(ctx,
-                                                             skgpu::Budgeted::kYes,
-                                                             info.imageInfo,
-                                                             msaaSampleCount,
-                                                             kTopLeft_GrSurfaceOrigin,
-                                                             &info.props,
-                                                             createWithMips),
-                                    fPicture.get());
-        if (!image) {
-            return nullptr;
-        }
-
-        auto [v, ct] = skgpu::ganesh::AsView(ctx, image, GrMipmapped::kNo);
-        view = std::move(v);
-        provider->assignUniqueKeyToProxy(key, view.asTextureProxy());
-    }
-
-    const GrSamplerState sampler(static_cast<GrSamplerState::WrapMode>(fTmx),
-                                 static_cast<GrSamplerState::WrapMode>(fTmy),
-                                 fFilter);
-    auto fp = GrTextureEffect::Make(std::move(view),
-                                    kPremul_SkAlphaType,
-                                    SkMatrix::I(),
-                                    sampler,
-                                    *ctx->priv().caps());
-    SkMatrix scale = SkMatrix::Scale(info.tileScale.width(), info.tileScale.height());
-    bool success;
-    std::tie(success, fp) = mRec.apply(std::move(fp), scale);
-    return success ? std::move(fp) : nullptr;
-}
-#endif
-
 #if defined(SK_GRAPHITE)
 void SkPictureShader::addToKey(const skgpu::graphite::KeyContext& keyContext,
                                skgpu::graphite::PaintParamsKeyBuilder* builder,
diff --git a/src/shaders/SkPictureShader.h b/src/shaders/SkPictureShader.h
index f4c9f1a..bb810b0 100644
--- a/src/shaders/SkPictureShader.h
+++ b/src/shaders/SkPictureShader.h
@@ -8,13 +8,28 @@
 #ifndef SkPictureShader_DEFINED
 #define SkPictureShader_DEFINED
 
-#include "include/core/SkTileMode.h"
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkPicture.h"
+#include "include/core/SkRect.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkSize.h"
+#include "include/core/SkSurfaceProps.h"
+#include "include/core/SkTypes.h"
 #include "src/shaders/SkShaderBase.h"
-#include <atomic>
 
 class SkArenaAlloc;
-class SkBitmap;
-class SkPicture;
+class SkColorSpace;
+class SkImage;
+class SkReadBuffer;
+class SkShader;
+class SkSurface;
+class SkWriteBuffer;
+enum SkColorType : int;
+enum class SkFilterMode;
+enum class SkTileMode;
+struct SkStageRec;
 
 /*
  * An SkPictureShader can be used to draw SkPicture-based patterns.
@@ -27,10 +42,6 @@
     static sk_sp<SkShader> Make(sk_sp<SkPicture>, SkTileMode, SkTileMode, SkFilterMode,
                                 const SkMatrix*, const SkRect*);
 
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
 #if defined(SK_GRAPHITE)
     void addToKey(const skgpu::graphite::KeyContext&,
                   skgpu::graphite::PaintParamsKeyBuilder*,
@@ -39,16 +50,41 @@
 
     SkPictureShader(sk_sp<SkPicture>, SkTileMode, SkTileMode, SkFilterMode, const SkRect*);
 
+    ShaderType type() const override { return ShaderType::kPicture; }
+
+    sk_sp<SkPicture> picture() const { return fPicture; }
+    SkRect tile() const { return fTile; }
+    SkTileMode tileModeX() const { return fTmx; }
+    SkTileMode tileModeY() const { return fTmy; }
+    SkFilterMode filter() const { return fFilter; }
+
+    struct CachedImageInfo {
+        bool success;
+        SkSize tileScale;        // Additional scale factors to apply when sampling image.
+        SkMatrix matrixForDraw;  // Matrix used to produce an image from the picture
+        SkImageInfo imageInfo;
+        SkSurfaceProps props;
+
+        static CachedImageInfo Make(const SkRect& bounds,
+                                    const SkMatrix& totalM,
+                                    SkColorType dstColorType,
+                                    SkColorSpace* dstColorSpace,
+                                    const int maxTextureSize,
+                                    const SkSurfaceProps& propsIn);
+
+        sk_sp<SkImage> makeImage(sk_sp<SkSurface> surf, const SkPicture* pict) const;
+    };
+
 protected:
     SkPictureShader(SkReadBuffer&);
     void flatten(SkWriteBuffer&) const override;
-    bool appendStages(const SkStageRec&, const MatrixRec&) const override;
+    bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const override;
 #if defined(SK_ENABLE_SKVM)
     skvm::Color program(skvm::Builder*,
                         skvm::Coord device,
                         skvm::Coord local,
                         skvm::Color paint,
-                        const MatrixRec&,
+                        const SkShaders::MatrixRec&,
                         const SkColorInfo& dst,
                         skvm::Uniforms* uniforms,
                         SkArenaAlloc* alloc) const override;
@@ -68,9 +104,7 @@
     sk_sp<SkPicture>    fPicture;
     SkRect              fTile;
     SkTileMode          fTmx, fTmy;
-    SkFilterMode        fFilter;
-
-    using INHERITED = SkShaderBase;
+    SkFilterMode fFilter;
 };
 
 #endif // SkPictureShader_DEFINED
diff --git a/src/shaders/SkRuntimeShader.cpp b/src/shaders/SkRuntimeShader.cpp
new file mode 100644
index 0000000..cbaee17
--- /dev/null
+++ b/src/shaders/SkRuntimeShader.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "src/shaders/SkRuntimeShader.h"
+
+#include "include/core/SkCapabilities.h"
+#include "include/core/SkData.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkShader.h"
+#include "include/core/SkString.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/private/SkSLSampleUsage.h"
+#include "include/private/base/SkAssert.h"
+#include "include/private/base/SkDebug.h"
+#include "include/private/base/SkTArray.h"
+#include "include/sksl/SkSLDebugTrace.h"
+#include "src/base/SkTLazy.h"
+#include "src/core/SkEffectPriv.h"
+#include "src/core/SkPicturePriv.h"
+#include "src/core/SkReadBuffer.h"
+#include "src/core/SkRuntimeEffectPriv.h"
+#include "src/core/SkWriteBuffer.h"
+#include "src/shaders/SkShaderBase.h"
+#include "src/sksl/codegen/SkSLRasterPipelineBuilder.h"
+#include "src/sksl/tracing/SkSLDebugTracePriv.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyContext.h"
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif
+
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <utility>
+
+class SkColorSpace;
+struct SkIPoint;
+
+SkRuntimeShader::SkRuntimeShader(sk_sp<SkRuntimeEffect> effect,
+                                 sk_sp<SkSL::DebugTracePriv> debugTrace,
+                                 sk_sp<const SkData> uniforms,
+                                 SkSpan<SkRuntimeEffect::ChildPtr> children)
+        : fEffect(std::move(effect))
+        , fDebugTrace(std::move(debugTrace))
+        , fUniformData(std::move(uniforms))
+        , fChildren(children.begin(), children.end()) {}
+
+SkRuntimeShader::SkRuntimeShader(sk_sp<SkRuntimeEffect> effect,
+                                 sk_sp<SkSL::DebugTracePriv> debugTrace,
+                                 UniformsCallback uniformsCallback,
+                                 SkSpan<SkRuntimeEffect::ChildPtr> children)
+        : fEffect(std::move(effect))
+        , fDebugTrace(std::move(debugTrace))
+        , fUniformsCallback(std::move(uniformsCallback))
+        , fChildren(children.begin(), children.end()) {}
+
+static sk_sp<SkSL::DebugTracePriv> make_debug_trace(SkRuntimeEffect* effect,
+                                                    const SkIPoint& coord) {
+    auto debugTrace = sk_make_sp<SkSL::DebugTracePriv>();
+    debugTrace->setSource(effect->source());
+    debugTrace->setTraceCoord(coord);
+    return debugTrace;
+}
+
+SkRuntimeEffect::TracedShader SkRuntimeShader::makeTracedClone(const SkIPoint& coord) {
+    sk_sp<SkRuntimeEffect> unoptimized = fEffect->makeUnoptimizedClone();
+    sk_sp<SkSL::DebugTracePriv> debugTrace = make_debug_trace(unoptimized.get(), coord);
+    auto debugShader = sk_make_sp<SkRuntimeShader>(
+            unoptimized, debugTrace, this->uniformData(nullptr), SkSpan(fChildren));
+
+    return SkRuntimeEffect::TracedShader{std::move(debugShader), std::move(debugTrace)};
+}
+
+#if defined(SK_GRAPHITE)
+void SkRuntimeShader::addToKey(const skgpu::graphite::KeyContext& keyContext,
+                               skgpu::graphite::PaintParamsKeyBuilder* builder,
+                               skgpu::graphite::PipelineDataGatherer* gatherer) const {
+    using namespace skgpu::graphite;
+
+    sk_sp<const SkData> uniforms = SkRuntimeEffectPriv::TransformUniforms(
+            fEffect->uniforms(),
+            this->uniformData(keyContext.dstColorInfo().colorSpace()),
+            keyContext.dstColorInfo().colorSpace());
+    SkASSERT(uniforms);
+
+    RuntimeEffectBlock::BeginBlock(keyContext, builder, gatherer, {fEffect, std::move(uniforms)});
+
+    SkRuntimeEffectPriv::AddChildrenToKey(
+            fChildren, fEffect->children(), keyContext, builder, gatherer);
+
+    builder->endBlock();
+}
+#endif
+
+bool SkRuntimeShader::appendStages(const SkStageRec& rec, const SkShaders::MatrixRec& mRec) const {
+#ifdef SK_ENABLE_SKSL_IN_RASTER_PIPELINE
+    if (!SkRuntimeEffectPriv::CanDraw(SkCapabilities::RasterBackend().get(), fEffect.get())) {
+        // SkRP has support for many parts of #version 300 already, but for now, we restrict its
+        // usage in runtime effects to just #version 100.
+        return false;
+    }
+    if (const SkSL::RP::Program* program = fEffect->getRPProgram(fDebugTrace.get())) {
+        std::optional<SkShaders::MatrixRec> newMRec = mRec.apply(rec);
+        if (!newMRec.has_value()) {
+            return false;
+        }
+        SkSpan<const float> uniforms =
+                SkRuntimeEffectPriv::UniformsAsSpan(fEffect->uniforms(),
+                                                    this->uniformData(rec.fDstCS),
+                                                    /*alwaysCopyIntoAlloc=*/fUniformData == nullptr,
+                                                    rec.fDstCS,
+                                                    rec.fAlloc);
+        RuntimeEffectRPCallbacks callbacks(rec, *newMRec, fChildren, fEffect->fSampleUsages);
+        bool success = program->appendStages(rec.fPipeline, rec.fAlloc, &callbacks, uniforms);
+        return success;
+    }
+#endif
+    return false;
+}
+
+#if defined(SK_ENABLE_SKVM)
+skvm::Color SkRuntimeShader::program(skvm::Builder* p,
+                                     skvm::Coord device,
+                                     skvm::Coord local,
+                                     skvm::Color paint,
+                                     const SkShaders::MatrixRec& mRec,
+                                     const SkColorInfo& colorInfo,
+                                     skvm::Uniforms* uniforms,
+                                     SkArenaAlloc* alloc) const {
+    if (!SkRuntimeEffectPriv::CanDraw(SkCapabilities::RasterBackend().get(), fEffect.get())) {
+        return {};
+    }
+
+    sk_sp<const SkData> inputs = SkRuntimeEffectPriv::TransformUniforms(
+            fEffect->uniforms(), this->uniformData(colorInfo.colorSpace()), colorInfo.colorSpace());
+    SkASSERT(inputs);
+
+    // Ensure any pending transform is applied before running the runtime shader's code, which
+    // gets to use and manipulate the coordinates.
+    std::optional<SkShaders::MatrixRec> newMRec = mRec.apply(p, &local, uniforms);
+    if (!newMRec.has_value()) {
+        return {};
+    }
+    // We could omit this for children that are only sampled with passthrough coords.
+    newMRec->markTotalMatrixInvalid();
+
+    RuntimeEffectVMCallbacks callbacks(p, uniforms, alloc, fChildren, *newMRec, paint, colorInfo);
+    std::vector<skvm::Val> uniform =
+            SkRuntimeEffectPriv::MakeSkVMUniforms(p, uniforms, fEffect->uniformSize(), *inputs);
+
+    return SkSL::ProgramToSkVM(*fEffect->fBaseProgram,
+                               fEffect->fMain,
+                               p,
+                               fDebugTrace.get(),
+                               SkSpan(uniform),
+                               device,
+                               local,
+                               paint,
+                               paint,
+                               &callbacks);
+}
+#endif
+
+void SkRuntimeShader::flatten(SkWriteBuffer& buffer) const {
+    buffer.writeString(fEffect->source().c_str());
+    buffer.writeDataAsByteArray(this->uniformData(nullptr).get());
+    SkRuntimeEffectPriv::WriteChildEffects(buffer, fChildren);
+}
+
+sk_sp<const SkData> SkRuntimeShader::uniformData(const SkColorSpace* dstCS) const {
+    if (fUniformData) {
+        return fUniformData;
+    }
+
+    // We want to invoke the uniforms-callback each time a paint occurs.
+    SkASSERT(fUniformsCallback);
+    sk_sp<const SkData> uniforms = fUniformsCallback({dstCS});
+    SkASSERT(uniforms && uniforms->size() == fEffect->uniformSize());
+    return uniforms;
+}
+
+sk_sp<SkFlattenable> SkRuntimeShader::CreateProc(SkReadBuffer& buffer) {
+    if (!buffer.validate(buffer.allowSkSL())) {
+        return nullptr;
+    }
+
+    SkString sksl;
+    buffer.readString(&sksl);
+    sk_sp<SkData> uniforms = buffer.readByteArrayAsData();
+
+    SkTLazy<SkMatrix> localM;
+    if (buffer.isVersionLT(SkPicturePriv::kNoShaderLocalMatrix)) {
+        uint32_t flags = buffer.read32();
+        if (flags & kHasLegacyLocalMatrix_Flag) {
+            buffer.readMatrix(localM.init());
+        }
+    }
+
+    auto effect = SkMakeCachedRuntimeEffect(SkRuntimeEffect::MakeForShader, std::move(sksl));
+#if !SK_LENIENT_SKSL_DESERIALIZATION
+    if (!buffer.validate(effect != nullptr)) {
+        return nullptr;
+    }
+#endif
+
+    skia_private::STArray<4, SkRuntimeEffect::ChildPtr> children;
+    if (!SkRuntimeEffectPriv::ReadChildEffects(buffer, effect.get(), &children)) {
+        return nullptr;
+    }
+
+#if SK_LENIENT_SKSL_DESERIALIZATION
+    if (!effect) {
+        // If any children were SkShaders, return the first one. This is a reasonable fallback.
+        for (int i = 0; i < children.size(); i++) {
+            if (children[i].shader()) {
+                SkDebugf("Serialized SkSL failed to compile. Replacing shader with child %d.\n", i);
+                return sk_ref_sp(children[i].shader());
+            }
+        }
+
+        // We don't know what to do, so just return nullptr (but *don't* poison the buffer).
+        SkDebugf("Serialized SkSL failed to compile. Ignoring/dropping SkSL shader.\n");
+        return nullptr;
+    }
+#endif
+
+    return effect->makeShader(std::move(uniforms), SkSpan(children), localM.getMaybeNull());
+}
diff --git a/src/shaders/SkRuntimeShader.h b/src/shaders/SkRuntimeShader.h
new file mode 100644
index 0000000..9a8372b
--- /dev/null
+++ b/src/shaders/SkRuntimeShader.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkRuntimeShader_DEFINED
+#define SkRuntimeShader_DEFINED
+
+#include "include/core/SkData.h"
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkSpan.h"
+#include "include/effects/SkRuntimeEffect.h"
+#include "include/private/base/SkDebug.h"
+#include "src/core/SkRuntimeEffectPriv.h"
+#include "src/shaders/SkShaderBase.h"
+#include "src/sksl/tracing/SkSLDebugTracePriv.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyContext.h"
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif
+
+#include <vector>
+
+class SkColorSpace;
+class SkReadBuffer;
+class SkWriteBuffer;
+struct SkIPoint;
+struct SkStageRec;
+
+using UniformsCallback = SkRuntimeEffectPriv::UniformsCallback;
+
+class SkRuntimeShader : public SkShaderBase {
+public:
+    SkRuntimeShader(sk_sp<SkRuntimeEffect> effect,
+                    sk_sp<SkSL::DebugTracePriv> debugTrace,
+                    sk_sp<const SkData> uniforms,
+                    SkSpan<SkRuntimeEffect::ChildPtr> children);
+
+    SkRuntimeShader(sk_sp<SkRuntimeEffect> effect,
+                    sk_sp<SkSL::DebugTracePriv> debugTrace,
+                    UniformsCallback uniformsCallback,
+                    SkSpan<SkRuntimeEffect::ChildPtr> children);
+
+    SkRuntimeEffect::TracedShader makeTracedClone(const SkIPoint& coord);
+
+    bool isOpaque() const override { return fEffect->alwaysOpaque(); }
+
+    ShaderType type() const override { return ShaderType::kRuntime; }
+
+#if defined(SK_GRAPHITE)
+    void addToKey(const skgpu::graphite::KeyContext& keyContext,
+                  skgpu::graphite::PaintParamsKeyBuilder* builder,
+                  skgpu::graphite::PipelineDataGatherer* gatherer) const override;
+#endif
+
+    bool appendStages(const SkStageRec& rec, const SkShaders::MatrixRec& mRec) const override;
+
+#if defined(SK_ENABLE_SKVM)
+    skvm::Color program(skvm::Builder* p,
+                        skvm::Coord device,
+                        skvm::Coord local,
+                        skvm::Color paint,
+                        const SkShaders::MatrixRec& mRec,
+                        const SkColorInfo& colorInfo,
+                        skvm::Uniforms* uniforms,
+                        SkArenaAlloc* alloc) const override;
+#endif
+
+    void flatten(SkWriteBuffer& buffer) const override;
+
+    SkRuntimeEffect* asRuntimeEffect() const override { return fEffect.get(); }
+
+    sk_sp<SkRuntimeEffect> effect() const { return fEffect; }
+    std::vector<SkRuntimeEffect::ChildPtr> children() const { return fChildren; }
+
+    sk_sp<const SkData> uniformData(const SkColorSpace* dstCS) const;
+
+    SK_FLATTENABLE_HOOKS(SkRuntimeShader)
+
+private:
+    enum Flags {
+        kHasLegacyLocalMatrix_Flag = 1 << 1,
+    };
+
+    sk_sp<SkRuntimeEffect> fEffect;
+    sk_sp<SkSL::DebugTracePriv> fDebugTrace;
+    sk_sp<const SkData> fUniformData;
+    UniformsCallback fUniformsCallback;
+    std::vector<SkRuntimeEffect::ChildPtr> fChildren;
+};
+
+#endif
diff --git a/src/shaders/SkShader.cpp b/src/shaders/SkShader.cpp
index f6ed560..80caa89 100644
--- a/src/shaders/SkShader.cpp
+++ b/src/shaders/SkShader.cpp
@@ -4,335 +4,49 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+#include "include/core/SkShader.h"
 
-#include "include/core/SkMallocPixelRef.h"
-#include "include/core/SkPaint.h"
-#include "include/core/SkScalar.h"
-#include "src/base/SkArenaAlloc.h"
-#include "src/base/SkTLazy.h"
-#include "src/core/SkColorSpacePriv.h"
-#include "src/core/SkColorSpaceXformSteps.h"
-#include "src/core/SkMatrixProvider.h"
-#include "src/core/SkRasterPipeline.h"
-#include "src/core/SkReadBuffer.h"
-#include "src/core/SkWriteBuffer.h"
-#include "src/shaders/SkBitmapProcShader.h"
-#include "src/shaders/SkImageShader.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkRefCnt.h"
+#include "src/shaders/SkColorFilterShader.h"
+#include "src/shaders/SkLocalMatrixShader.h"
 #include "src/shaders/SkShaderBase.h"
-#include "src/shaders/SkTransformShader.h"
 
-#if defined(SK_GANESH)
-#include "src/gpu/ganesh/GrFragmentProcessor.h"
-#include "src/gpu/ganesh/effects/GrMatrixEffect.h"
-#endif
+#include <utility>
 
-#if defined(SK_GRAPHITE)
-#include "src/gpu/graphite/KeyHelpers.h"
-#include "src/gpu/graphite/PaintParamsKey.h"
-#endif
-
-SkShaderBase::SkShaderBase() = default;
-
-SkShaderBase::~SkShaderBase() = default;
-
-SkShaderBase::MatrixRec::MatrixRec(const SkMatrix& ctm) : fCTM(ctm) {}
-
-std::optional<SkShaderBase::MatrixRec>
-SkShaderBase::MatrixRec::apply(const SkStageRec& rec, const SkMatrix& postInv) const {
-    SkMatrix total = fPendingLocalMatrix;
-    if (!fCTMApplied) {
-        total = SkMatrix::Concat(fCTM, total);
-    }
-    if (!total.invert(&total)) {
-        return {};
-    }
-    total = SkMatrix::Concat(postInv, total);
-    if (!fCTMApplied) {
-        rec.fPipeline->append(SkRasterPipelineOp::seed_shader);
-    }
-    // append_matrix is a no-op if total worked out to identity.
-    rec.fPipeline->append_matrix(rec.fAlloc, total);
-    return MatrixRec{fCTM,
-                     fTotalLocalMatrix,
-                     /*pendingLocalMatrix=*/SkMatrix::I(),
-                     fTotalMatrixIsValid,
-                     /*ctmApplied=*/true};
-}
-
-#if defined(SK_ENABLE_SKVM)
-std::optional<SkShaderBase::MatrixRec>
-SkShaderBase::MatrixRec::apply(skvm::Builder* p,
-                               skvm::Coord* local,
-                               skvm::Uniforms* uniforms,
-                               const SkMatrix& postInv) const {
-    SkMatrix total = fPendingLocalMatrix;
-    if (!fCTMApplied) {
-        total = SkMatrix::Concat(fCTM, total);
-    }
-    if (!total.invert(&total)) {
-        return {};
-    }
-    total = SkMatrix::Concat(postInv, total);
-    // ApplyMatrix is a no-op if total worked out to identity.
-    *local = SkShaderBase::ApplyMatrix(p, total, *local, uniforms);
-    return MatrixRec{fCTM,
-                     fTotalLocalMatrix,
-                     /*pendingLocalMatrix=*/SkMatrix::I(),
-                     fTotalMatrixIsValid,
-                     /*ctmApplied=*/true};
-}
-#endif
-#if defined(SK_GANESH)
-GrFPResult SkShaderBase::MatrixRec::apply(std::unique_ptr<GrFragmentProcessor> fp,
-                                          const SkMatrix& postInv) const {
-    // FP matrices work differently than SkRasterPipeline and SkVM. The starting coordinates
-    // provided to the root SkShader's FP are already in local space. So we never apply the inverse
-    // CTM.
-    SkASSERT(!fCTMApplied);
-    SkMatrix total;
-    if (!fPendingLocalMatrix.invert(&total)) {
-        return {false, std::move(fp)};
-    }
-    total = SkMatrix::Concat(postInv, total);
-    // GrMatrixEffect returns 'fp' if total worked out to identity.
-    return {true, GrMatrixEffect::Make(total, std::move(fp))};
-}
-
-SkShaderBase::MatrixRec SkShaderBase::MatrixRec::applied() const {
-    // We mark the CTM as "not applied" because we *never* apply the CTM for FPs. Their starting
-    // coords are local, not device, coords.
-    return MatrixRec{fCTM,
-                     fTotalLocalMatrix,
-                     /*pendingLocalMatrix=*/SkMatrix::I(),
-                     fTotalMatrixIsValid,
-                     /*ctmApplied=*/false};
-}
-#endif
-
-SkShaderBase::MatrixRec SkShaderBase::MatrixRec::concat(const SkMatrix& m) const {
-    return {fCTM,
-            SkShaderBase::ConcatLocalMatrices(fTotalLocalMatrix, m),
-            SkShaderBase::ConcatLocalMatrices(fPendingLocalMatrix, m),
-            fTotalMatrixIsValid,
-            fCTMApplied};
-}
-
-void SkShaderBase::flatten(SkWriteBuffer& buffer) const { this->INHERITED::flatten(buffer); }
-
-bool SkShaderBase::computeTotalInverse(const SkMatrix& ctm,
-                                       const SkMatrix* localMatrix,
-                                       SkMatrix* totalInverse) const {
-    return (localMatrix ? SkMatrix::Concat(ctm, *localMatrix) : ctm).invert(totalInverse);
-}
-
-bool SkShaderBase::asLuminanceColor(SkColor* colorPtr) const {
-    SkColor storage;
-    if (nullptr == colorPtr) {
-        colorPtr = &storage;
-    }
-    if (this->onAsLuminanceColor(colorPtr)) {
-        *colorPtr = SkColorSetA(*colorPtr, 0xFF);   // we only return opaque
-        return true;
-    }
-    return false;
-}
-
-SkShaderBase::Context* SkShaderBase::makeContext(const ContextRec& rec, SkArenaAlloc* alloc) const {
-#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
-    // We always fall back to raster pipeline when perspective is present.
-    if (rec.fMatrix->hasPerspective() || (rec.fLocalMatrix && rec.fLocalMatrix->hasPerspective()) ||
-        !this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, nullptr)) {
-        return nullptr;
-    }
-
-    return this->onMakeContext(rec, alloc);
-#else
-    return nullptr;
-#endif
-}
-
-SkShaderBase::Context::Context(const SkShaderBase& shader, const ContextRec& rec)
-    : fShader(shader), fCTM(*rec.fMatrix)
-{
-    // We should never use a context with perspective.
-    SkASSERT(!rec.fMatrix->hasPerspective());
-    SkASSERT(!rec.fLocalMatrix || !rec.fLocalMatrix->hasPerspective());
-
-    // Because the context parameters must be valid at this point, we know that the matrix is
-    // invertible.
-    SkAssertResult(fShader.computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &fTotalInverse));
-
-    fPaintAlpha = rec.fPaintAlpha;
-}
-
-SkShaderBase::Context::~Context() {}
-
-bool SkShaderBase::ContextRec::isLegacyCompatible(SkColorSpace* shaderColorSpace) const {
-    // In legacy pipelines, shaders always produce premul (or opaque) and the destination is also
-    // always premul (or opaque).  (And those "or opaque" caveats won't make any difference here.)
-    SkAlphaType shaderAT = kPremul_SkAlphaType,
-                   dstAT = kPremul_SkAlphaType;
-    return 0 == SkColorSpaceXformSteps{shaderColorSpace, shaderAT,
-                                         fDstColorSpace,    dstAT}.flags.mask();
-}
+class SkColorFilter;
+class SkImage;
+enum class SkTileMode;
 
 SkImage* SkShader::isAImage(SkMatrix* localMatrix, SkTileMode xy[2]) const {
     return as_SB(this)->onIsAImage(localMatrix, xy);
 }
 
-#if defined(SK_GANESH)
-std::unique_ptr<GrFragmentProcessor>
-SkShaderBase::asRootFragmentProcessor(const GrFPArgs& args, const SkMatrix& ctm) const {
-    return this->asFragmentProcessor(args, MatrixRec(ctm));
-}
-
-std::unique_ptr<GrFragmentProcessor> SkShaderBase::asFragmentProcessor(const GrFPArgs&,
-                                                                       const MatrixRec&) const {
-    return nullptr;
-}
-#endif
-
-sk_sp<SkShader> SkShaderBase::makeAsALocalMatrixShader(SkMatrix*) const {
-    return nullptr;
-}
-
-#if defined(SK_GRAPHITE)
-// TODO: add implementations for derived classes
-void SkShaderBase::addToKey(const skgpu::graphite::KeyContext& keyContext,
-                            skgpu::graphite::PaintParamsKeyBuilder* builder,
-                            skgpu::graphite::PipelineDataGatherer* gatherer) const {
-    using namespace skgpu::graphite;
-
-    SolidColorShaderBlock::BeginBlock(keyContext, builder, gatherer, {1, 0, 0, 1});
-    builder->endBlock();
-}
-#endif
-
-bool SkShaderBase::appendRootStages(const SkStageRec& rec, const SkMatrix& ctm) const {
-    return this->appendStages(rec, MatrixRec(ctm));
-}
-
-bool SkShaderBase::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
-    // SkShader::Context::shadeSpan() handles the paint opacity internally,
-    // but SkRasterPipelineBlitter applies it as a separate stage.
-    // We skip the internal shadeSpan() step by forcing the paint opaque.
-    SkColor4f opaquePaintColor = rec.fPaintColor.makeOpaque();
-
-    // We don't have a separate ctm and local matrix at this point. Just pass the combined matrix
-    // as the CTM. TODO: thread the MatrixRec through the legacy context system.
-    auto tm = mRec.totalMatrix();
-    ContextRec cr(opaquePaintColor,
-                  tm,
-                  nullptr,
-                  rec.fDstColorType,
-                  sk_srgb_singleton(),
-                  rec.fSurfaceProps);
-
-    struct CallbackCtx : SkRasterPipeline_CallbackCtx {
-        sk_sp<const SkShader> shader;
-        Context*              ctx;
-    };
-    auto cb = rec.fAlloc->make<CallbackCtx>();
-    cb->shader = sk_ref_sp(this);
-    cb->ctx = as_SB(this)->makeContext(cr, rec.fAlloc);
-    cb->fn  = [](SkRasterPipeline_CallbackCtx* self, int active_pixels) {
-        auto c = (CallbackCtx*)self;
-        int x = (int)c->rgba[0],
-            y = (int)c->rgba[1];
-        SkPMColor tmp[SkRasterPipeline_kMaxStride_highp];
-        c->ctx->shadeSpan(x,y, tmp, active_pixels);
-
-        for (int i = 0; i < active_pixels; i++) {
-            auto rgba_4f = SkPMColor4f::FromPMColor(tmp[i]);
-            memcpy(c->rgba + 4*i, rgba_4f.vec(), 4*sizeof(float));
-        }
-    };
-
-    if (cb->ctx) {
-        rec.fPipeline->append(SkRasterPipelineOp::seed_shader);
-        rec.fPipeline->append(SkRasterPipelineOp::callback, cb);
-        rec.fAlloc->make<SkColorSpaceXformSteps>(sk_srgb_singleton(), kPremul_SkAlphaType,
-                                                 rec.fDstCS,          kPremul_SkAlphaType)
-            ->apply(rec.fPipeline);
-        return true;
+sk_sp<SkShader> SkShader::makeWithLocalMatrix(const SkMatrix& localMatrix) const {
+    if (localMatrix.isIdentity()) {
+        return sk_ref_sp(const_cast<SkShader*>(this));
     }
-    return false;
-}
 
-#if defined(SK_ENABLE_SKVM)
-skvm::Color SkShaderBase::rootProgram(skvm::Builder* p,
-                                      skvm::Coord device,
-                                      skvm::Color paint,
-                                      const SkMatrix& ctm,
-                                      const SkColorInfo& dst,
-                                      skvm::Uniforms* uniforms,
-                                      SkArenaAlloc* alloc) const {
-    // Shader subclasses should always act as if the destination were premul or opaque.
-    // SkVMBlitter handles all the coordination of unpremul itself, via premul.
-    SkColorInfo tweaked = dst.alphaType() == kUnpremul_SkAlphaType
-                           ? dst.makeAlphaType(kPremul_SkAlphaType)
-                           : dst;
+    const SkMatrix* lm = &localMatrix;
 
-    // Force opaque alpha for all opaque shaders.
-    //
-    // This is primarily nice in that we usually have a 1.0f constant splat
-    // somewhere in the program anyway, and this will let us drop the work the
-    // shader notionally does to produce alpha, p->extract(...), etc. in favor
-    // of that simple hoistable splat.
-    //
-    // More subtly, it makes isOpaque() a parameter to all shader program
-    // generation, guaranteeing that is-opaque bit is mixed into the overall
-    // shader program hash and blitter Key.  This makes it safe for us to use
-    // that bit to make decisions when constructing an SkVMBlitter, like doing
-    // SrcOver -> Src strength reduction.
-    if (auto color = this->program(p,
-                                   device,
-                                   /*local=*/device,
-                                   paint,
-                                   MatrixRec(ctm),
-                                   tweaked,
-                                   uniforms,
-                                   alloc)) {
-        if (this->isOpaque()) {
-            color.a = p->splat(1.0f);
-        }
-        return color;
+    sk_sp<SkShader> baseShader;
+    SkMatrix otherLocalMatrix;
+    sk_sp<SkShader> proxy = as_SB(this)->makeAsALocalMatrixShader(&otherLocalMatrix);
+    if (proxy) {
+        otherLocalMatrix = SkShaderBase::ConcatLocalMatrices(localMatrix, otherLocalMatrix);
+        lm = &otherLocalMatrix;
+        baseShader = proxy;
+    } else {
+        baseShader = sk_ref_sp(const_cast<SkShader*>(this));
     }
-    return {};
-}
-#endif  // defined(SK_ENABLE_SKVM)
 
-// need a cheap way to invert the alpha channel of a shader (i.e. 1 - a)
-sk_sp<SkShader> SkShaderBase::makeInvertAlpha() const {
-    return this->makeWithColorFilter(SkColorFilters::Blend(0xFFFFFFFF, SkBlendMode::kSrcOut));
+    return sk_make_sp<SkLocalMatrixShader>(std::move(baseShader), *lm);
 }
 
-#if defined(SK_ENABLE_SKVM)
-skvm::Coord SkShaderBase::ApplyMatrix(skvm::Builder* p, const SkMatrix& m,
-                                      skvm::Coord coord, skvm::Uniforms* uniforms) {
-    skvm::F32 x = coord.x,
-              y = coord.y;
-    if (m.isIdentity()) {
-        // That was easy.
-    } else if (m.isTranslate()) {
-        x = p->add(x, p->uniformF(uniforms->pushF(m[2])));
-        y = p->add(y, p->uniformF(uniforms->pushF(m[5])));
-    } else if (m.isScaleTranslate()) {
-        x = p->mad(x, p->uniformF(uniforms->pushF(m[0])), p->uniformF(uniforms->pushF(m[2])));
-        y = p->mad(y, p->uniformF(uniforms->pushF(m[4])), p->uniformF(uniforms->pushF(m[5])));
-    } else {  // Affine or perspective.
-        auto dot = [&,x,y](int row) {
-            return p->mad(x, p->uniformF(uniforms->pushF(m[3*row+0])),
-                   p->mad(y, p->uniformF(uniforms->pushF(m[3*row+1])),
-                             p->uniformF(uniforms->pushF(m[3*row+2]))));
-        };
-        x = dot(0);
-        y = dot(1);
-        if (m.hasPerspective()) {
-            x = x * (1.0f / dot(2));
-            y = y * (1.0f / dot(2));
-        }
+sk_sp<SkShader> SkShader::makeWithColorFilter(sk_sp<SkColorFilter> filter) const {
+    SkShader* base = const_cast<SkShader*>(this);
+    if (!filter) {
+        return sk_ref_sp(base);
     }
-    return {x,y};
+    return sk_make_sp<SkColorFilterShader>(sk_ref_sp(base), 1.0f, std::move(filter));
 }
-#endif  // defined(SK_ENABLE_SKVM)
diff --git a/src/shaders/SkShaderBase.cpp b/src/shaders/SkShaderBase.cpp
new file mode 100644
index 0000000..b571223
--- /dev/null
+++ b/src/shaders/SkShaderBase.cpp
@@ -0,0 +1,318 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/shaders/SkShaderBase.h"
+
+#include "include/core/SkAlphaType.h"
+#include "include/core/SkBlendMode.h"
+#include "include/core/SkColorFilter.h"
+#include "include/private/SkColorData.h"
+#include "src/base/SkArenaAlloc.h"
+#include "src/core/SkColorSpacePriv.h"
+#include "src/core/SkColorSpaceXformSteps.h"
+#include "src/core/SkEffectPriv.h"
+#include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpContexts.h"
+#include "src/core/SkRasterPipelineOpList.h"
+#include "src/shaders/SkLocalMatrixShader.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif
+
+#include <cstring>
+
+class SkWriteBuffer;
+
+namespace SkShaders {
+MatrixRec::MatrixRec(const SkMatrix& ctm) : fCTM(ctm) {}
+
+std::optional<MatrixRec> MatrixRec::apply(const SkStageRec& rec, const SkMatrix& postInv) const {
+    SkMatrix total = fPendingLocalMatrix;
+    if (!fCTMApplied) {
+        total = SkMatrix::Concat(fCTM, total);
+    }
+    if (!total.invert(&total)) {
+        return {};
+    }
+    total = SkMatrix::Concat(postInv, total);
+    if (!fCTMApplied) {
+        rec.fPipeline->append(SkRasterPipelineOp::seed_shader);
+    }
+    // append_matrix is a no-op if total worked out to identity.
+    rec.fPipeline->append_matrix(rec.fAlloc, total);
+    return MatrixRec{fCTM,
+                     fTotalLocalMatrix,
+                     /*pendingLocalMatrix=*/SkMatrix::I(),
+                     fTotalMatrixIsValid,
+                     /*ctmApplied=*/true};
+}
+
+#if defined(SK_ENABLE_SKVM)
+std::optional<MatrixRec> MatrixRec::apply(skvm::Builder* p,
+                                          skvm::Coord* local,
+                                          skvm::Uniforms* uniforms,
+                                          const SkMatrix& postInv) const {
+    SkMatrix total = fPendingLocalMatrix;
+    if (!fCTMApplied) {
+        total = SkMatrix::Concat(fCTM, total);
+    }
+    if (!total.invert(&total)) {
+        return {};
+    }
+    total = SkMatrix::Concat(postInv, total);
+    // ApplyMatrix is a no-op if total worked out to identity.
+    *local = SkShaderBase::ApplyMatrix(p, total, *local, uniforms);
+    return MatrixRec{fCTM,
+                     fTotalLocalMatrix,
+                     /*pendingLocalMatrix=*/SkMatrix::I(),
+                     fTotalMatrixIsValid,
+                     /*ctmApplied=*/true};
+}
+#endif
+
+std::tuple<SkMatrix, bool> MatrixRec::applyForFragmentProcessor(const SkMatrix& postInv) const {
+    SkASSERT(!fCTMApplied);
+    SkMatrix total;
+    if (!fPendingLocalMatrix.invert(&total)) {
+        return {SkMatrix::I(), false};
+    }
+    return {SkMatrix::Concat(postInv, total), true};
+}
+
+MatrixRec MatrixRec::applied() const {
+    // We mark the CTM as "not applied" because we *never* apply the CTM for FPs. Their starting
+    // coords are local, not device, coords.
+    return MatrixRec{fCTM,
+                     fTotalLocalMatrix,
+                     /*pendingLocalMatrix=*/SkMatrix::I(),
+                     fTotalMatrixIsValid,
+                     /*ctmApplied=*/false};
+}
+
+MatrixRec MatrixRec::concat(const SkMatrix& m) const {
+    return {fCTM,
+            SkShaderBase::ConcatLocalMatrices(fTotalLocalMatrix, m),
+            SkShaderBase::ConcatLocalMatrices(fPendingLocalMatrix, m),
+            fTotalMatrixIsValid,
+            fCTMApplied};
+}
+
+}  // namespace SkShaders
+
+///////////////////////////////////////////////////////////////////////////////////////
+
+SkShaderBase::SkShaderBase() = default;
+
+SkShaderBase::~SkShaderBase() = default;
+
+void SkShaderBase::flatten(SkWriteBuffer& buffer) const { this->INHERITED::flatten(buffer); }
+
+bool SkShaderBase::computeTotalInverse(const SkMatrix& ctm,
+                                       const SkMatrix* localMatrix,
+                                       SkMatrix* totalInverse) const {
+    return (localMatrix ? SkMatrix::Concat(ctm, *localMatrix) : ctm).invert(totalInverse);
+}
+
+bool SkShaderBase::asLuminanceColor(SkColor* colorPtr) const {
+    SkColor storage;
+    if (nullptr == colorPtr) {
+        colorPtr = &storage;
+    }
+    if (this->onAsLuminanceColor(colorPtr)) {
+        *colorPtr = SkColorSetA(*colorPtr, 0xFF);  // we only return opaque
+        return true;
+    }
+    return false;
+}
+
+SkShaderBase::Context* SkShaderBase::makeContext(const ContextRec& rec, SkArenaAlloc* alloc) const {
+#ifdef SK_ENABLE_LEGACY_SHADERCONTEXT
+    // We always fall back to raster pipeline when perspective is present.
+    if (rec.fMatrix->hasPerspective() || (rec.fLocalMatrix && rec.fLocalMatrix->hasPerspective()) ||
+        !this->computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, nullptr)) {
+        return nullptr;
+    }
+
+    return this->onMakeContext(rec, alloc);
+#else
+    return nullptr;
+#endif
+}
+
+SkShaderBase::Context::Context(const SkShaderBase& shader, const ContextRec& rec)
+        : fShader(shader), fCTM(*rec.fMatrix) {
+    // We should never use a context with perspective.
+    SkASSERT(!rec.fMatrix->hasPerspective());
+    SkASSERT(!rec.fLocalMatrix || !rec.fLocalMatrix->hasPerspective());
+
+    // Because the context parameters must be valid at this point, we know that the matrix is
+    // invertible.
+    SkAssertResult(fShader.computeTotalInverse(*rec.fMatrix, rec.fLocalMatrix, &fTotalInverse));
+
+    fPaintAlpha = rec.fPaintAlpha;
+}
+
+SkShaderBase::Context::~Context() {}
+
+bool SkShaderBase::ContextRec::isLegacyCompatible(SkColorSpace* shaderColorSpace) const {
+    // In legacy pipelines, shaders always produce premul (or opaque) and the destination is also
+    // always premul (or opaque).  (And those "or opaque" caveats won't make any difference here.)
+    SkAlphaType shaderAT = kPremul_SkAlphaType, dstAT = kPremul_SkAlphaType;
+    return 0 ==
+           SkColorSpaceXformSteps{shaderColorSpace, shaderAT, fDstColorSpace, dstAT}.flags.mask();
+}
+
+sk_sp<SkShader> SkShaderBase::makeAsALocalMatrixShader(SkMatrix*) const { return nullptr; }
+
+#if defined(SK_GRAPHITE)
+// TODO: add implementations for derived classes
+void SkShaderBase::addToKey(const skgpu::graphite::KeyContext& keyContext,
+                            skgpu::graphite::PaintParamsKeyBuilder* builder,
+                            skgpu::graphite::PipelineDataGatherer* gatherer) const {
+    using namespace skgpu::graphite;
+
+    SolidColorShaderBlock::BeginBlock(keyContext, builder, gatherer, {1, 0, 0, 1});
+    builder->endBlock();
+}
+#endif
+
+bool SkShaderBase::appendRootStages(const SkStageRec& rec, const SkMatrix& ctm) const {
+    return this->appendStages(rec, SkShaders::MatrixRec(ctm));
+}
+
+bool SkShaderBase::appendStages(const SkStageRec& rec, const SkShaders::MatrixRec& mRec) const {
+    // SkShader::Context::shadeSpan() handles the paint opacity internally,
+    // but SkRasterPipelineBlitter applies it as a separate stage.
+    // We skip the internal shadeSpan() step by forcing the paint opaque.
+    SkColor4f opaquePaintColor = rec.fPaintColor.makeOpaque();
+
+    // We don't have a separate ctm and local matrix at this point. Just pass the combined matrix
+    // as the CTM. TODO: thread the MatrixRec through the legacy context system.
+    auto tm = mRec.totalMatrix();
+    ContextRec cr(opaquePaintColor,
+                  tm,
+                  nullptr,
+                  rec.fDstColorType,
+                  sk_srgb_singleton(),
+                  rec.fSurfaceProps);
+
+    struct CallbackCtx : SkRasterPipeline_CallbackCtx {
+        sk_sp<const SkShader> shader;
+        Context* ctx;
+    };
+    auto cb = rec.fAlloc->make<CallbackCtx>();
+    cb->shader = sk_ref_sp(this);
+    cb->ctx = as_SB(this)->makeContext(cr, rec.fAlloc);
+    cb->fn = [](SkRasterPipeline_CallbackCtx* self, int active_pixels) {
+        auto c = (CallbackCtx*)self;
+        int x = (int)c->rgba[0], y = (int)c->rgba[1];
+        SkPMColor tmp[SkRasterPipeline_kMaxStride_highp];
+        c->ctx->shadeSpan(x, y, tmp, active_pixels);
+
+        for (int i = 0; i < active_pixels; i++) {
+            auto rgba_4f = SkPMColor4f::FromPMColor(tmp[i]);
+            memcpy(c->rgba + 4 * i, rgba_4f.vec(), 4 * sizeof(float));
+        }
+    };
+
+    if (cb->ctx) {
+        rec.fPipeline->append(SkRasterPipelineOp::seed_shader);
+        rec.fPipeline->append(SkRasterPipelineOp::callback, cb);
+        rec.fAlloc
+                ->make<SkColorSpaceXformSteps>(
+                        sk_srgb_singleton(), kPremul_SkAlphaType, rec.fDstCS, kPremul_SkAlphaType)
+                ->apply(rec.fPipeline);
+        return true;
+    }
+    return false;
+}
+
+sk_sp<SkShader> SkShaderBase::makeWithCTM(const SkMatrix& postM) const {
+    return sk_sp<SkShader>(new SkCTMShader(sk_ref_sp(this), postM));
+}
+
+#if defined(SK_ENABLE_SKVM)
+skvm::Color SkShaderBase::rootProgram(skvm::Builder* p,
+                                      skvm::Coord device,
+                                      skvm::Color paint,
+                                      const SkMatrix& ctm,
+                                      const SkColorInfo& dst,
+                                      skvm::Uniforms* uniforms,
+                                      SkArenaAlloc* alloc) const {
+    // Shader subclasses should always act as if the destination were premul or opaque.
+    // SkVMBlitter handles all the coordination of unpremul itself, via premul.
+    SkColorInfo tweaked =
+            dst.alphaType() == kUnpremul_SkAlphaType ? dst.makeAlphaType(kPremul_SkAlphaType) : dst;
+
+    // Force opaque alpha for all opaque shaders.
+    //
+    // This is primarily nice in that we usually have a 1.0f constant splat
+    // somewhere in the program anyway, and this will let us drop the work the
+    // shader notionally does to produce alpha, p->extract(...), etc. in favor
+    // of that simple hoistable splat.
+    //
+    // More subtly, it makes isOpaque() a parameter to all shader program
+    // generation, guaranteeing that is-opaque bit is mixed into the overall
+    // shader program hash and blitter Key.  This makes it safe for us to use
+    // that bit to make decisions when constructing an SkVMBlitter, like doing
+    // SrcOver -> Src strength reduction.
+    if (auto color = this->program(p,
+                                   device,
+                                   /*local=*/device,
+                                   paint,
+                                   SkShaders::MatrixRec(ctm),
+                                   tweaked,
+                                   uniforms,
+                                   alloc)) {
+        if (this->isOpaque()) {
+            color.a = p->splat(1.0f);
+        }
+        return color;
+    }
+    return {};
+}
+#endif  // defined(SK_ENABLE_SKVM)
+
+// need a cheap way to invert the alpha channel of a shader (i.e. 1 - a)
+sk_sp<SkShader> SkShaderBase::makeInvertAlpha() const {
+    return this->makeWithColorFilter(SkColorFilters::Blend(0xFFFFFFFF, SkBlendMode::kSrcOut));
+}
+
+#if defined(SK_ENABLE_SKVM)
+skvm::Coord SkShaderBase::ApplyMatrix(skvm::Builder* p,
+                                      const SkMatrix& m,
+                                      skvm::Coord coord,
+                                      skvm::Uniforms* uniforms) {
+    skvm::F32 x = coord.x, y = coord.y;
+    if (m.isIdentity()) {
+        // That was easy.
+    } else if (m.isTranslate()) {
+        x = p->add(x, p->uniformF(uniforms->pushF(m[2])));
+        y = p->add(y, p->uniformF(uniforms->pushF(m[5])));
+    } else if (m.isScaleTranslate()) {
+        x = p->mad(x, p->uniformF(uniforms->pushF(m[0])), p->uniformF(uniforms->pushF(m[2])));
+        y = p->mad(y, p->uniformF(uniforms->pushF(m[4])), p->uniformF(uniforms->pushF(m[5])));
+    } else {  // Affine or perspective.
+        auto dot = [&, x, y](int row) {
+            return p->mad(x,
+                          p->uniformF(uniforms->pushF(m[3 * row + 0])),
+                          p->mad(y,
+                                 p->uniformF(uniforms->pushF(m[3 * row + 1])),
+                                 p->uniformF(uniforms->pushF(m[3 * row + 2]))));
+        };
+        x = dot(0);
+        y = dot(1);
+        if (m.hasPerspective()) {
+            x = x * (1.0f / dot(2));
+            y = y * (1.0f / dot(2));
+        }
+    }
+    return {x, y};
+}
+#endif  // defined(SK_ENABLE_SKVM)
diff --git a/src/shaders/SkShaderBase.h b/src/shaders/SkShaderBase.h
index c0f76fd..c7ecb18 100644
--- a/src/shaders/SkShaderBase.h
+++ b/src/shaders/SkShaderBase.h
@@ -9,42 +9,204 @@
 #define SkShaderBase_DEFINED
 
 #include "include/core/SkColor.h"
-#include "include/core/SkImageInfo.h"
+#include "include/core/SkFlattenable.h"
 #include "include/core/SkMatrix.h"
-#include "include/core/SkPaint.h"
-#include "include/core/SkSamplingOptions.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkScalar.h"
 #include "include/core/SkShader.h"
 #include "include/core/SkSurfaceProps.h"
+#include "include/core/SkTypes.h"
 #include "include/private/base/SkNoncopyable.h"
-#include "src/base/SkTLazy.h"
-#include "src/core/SkEffectPriv.h"
-#include "src/core/SkMask.h"
-#include "src/core/SkVM_fwd.h"
 
+#include <cstddef>
+#include <cstdint>
+#include <optional>
 #include <tuple>
 
-class GrFragmentProcessor;
-struct GrFPArgs;
 class SkArenaAlloc;
 class SkColorSpace;
 class SkImage;
-struct SkImageInfo;
-class SkPaint;
-class SkRasterPipeline;
 class SkRuntimeEffect;
-class SkStageUpdater;
-class SkUpdatableShader;
+class SkWriteBuffer;
+enum SkColorType : int;
+enum class SkTileMode;
+struct SkDeserialProcs;
+struct SkStageRec;
 
+#if defined(SK_GRAPHITE)
 namespace skgpu::graphite {
 class KeyContext;
 class PaintParamsKeyBuilder;
 class PipelineDataGatherer;
 }
-
-#if defined(SK_GANESH)
-using GrFPResult = std::tuple<bool /*success*/, std::unique_ptr<GrFragmentProcessor>>;
 #endif
 
+#if defined(SK_ENABLE_SKVM)
+#include "include/core/SkImageInfo.h"
+#include "src/core/SkVM.h"
+#endif
+
+namespace SkShaders {
+/**
+ * This is used to accumulate matrices, starting with the CTM, when building up
+ * SkRasterPipeline, SkVM, and GrFragmentProcessor by walking the SkShader tree. It avoids
+ * adding a matrix multiply for each individual matrix. It also handles the reverse matrix
+ * concatenation order required by Android Framework, see b/256873449.
+ *
+ * This also tracks the dubious concept of a "total matrix", which includes all the matrices
+ * encountered during traversal to the current shader, including ones that have already been
+ * applied. The total matrix represents the transformation from the current shader's coordinate
+ * space to device space. It is dubious because it doesn't account for SkShaders that manipulate
+ * the coordinates passed to their children, which may not even be representable by a matrix.
+ *
+ * The total matrix is used for mipmap level selection and a filter downgrade optimizations in
+ * SkImageShader and sizing of the SkImage created by SkPictureShader. If we can remove usages
+ * of the "total matrix" and if Android Framework could be updated to not use backwards local
+ * matrix concatenation this could just be replaced by a simple SkMatrix or SkM44 passed down
+ * during traversal.
+ */
+class MatrixRec {
+public:
+    MatrixRec() = default;
+
+    explicit MatrixRec(const SkMatrix& ctm);
+
+    /**
+     * Returns a new MatrixRec that represents the existing total and pending matrix
+     * pre-concat'ed with m.
+     */
+    MatrixRec SK_WARN_UNUSED_RESULT concat(const SkMatrix& m) const;
+
+    /**
+     * Appends a mul by the inverse of the pending local matrix to the pipeline. 'postInv' is an
+     * additional matrix to post-apply to the inverted pending matrix. If the pending matrix is
+     * not invertible the std::optional result won't have a value and the pipeline will be
+     * unmodified.
+     */
+    std::optional<MatrixRec> SK_WARN_UNUSED_RESULT apply(const SkStageRec& rec,
+                                                         const SkMatrix& postInv = {}) const;
+
+#if defined(SK_ENABLE_SKVM)
+    /**
+     * Muls local by the inverse of the pending matrix. 'postInv' is an additional matrix to
+     * post-apply to the inverted pending matrix. If the pending matrix is not invertible the
+     * std::optional result won't have a value and the Builder will be unmodified.
+     */
+    std::optional<MatrixRec> SK_WARN_UNUSED_RESULT apply(skvm::Builder*,
+                                                         skvm::Coord* local,  // inout
+                                                         skvm::Uniforms*,
+                                                         const SkMatrix& postInv = {}) const;
+#endif
+
+    /**
+     * FP matrices work differently than SkRasterPipeline and SkVM. The starting coordinates
+     * provided to the root SkShader's FP are already in local space. So we never apply the inverse
+     * CTM. This returns the inverted pending local matrix with the provided postInv matrix
+     * applied after it. If the pending local matrix cannot be inverted, the boolean is false.
+     */
+    std::tuple<SkMatrix, bool> applyForFragmentProcessor(const SkMatrix& postInv) const;
+
+    /**
+     * A parent FP may need to create a FP for its child by calling
+     * SkShaderBase::asFragmentProcessor() and then pass the result to the apply() above.
+     * This comes up when the parent needs to ensure pending matrices are applied before the
+     * child because the parent is going to manipulate the coordinates *after* any pending
+     * matrix and pass the resulting coords to the child. This function gets a MatrixRec that
+     * reflects the state after this MatrixRec has bee applied but it does not apply it!
+     * Example:
+     * auto childFP = fChild->asFragmentProcessor(args, mrec.applied());
+     * childFP = MakeAWrappingFPThatModifiesChildsCoords(std::move(childFP));
+     * auto [success, parentFP] = mrec.apply(std::move(childFP));
+     */
+    MatrixRec applied() const;
+
+    /** Call to indicate that the mapping from shader to device space is not known. */
+    void markTotalMatrixInvalid() { fTotalMatrixIsValid = false; }
+
+    /** Marks the CTM as already applied; can avoid re-seeding the shader unnecessarily. */
+    void markCTMApplied() { fCTMApplied = true; }
+
+    /**
+     * Indicates whether the total matrix of a MatrixRec passed to a SkShader actually
+     * represents the full transform between that shader's coordinate space and device space.
+     */
+    bool totalMatrixIsValid() const { return fTotalMatrixIsValid; }
+
+    /**
+     * Gets the total transform from the current shader's space to device space. This may or
+     * may not be valid. Shaders should avoid making decisions based on this matrix if
+     * totalMatrixIsValid() is false.
+     */
+    SkMatrix totalMatrix() const { return SkMatrix::Concat(fCTM, fTotalLocalMatrix); }
+
+    /** Gets the inverse of totalMatrix(), if invertible. */
+    bool SK_WARN_UNUSED_RESULT totalInverse(SkMatrix* out) const {
+        return this->totalMatrix().invert(out);
+    }
+
+    /** Is there a transform that has not yet been applied by a parent shader? */
+    bool hasPendingMatrix() const {
+        return (!fCTMApplied && !fCTM.isIdentity()) || !fPendingLocalMatrix.isIdentity();
+    }
+
+    /** When generating raster pipeline, have the device coordinates been seeded? */
+    bool rasterPipelineCoordsAreSeeded() const { return fCTMApplied; }
+
+private:
+    MatrixRec(const SkMatrix& ctm,
+              const SkMatrix& totalLocalMatrix,
+              const SkMatrix& pendingLocalMatrix,
+              bool totalIsValid,
+              bool ctmApplied)
+            : fCTM(ctm)
+            , fTotalLocalMatrix(totalLocalMatrix)
+            , fPendingLocalMatrix(pendingLocalMatrix)
+            , fTotalMatrixIsValid(totalIsValid)
+            , fCTMApplied(ctmApplied) {}
+
+    const SkMatrix fCTM;
+
+    // Concatenation of all local matrices, including those already applied.
+    const SkMatrix fTotalLocalMatrix;
+
+    // The accumulated local matrices from walking down the shader hierarchy that have NOT yet
+    // been incorporated into the SkRasterPipeline.
+    const SkMatrix fPendingLocalMatrix;
+
+    bool fTotalMatrixIsValid = true;
+
+    // Tracks whether the CTM has already been applied (and in raster pipeline whether the
+    // device coords have been seeded.)
+    bool fCTMApplied = false;
+};
+
+}  // namespace SkShaders
+
+#define SK_ALL_SHADERS(M) \
+    M(Blend)              \
+    M(CTM)                \
+    M(Color)              \
+    M(Color4)             \
+    M(ColorFilter)        \
+    M(CoordClamp)         \
+    M(Empty)              \
+    M(GradientBase)       \
+    M(Image)              \
+    M(LocalMatrix)        \
+    M(PerlinNoise)        \
+    M(Picture)            \
+    M(Runtime)            \
+    M(Transform)          \
+    M(TriColor)           \
+    M(UpdatableColor)
+
+#define SK_ALL_GRADIENTS(M) \
+    M(Conical)              \
+    M(Linear)               \
+    M(Radial)               \
+    M(Sweep)
+
 class SkShaderBase : public SkShader {
 public:
     ~SkShaderBase() override;
@@ -58,13 +220,19 @@
      */
     virtual bool isConstant() const { return false; }
 
+    enum class ShaderType {
+#define M(type) k##type,
+        SK_ALL_SHADERS(M)
+#undef M
+    };
+
+    virtual ShaderType type() const = 0;
+
     enum class GradientType {
         kNone,
-        kColor,
-        kLinear,
-        kRadial,
-        kSweep,
-        kConical
+#define M(type) k##type,
+        SK_ALL_GRADIENTS(M)
+#undef M
     };
 
     /**
@@ -181,165 +349,12 @@
     };
 
     /**
-     * This is used to accumulate matrices, starting with the CTM, when building up
-     * SkRasterPipeline, SkVM, and GrFragmentProcessor by walking the SkShader tree. It avoids
-     * adding a matrix multiply for each individual matrix. It also handles the reverse matrix
-     * concatenation order required by Android Framework, see b/256873449.
-     *
-     * This also tracks the dubious concept of a "total matrix", which includes all the matrices
-     * encountered during traversal to the current shader, including ones that have already been
-     * applied. The total matrix represents the transformation from the current shader's coordinate
-     * space to device space. It is dubious because it doesn't account for SkShaders that manipulate
-     * the coordinates passed to their children, which may not even be representable by a matrix.
-     *
-     * The total matrix is used for mipmap level selection and a filter downgrade optimizations in
-     * SkImageShader and sizing of the SkImage created by SkPictureShader. If we can remove usages
-     * of the "total matrix" and if Android Framework could be updated to not use backwards local
-     * matrix concatenation this could just be replaced by a simple SkMatrix or SkM44 passed down
-     * during traversal.
-     */
-    class MatrixRec {
-    public:
-        MatrixRec() = default;
-
-        explicit MatrixRec(const SkMatrix& ctm);
-
-        /**
-         * Returns a new MatrixRec that represents the existing total and pending matrix
-         * pre-concat'ed with m.
-         */
-        MatrixRec SK_WARN_UNUSED_RESULT concat(const SkMatrix& m) const;
-
-        /**
-         * Appends a mul by the inverse of the pending local matrix to the pipeline. 'postInv' is an
-         * additional matrix to post-apply to the inverted pending matrix. If the pending matrix is
-         * not invertible the std::optional result won't have a value and the pipeline will be
-         * unmodified.
-         */
-        std::optional<MatrixRec> SK_WARN_UNUSED_RESULT apply(const SkStageRec& rec,
-                                                             const SkMatrix& postInv = {}) const;
-
-#if defined(SK_ENABLE_SKVM)
-        /**
-         * Muls local by the inverse of the pending matrix. 'postInv' is an additional matrix to
-         * post-apply to the inverted pending matrix. If the pending matrix is not invertible the
-         * std::optional result won't have a value and the Builder will be unmodified.
-         */
-        std::optional<MatrixRec> SK_WARN_UNUSED_RESULT apply(skvm::Builder*,
-                                                             skvm::Coord* local,  // inout
-                                                             skvm::Uniforms*,
-                                                             const SkMatrix& postInv = {}) const;
-#endif
-#if defined(SK_GANESH)
-        /**
-         * Produces an FP that muls its input coords by the inverse of the pending matrix and then
-         * samples the passed FP with those coordinates. 'postInv' is an additional matrix to
-         * post-apply to the inverted pending matrix. If the pending matrix is not invertible the
-         * GrFPResult's bool will be false and the passed FP will be returned to the caller in the
-         * GrFPResult.
-         */
-        GrFPResult SK_WARN_UNUSED_RESULT apply(std::unique_ptr<GrFragmentProcessor>,
-                                               const SkMatrix& postInv = {}) const;
-        /**
-         * A parent FP may need to create a FP for its child by calling
-         * SkShaderBase::asFragmentProcessor() and then pass the result to the apply() above.
-         * This comes up when the parent needs to ensure pending matrices are applied before the
-         * child because the parent is going to manipulate the coordinates *after* any pending
-         * matrix and pass the resulting coords to the child. This function gets a MatrixRec that
-         * reflects the state after this MatrixRec has bee applied but it does not apply it!
-         * Example:
-         * auto childFP = fChild->asFragmentProcessor(args, mrec.applied());
-         * childFP = MakeAWrappingFPThatModifiesChildsCoords(std::move(childFP));
-         * auto [success, parentFP] = mrec.apply(std::move(childFP));
-         */
-        MatrixRec applied() const;
-#endif
-
-        /** Call to indicate that the mapping from shader to device space is not known. */
-        void markTotalMatrixInvalid() { fTotalMatrixIsValid = false; }
-
-        /** Marks the CTM as already applied; can avoid re-seeding the shader unnecessarily. */
-        void markCTMApplied() { fCTMApplied = true; }
-
-        /**
-         * Indicates whether the total matrix of a MatrixRec passed to a SkShader actually
-         * represents the full transform between that shader's coordinate space and device space.
-         */
-        bool totalMatrixIsValid() const { return fTotalMatrixIsValid; }
-
-        /**
-         * Gets the total transform from the current shader's space to device space. This may or
-         * may not be valid. Shaders should avoid making decisions based on this matrix if
-         * totalMatrixIsValid() is false.
-         */
-        SkMatrix totalMatrix() const { return SkMatrix::Concat(fCTM, fTotalLocalMatrix); }
-
-        /** Gets the inverse of totalMatrix(), if invertible. */
-        bool SK_WARN_UNUSED_RESULT totalInverse(SkMatrix* out) const {
-            return this->totalMatrix().invert(out);
-        }
-
-        /** Is there a transform that has not yet been applied by a parent shader? */
-        bool hasPendingMatrix() const {
-            return (!fCTMApplied && !fCTM.isIdentity()) || !fPendingLocalMatrix.isIdentity();
-        }
-
-        /** When generating raster pipeline, have the device coordinates been seeded? */
-        bool rasterPipelineCoordsAreSeeded() const { return fCTMApplied; }
-
-    private:
-        MatrixRec(const SkMatrix& ctm,
-                  const SkMatrix& totalLocalMatrix,
-                  const SkMatrix& pendingLocalMatrix,
-                  bool totalIsValid,
-                  bool ctmApplied)
-                : fCTM(ctm)
-                , fTotalLocalMatrix(totalLocalMatrix)
-                , fPendingLocalMatrix(pendingLocalMatrix)
-                , fTotalMatrixIsValid(totalIsValid)
-                , fCTMApplied(ctmApplied) {}
-
-        const SkMatrix fCTM;
-
-        // Concatenation of all local matrices, including those already applied.
-        const SkMatrix fTotalLocalMatrix;
-
-        // The accumulated local matrices from walking down the shader hierarchy that have NOT yet
-        // been incorporated into the SkRasterPipeline.
-        const SkMatrix fPendingLocalMatrix;
-
-        bool fTotalMatrixIsValid = true;
-
-        // Tracks whether the CTM has already been applied (and in raster pipeline whether the
-        // device coords have been seeded.)
-        bool fCTMApplied = false;
-    };
-
-    /**
      * Make a context using the memory provided by the arena.
      *
      * @return pointer to context or nullptr if can't be created
      */
     Context* makeContext(const ContextRec&, SkArenaAlloc*) const;
 
-#if defined(SK_GANESH)
-    /**
-     * Call on the root SkShader to produce a GrFragmentProcessor.
-     *
-     * The returned GrFragmentProcessor expects an unpremultiplied input color and produces a
-     * premultiplied output.
-     */
-    std::unique_ptr<GrFragmentProcessor> asRootFragmentProcessor(const GrFPArgs&,
-                                                                 const SkMatrix& ctm) const;
-    /**
-     * Virtualized implementation of above. Any pending matrix in the MatrixRec should be applied
-     * to the coords if the SkShader uses its coordinates. This can be done by calling
-     * MatrixRec::apply() to wrap a GrFragmentProcessor in a GrMatrixEffect.
-     */
-    virtual std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                                     const MatrixRec&) const;
-#endif
-
     /**
      *  If the shader can represent its "average" luminance in a single color, return true and
      *  if color is not NULL, return that color. If it cannot, return false and ignore the color
@@ -363,7 +378,7 @@
      * in r,g MatrixRec::apply() must be called (unless the shader doesn't require it's input
      * coords). The default impl creates shadercontext and calls that (not very efficient).
      */
-    virtual bool appendStages(const SkStageRec&, const MatrixRec&) const;
+    virtual bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const;
 
     bool SK_WARN_UNUSED_RESULT computeTotalInverse(const SkMatrix& ctm,
                                                    const SkMatrix* localMatrix,
@@ -415,7 +430,7 @@
                                 skvm::Coord device,
                                 skvm::Coord local,
                                 skvm::Color paint,
-                                const MatrixRec&,
+                                const SkShaders::MatrixRec&,
                                 const SkColorInfo& dst,
                                 skvm::Uniforms*,
                                 SkArenaAlloc*) const = 0;
@@ -466,7 +481,7 @@
     static skvm::Coord ApplyMatrix(skvm::Builder*, const SkMatrix&, skvm::Coord, skvm::Uniforms*);
 #endif
 
-    using INHERITED = SkShader;
+    friend class SkShaders::MatrixRec;
 };
 inline SkShaderBase* as_SB(SkShader* shader) {
     return static_cast<SkShaderBase*>(shader);
@@ -480,9 +495,9 @@
     return static_cast<SkShaderBase*>(shader.get());
 }
 
+void SkRegisterBlendShaderFlattenable();
 void SkRegisterColor4ShaderFlattenable();
 void SkRegisterColorShaderFlattenable();
-void SkRegisterComposeShaderFlattenable();
 void SkRegisterCoordClampShaderFlattenable();
 void SkRegisterEmptyShaderFlattenable();
 void SkRegisterPerlinNoiseShaderFlattenable();
diff --git a/src/shaders/SkTransformShader.cpp b/src/shaders/SkTransformShader.cpp
index afe52cd..8ea3cd6 100644
--- a/src/shaders/SkTransformShader.cpp
+++ b/src/shaders/SkTransformShader.cpp
@@ -5,10 +5,19 @@
  * found in the LICENSE file.
  */
 
-#include "src/core/SkMatrixProvider.h"
-#include "src/core/SkRasterPipeline.h"
 #include "src/shaders/SkTransformShader.h"
 
+#include "include/core/SkMatrix.h"
+#include "src/core/SkEffectPriv.h"
+#include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpList.h"
+
+#if defined(SK_ENABLE_SKVM)
+#include "src/core/SkVM.h"
+#endif
+
+#include <optional>
+
 SkTransformShader::SkTransformShader(const SkShaderBase& shader, bool allowPerspective)
         : fShader{shader}, fAllowPerspective{allowPerspective} {
     SkMatrix::I().get9(fMatrixStorage);
@@ -19,7 +28,7 @@
                                        skvm::Coord device,
                                        skvm::Coord local,
                                        skvm::Color color,
-                                       const MatrixRec& mRec,
+                                       const SkShaders::MatrixRec& mRec,
                                        const SkColorInfo& dst,
                                        skvm::Uniforms* uniforms,
                                        SkArenaAlloc* alloc) const {
@@ -31,13 +40,13 @@
     // optimization opportunity, not a correctness bug.
     SkASSERT(!mRec.hasPendingMatrix());
 
-    std::optional<MatrixRec> childMRec = mRec.apply(b, &local, uniforms);
+    std::optional<SkShaders::MatrixRec> childMRec = mRec.apply(b, &local, uniforms);
     if (!childMRec.has_value()) {
         return {};
     }
     // The matrix we're about to insert gets updated between uses of the VM so our children can't
     // know the total transform when they add their stages. We don't incorporate this shader's
-    // matrix into the MatrixRec at all.
+    // matrix into the SkShaders::MatrixRec at all.
     childMRec->markTotalMatrixInvalid();
 
     auto matrix = uniforms->pushPtr(&fMatrixStorage);
@@ -75,7 +84,8 @@
     return false;
 }
 
-bool SkTransformShader::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
+bool SkTransformShader::appendStages(const SkStageRec& rec,
+                                     const SkShaders::MatrixRec& mRec) const {
     // We have to seed and apply any constant matrices before appending our matrix that may
     // mutate. We could try to add one matrix stage and then incorporate the parent matrix
     // with the variable matrix in each call to update(). However, in practice our callers
@@ -83,13 +93,13 @@
     // shaders so the call to apply below should just seed the coordinates. If this assert fires
     // it just indicates an optimization opportunity, not a correctness bug.
     SkASSERT(!mRec.hasPendingMatrix());
-    std::optional<MatrixRec> childMRec = mRec.apply(rec);
+    std::optional<SkShaders::MatrixRec> childMRec = mRec.apply(rec);
     if (!childMRec.has_value()) {
         return false;
     }
     // The matrix we're about to insert gets updated between uses of the pipeline so our children
     // can't know the total transform when they add their stages. We don't even incorporate this
-    // matrix into the MatrixRec at all.
+    // matrix into the SkShaders::MatrixRec at all.
     childMRec->markTotalMatrixInvalid();
 
     auto type = fAllowPerspective ? SkRasterPipelineOp::matrix_perspective
diff --git a/src/shaders/SkTransformShader.h b/src/shaders/SkTransformShader.h
index f536cf4..ea08bda 100644
--- a/src/shaders/SkTransformShader.h
+++ b/src/shaders/SkTransformShader.h
@@ -7,9 +7,13 @@
 #ifndef SkTextCoordShader_DEFINED
 #define SkTextCoordShader_DEFINED
 
-#include "src/core/SkVM.h"
+#include "include/core/SkScalar.h"
+#include "include/private/base/SkAssert.h"
 #include "src/shaders/SkShaderBase.h"
 
+class SkMatrix;
+struct SkStageRec;
+
 // SkTransformShader applies a matrix transform to the shader coordinates, like a local matrix
 // shader. The difference with a typical local matrix shader is that this shader's matrix is
 // not combined with the inverse CTM or other local matrices in order to facilitate modifying the
@@ -28,7 +32,7 @@
                         skvm::Coord device,
                         skvm::Coord local,
                         skvm::Color color,
-                        const MatrixRec& mRec,
+                        const SkShaders::MatrixRec& mRec,
                         const SkColorInfo& dst,
                         skvm::Uniforms* uniforms,
                         SkArenaAlloc* alloc) const override;
@@ -36,11 +40,13 @@
 
     // Adds a pipestage to multiply the incoming coords in 'r' and 'g' by the matrix. The child
     // shader is called with no pending local matrix and the total transform as unknowable.
-    bool appendStages(const SkStageRec& rec, const MatrixRec&) const override;
+    bool appendStages(const SkStageRec& rec, const SkShaders::MatrixRec&) const override;
 
     // Change the matrix used by the generated SkRasterpipeline or SkVM.
     bool update(const SkMatrix& matrix);
 
+    ShaderType type() const override { return ShaderType::kTransform; }
+
     // These are never serialized/deserialized
     Factory getFactory() const override {
         SkDEBUGFAIL("SkTransformShader shouldn't be serialized.");
diff --git a/src/shaders/SkTriColorShader.cpp b/src/shaders/SkTriColorShader.cpp
new file mode 100644
index 0000000..9e4e5ee
--- /dev/null
+++ b/src/shaders/SkTriColorShader.cpp
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/shaders/SkTriColorShader.h"
+
+#include "include/core/SkMatrix.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkColorData.h"
+#include "src/base/SkVx.h"
+#include "src/core/SkEffectPriv.h"
+#include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpList.h"
+
+#if defined(SK_ENABLE_SKVM)
+#include "src/core/SkVM.h"
+#endif
+
+bool SkTriColorShader::appendStages(const SkStageRec& rec, const SkShaders::MatrixRec&) const {
+    rec.fPipeline->append(SkRasterPipelineOp::seed_shader);
+    if (fUsePersp) {
+        rec.fPipeline->append(SkRasterPipelineOp::matrix_perspective, &fM33);
+    }
+    rec.fPipeline->append(SkRasterPipelineOp::matrix_4x3, &fM43);
+    return true;
+}
+
+#if defined(SK_ENABLE_SKVM)
+skvm::Color SkTriColorShader::program(skvm::Builder* b,
+                                      skvm::Coord device,
+                                      skvm::Coord local,
+                                      skvm::Color,
+                                      const SkShaders::MatrixRec&,
+                                      const SkColorInfo&,
+                                      skvm::Uniforms* uniforms,
+                                      SkArenaAlloc* alloc) const {
+    fColorMatrix = uniforms->pushPtr(&fM43);
+
+    skvm::F32 x = local.x, y = local.y;
+
+    if (fUsePersp) {
+        fCoordMatrix = uniforms->pushPtr(&fM33);
+        auto dot = [&, x, y](int row) {
+            return b->mad(x, b->arrayF(fCoordMatrix, row),
+                             b->mad(y, b->arrayF(fCoordMatrix, row + 3),
+                                       b->arrayF(fCoordMatrix, row + 6)));
+        };
+
+        x = dot(0);
+        y = dot(1);
+        x = x * (1.0f / dot(2));
+        y = y * (1.0f / dot(2));
+    }
+
+    auto colorDot = [&, x, y](int row) {
+        return b->mad(x, b->arrayF(fColorMatrix, row),
+                         b->mad(y, b->arrayF(fColorMatrix, row + 4),
+                                   b->arrayF(fColorMatrix, row + 8)));
+    };
+
+    skvm::Color color;
+    color.r = colorDot(0);
+    color.g = colorDot(1);
+    color.b = colorDot(2);
+    color.a = colorDot(3);
+    return color;
+}
+#endif
+
+bool SkTriColorShader::update(const SkMatrix& ctmInv,
+                              const SkPoint pts[],
+                              const SkPMColor4f colors[],
+                              int index0,
+                              int index1,
+                              int index2) {
+    SkMatrix m, im;
+    m.reset();
+    m.set(0, pts[index1].fX - pts[index0].fX);
+    m.set(1, pts[index2].fX - pts[index0].fX);
+    m.set(2, pts[index0].fX);
+    m.set(3, pts[index1].fY - pts[index0].fY);
+    m.set(4, pts[index2].fY - pts[index0].fY);
+    m.set(5, pts[index0].fY);
+    if (!m.invert(&im)) {
+        return false;
+    }
+
+    fM33.setConcat(im, ctmInv);
+
+    auto c0 = skvx::float4::Load(colors[index0].vec()),
+         c1 = skvx::float4::Load(colors[index1].vec()),
+         c2 = skvx::float4::Load(colors[index2].vec());
+
+    (c1 - c0).store(&fM43.fMat[0]);
+    (c2 - c0).store(&fM43.fMat[4]);
+    c0.store(&fM43.fMat[8]);
+
+    if (!fUsePersp) {
+        fM43.setConcat(fM43, fM33);
+    }
+    return true;
+}
diff --git a/src/shaders/SkTriColorShader.h b/src/shaders/SkTriColorShader.h
new file mode 100644
index 0000000..38ad997
--- /dev/null
+++ b/src/shaders/SkTriColorShader.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkTriColorShader_DEFINED
+#define SkTriColorShader_DEFINED
+
+#include "include/core/SkMatrix.h"
+#include "include/core/SkTypes.h"
+#include "include/private/SkColorData.h"
+#include "src/shaders/SkShaderBase.h"
+
+struct SkPoint;
+struct SkStageRec;
+
+class SkTriColorShader : public SkShaderBase {
+public:
+    SkTriColorShader(bool isOpaque, bool usePersp) : fIsOpaque(isOpaque), fUsePersp(usePersp) {}
+
+    ShaderType type() const override { return ShaderType::kTriColor; }
+
+    // This gets called for each triangle, without re-calling appendStages.
+    bool update(const SkMatrix& ctmInv,
+                const SkPoint pts[],
+                const SkPMColor4f colors[],
+                int index0,
+                int index1,
+                int index2);
+
+protected:
+    bool appendStages(const SkStageRec& rec, const SkShaders::MatrixRec&) const override;
+
+#if defined(SK_ENABLE_SKVM)
+    skvm::Color program(skvm::Builder*,
+                        skvm::Coord,
+                        skvm::Coord,
+                        skvm::Color,
+                        const SkShaders::MatrixRec&,
+                        const SkColorInfo&,
+                        skvm::Uniforms*,
+                        SkArenaAlloc*) const override;
+#endif
+
+private:
+    bool isOpaque() const override { return fIsOpaque; }
+    // For serialization.  This will never be called.
+    Factory getFactory() const override { return nullptr; }
+    const char* getTypeName() const override { return nullptr; }
+
+    struct Matrix43 {
+        float fMat[12];  // column major
+
+        // Pass a by value, so we don't have to worry about aliasing with this
+        void setConcat(const Matrix43 a, const SkMatrix& b) {
+            SkASSERT(!b.hasPerspective());
+
+            fMat[0] = a.dot(0, b.getScaleX(), b.getSkewY());
+            fMat[1] = a.dot(1, b.getScaleX(), b.getSkewY());
+            fMat[2] = a.dot(2, b.getScaleX(), b.getSkewY());
+            fMat[3] = a.dot(3, b.getScaleX(), b.getSkewY());
+
+            fMat[4] = a.dot(0, b.getSkewX(), b.getScaleY());
+            fMat[5] = a.dot(1, b.getSkewX(), b.getScaleY());
+            fMat[6] = a.dot(2, b.getSkewX(), b.getScaleY());
+            fMat[7] = a.dot(3, b.getSkewX(), b.getScaleY());
+
+            fMat[8] = a.dot(0, b.getTranslateX(), b.getTranslateY()) + a.fMat[8];
+            fMat[9] = a.dot(1, b.getTranslateX(), b.getTranslateY()) + a.fMat[9];
+            fMat[10] = a.dot(2, b.getTranslateX(), b.getTranslateY()) + a.fMat[10];
+            fMat[11] = a.dot(3, b.getTranslateX(), b.getTranslateY()) + a.fMat[11];
+        }
+
+    private:
+        float dot(int index, float x, float y) const {
+            return fMat[index + 0] * x + fMat[index + 4] * y;
+        }
+    };
+
+    // If fUsePersp, we need both of these matrices,
+    // otherwise we can combine them, and only use fM43
+
+    Matrix43 fM43;
+    SkMatrix fM33;
+    const bool fIsOpaque;
+    const bool fUsePersp;  // controls our stages, and what we do in update()
+#if defined(SK_ENABLE_SKVM)
+    mutable skvm::Uniform fColorMatrix;
+    mutable skvm::Uniform fCoordMatrix;
+#endif
+};
+
+#endif
diff --git a/src/shaders/gradients/BUILD.bazel b/src/shaders/gradients/BUILD.bazel
index 604026d..c03aeb6 100644
--- a/src/shaders/gradients/BUILD.bazel
+++ b/src/shaders/gradients/BUILD.bazel
@@ -5,14 +5,16 @@
 exports_files_legacy()
 
 GRADIENT_FILES = [
-    "SkGradientShader.cpp",
-    "SkGradientShaderBase.cpp",
-    "SkGradientShaderBase.h",
+    "SkConicalGradient.cpp",
+    "SkConicalGradient.h",
+    "SkGradientBaseShader.cpp",
+    "SkGradientBaseShader.h",
     "SkLinearGradient.cpp",
     "SkLinearGradient.h",
     "SkRadialGradient.cpp",
+    "SkRadialGradient.h",
     "SkSweepGradient.cpp",
-    "SkTwoPointConicalGradient.cpp",
+    "SkSweepGradient.h",
 ]
 
 split_srcs_and_hdrs(
diff --git a/src/shaders/gradients/SkConicalGradient.cpp b/src/shaders/gradients/SkConicalGradient.cpp
new file mode 100644
index 0000000..0a71e8b
--- /dev/null
+++ b/src/shaders/gradients/SkConicalGradient.cpp
@@ -0,0 +1,449 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "src/shaders/gradients/SkConicalGradient.h"
+
+#include "include/core/SkColor.h"
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkShader.h"
+#include "include/core/SkTileMode.h"
+#include "include/effects/SkGradientShader.h"
+#include "include/private/base/SkAssert.h"
+#include "include/private/base/SkFloatingPoint.h"
+#include "include/private/base/SkTArray.h"
+#include "src/base/SkArenaAlloc.h"
+#include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpContexts.h"
+#include "src/core/SkRasterPipelineOpList.h"
+#include "src/core/SkReadBuffer.h"
+#include "src/core/SkWriteBuffer.h"
+#include "src/shaders/SkLocalMatrixShader.h"
+#include "src/shaders/SkShaderBase.h"
+#include "src/shaders/gradients/SkGradientBaseShader.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyContext.h"
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <utility>
+
+bool SkConicalGradient::FocalData::set(SkScalar r0, SkScalar r1, SkMatrix* matrix) {
+    fIsSwapped = false;
+    fFocalX = sk_ieee_float_divide(r0, (r0 - r1));
+    if (SkScalarNearlyZero(fFocalX - 1)) {
+        // swap r0, r1
+        matrix->postTranslate(-1, 0);
+        matrix->postScale(-1, 1);
+        std::swap(r0, r1);
+        fFocalX = 0;  // because r0 is now 0
+        fIsSwapped = true;
+    }
+
+    // Map {focal point, (1, 0)} to {(0, 0), (1, 0)}
+    const SkPoint from[2]   = { {fFocalX, 0}, {1, 0} };
+    const SkPoint to[2]     = { {0, 0}, {1, 0} };
+    SkMatrix focalMatrix;
+    if (!focalMatrix.setPolyToPoly(from, to, 2)) {
+        return false;
+    }
+    matrix->postConcat(focalMatrix);
+    fR1 = r1 / SkScalarAbs(1 - fFocalX);  // focalMatrix has a scale of 1/(1-f)
+
+    // The following transformations are just to accelerate the shader computation by saving
+    // some arithmatic operations.
+    if (this->isFocalOnCircle()) {
+        matrix->postScale(0.5, 0.5);
+    } else {
+        matrix->postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1)));
+    }
+    matrix->postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX));  // scale |1 - f|
+    return true;
+}
+
+sk_sp<SkShader> SkConicalGradient::Create(const SkPoint& c0,
+                                          SkScalar r0,
+                                          const SkPoint& c1,
+                                          SkScalar r1,
+                                          const Descriptor& desc,
+                                          const SkMatrix* localMatrix) {
+    SkMatrix gradientMatrix;
+    Type gradientType;
+
+    if (SkScalarNearlyZero((c0 - c1).length())) {
+        if (SkScalarNearlyZero(std::max(r0, r1)) || SkScalarNearlyEqual(r0, r1)) {
+            // Degenerate case; avoid dividing by zero. Should have been caught by caller but
+            // just in case, recheck here.
+            return nullptr;
+        }
+        // Concentric case: we can pretend we're radial (with a tiny twist).
+        const SkScalar scale = sk_ieee_float_divide(1, std::max(r0, r1));
+        gradientMatrix = SkMatrix::Translate(-c1.x(), -c1.y());
+        gradientMatrix.postScale(scale, scale);
+
+        gradientType = Type::kRadial;
+    } else {
+        const SkPoint centers[2] = { c0    , c1     };
+        const SkPoint unitvec[2] = { {0, 0}, {1, 0} };
+
+        if (!gradientMatrix.setPolyToPoly(centers, unitvec, 2)) {
+            // Degenerate case.
+            return nullptr;
+        }
+
+        gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal;
+    }
+
+    FocalData focalData;
+    if (gradientType == Type::kFocal) {
+        const auto dCenter = (c0 - c1).length();
+        if (!focalData.set(r0 / dCenter, r1 / dCenter, &gradientMatrix)) {
+            return nullptr;
+        }
+    }
+    return SkLocalMatrixShader::MakeWrapped<SkConicalGradient>(
+            localMatrix, c0, r0, c1, r1, desc, gradientType, gradientMatrix, focalData);
+}
+
+SkConicalGradient::SkConicalGradient(const SkPoint& start,
+                                     SkScalar startRadius,
+                                     const SkPoint& end,
+                                     SkScalar endRadius,
+                                     const Descriptor& desc,
+                                     Type type,
+                                     const SkMatrix& gradientMatrix,
+                                     const FocalData& data)
+        : SkGradientBaseShader(desc, gradientMatrix)
+        , fCenter1(start)
+        , fCenter2(end)
+        , fRadius1(startRadius)
+        , fRadius2(endRadius)
+        , fType(type) {
+    // this is degenerate, and should be caught by our caller
+    SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
+    if (type == Type::kFocal) {
+        fFocalData = data;
+    }
+}
+
+bool SkConicalGradient::isOpaque() const {
+    // Because areas outside the cone are left untouched, we cannot treat the
+    // shader as opaque even if the gradient itself is opaque.
+    // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
+    return false;
+}
+
+// Returns the original non-sorted version of the gradient
+SkShaderBase::GradientType SkConicalGradient::asGradient(GradientInfo* info,
+                                                         SkMatrix* localMatrix) const {
+    if (info) {
+        commonAsAGradient(info);
+        info->fPoint[0] = fCenter1;
+        info->fPoint[1] = fCenter2;
+        info->fRadius[0] = fRadius1;
+        info->fRadius[1] = fRadius2;
+    }
+    if (localMatrix) {
+        *localMatrix = SkMatrix::I();
+    }
+    return GradientType::kConical;
+}
+
+sk_sp<SkFlattenable> SkConicalGradient::CreateProc(SkReadBuffer& buffer) {
+    DescriptorScope desc;
+    SkMatrix legacyLocalMatrix;
+    if (!desc.unflatten(buffer, &legacyLocalMatrix)) {
+        return nullptr;
+    }
+    SkPoint c1 = buffer.readPoint();
+    SkPoint c2 = buffer.readPoint();
+    SkScalar r1 = buffer.readScalar();
+    SkScalar r2 = buffer.readScalar();
+
+    if (!buffer.isValid()) {
+        return nullptr;
+    }
+    return SkGradientShader::MakeTwoPointConical(c1,
+                                                 r1,
+                                                 c2,
+                                                 r2,
+                                                 desc.fColors,
+                                                 std::move(desc.fColorSpace),
+                                                 desc.fPositions,
+                                                 desc.fColorCount,
+                                                 desc.fTileMode,
+                                                 desc.fInterpolation,
+                                                 &legacyLocalMatrix);
+}
+
+void SkConicalGradient::flatten(SkWriteBuffer& buffer) const {
+    this->SkGradientBaseShader::flatten(buffer);
+    buffer.writePoint(fCenter1);
+    buffer.writePoint(fCenter2);
+    buffer.writeScalar(fRadius1);
+    buffer.writeScalar(fRadius2);
+}
+
+void SkConicalGradient::appendGradientStages(SkArenaAlloc* alloc,
+                                             SkRasterPipeline* p,
+                                             SkRasterPipeline* postPipeline) const {
+    const auto dRadius = fRadius2 - fRadius1;
+
+    if (fType == Type::kRadial) {
+        p->append(SkRasterPipelineOp::xy_to_radius);
+
+        // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2].
+        auto scale = std::max(fRadius1, fRadius2) / dRadius;
+        auto bias = -fRadius1 / dRadius;
+
+        p->append_matrix(alloc, SkMatrix::Translate(bias, 0) * SkMatrix::Scale(scale, 1));
+        return;
+    }
+
+    if (fType == Type::kStrip) {
+        auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
+        SkScalar scaledR0 = fRadius1 / this->getCenterX1();
+        ctx->fP0 = scaledR0 * scaledR0;
+        p->append(SkRasterPipelineOp::xy_to_2pt_conical_strip, ctx);
+        p->append(SkRasterPipelineOp::mask_2pt_conical_nan, ctx);
+        postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask);
+        return;
+    }
+
+    auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
+    ctx->fP0 = 1 / fFocalData.fR1;
+    ctx->fP1 = fFocalData.fFocalX;
+
+    if (fFocalData.isFocalOnCircle()) {
+        p->append(SkRasterPipelineOp::xy_to_2pt_conical_focal_on_circle);
+    } else if (fFocalData.isWellBehaved()) {
+        p->append(SkRasterPipelineOp::xy_to_2pt_conical_well_behaved, ctx);
+    } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
+        p->append(SkRasterPipelineOp::xy_to_2pt_conical_smaller, ctx);
+    } else {
+        p->append(SkRasterPipelineOp::xy_to_2pt_conical_greater, ctx);
+    }
+
+    if (!fFocalData.isWellBehaved()) {
+        p->append(SkRasterPipelineOp::mask_2pt_conical_degenerates, ctx);
+    }
+    if (1 - fFocalData.fFocalX < 0) {
+        p->append(SkRasterPipelineOp::negate_x);
+    }
+    if (!fFocalData.isNativelyFocal()) {
+        p->append(SkRasterPipelineOp::alter_2pt_conical_compensate_focal, ctx);
+    }
+    if (fFocalData.isSwapped()) {
+        p->append(SkRasterPipelineOp::alter_2pt_conical_unswap);
+    }
+    if (!fFocalData.isWellBehaved()) {
+        postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask);
+    }
+}
+
+#if defined(SK_ENABLE_SKVM)
+skvm::F32 SkConicalGradient::transformT(skvm::Builder* p,
+                                        skvm::Uniforms* uniforms,
+                                        skvm::Coord coord,
+                                        skvm::I32* mask) const {
+    auto mag = [](skvm::F32 x, skvm::F32 y) { return sqrt(x * x + y * y); };
+
+    // See https://skia.org/dev/design/conical, and appendStages() above.
+    // There's a lot going on here, and I'm not really sure what's independent
+    // or disjoint, what can be reordered, simplified, etc.  Tweak carefully.
+
+    const skvm::F32 x = coord.x, y = coord.y;
+    if (fType == Type::kRadial) {
+        float denom = 1.0f / (fRadius2 - fRadius1);
+        float scale = std::max(fRadius1, fRadius2) * denom;
+        float bias = -fRadius1 * denom;
+        return mag(x,y) * p->uniformF(uniforms->pushF(scale))
+                        + p->uniformF(uniforms->pushF(bias ));
+    }
+
+    if (fType == Type::kStrip) {
+        float r = fRadius1 / this->getCenterX1();
+        skvm::F32 t = x + sqrt(p->uniformF(uniforms->pushF(r * r)) - y * y);
+
+        *mask = (t == t);  // t != NaN
+        return t;
+    }
+
+    const skvm::F32 invR1 = p->uniformF(uniforms->pushF(1 / fFocalData.fR1));
+
+    skvm::F32 t;
+    if (fFocalData.isFocalOnCircle()) {
+        t = (y / x) * y + x;  // (x^2 + y^2) / x  ~~>  x + y^2/x  ~~>  y/x * y + x
+    } else if (fFocalData.isWellBehaved()) {
+        t = mag(x, y) - x * invR1;
+    } else {
+        skvm::F32 k = sqrt(x * x - y * y);
+        if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
+            k = -k;
+        }
+        t = k - x * invR1;
+    }
+
+    if (!fFocalData.isWellBehaved()) {
+        // TODO: not sure why we consider t == 0 degenerate
+        *mask = (t > 0.0f);  // and implicitly, t != NaN
+    }
+
+    const skvm::F32 focalX = p->uniformF(uniforms->pushF(fFocalData.fFocalX));
+    if (1 - fFocalData.fFocalX < 0) {
+        t = -t;
+    }
+    if (!fFocalData.isNativelyFocal()) {
+        t += focalX;
+    }
+    if (fFocalData.isSwapped()) {
+        t = 1.0f - t;
+    }
+    return t;
+}
+#endif
+
+#if defined(SK_GRAPHITE)
+void SkConicalGradient::addToKey(const skgpu::graphite::KeyContext& keyContext,
+                                 skgpu::graphite::PaintParamsKeyBuilder* builder,
+                                 skgpu::graphite::PipelineDataGatherer* gatherer) const {
+    this->addToKeyCommon(keyContext,
+                         builder,
+                         gatherer,
+                         GradientType::kConical,
+                         fCenter1,
+                         fCenter2,
+                         fRadius1,
+                         fRadius2,
+                         0.0f,
+                         0.0f);
+}
+#endif
+
+// assumes colors is SkColor4f* and pos is SkScalar*
+#define EXPAND_1_COLOR(count)            \
+    SkColor4f tmp[2];                    \
+    do {                                 \
+        if (1 == count) {                \
+            tmp[0] = tmp[1] = colors[0]; \
+            colors = tmp;                \
+            pos = nullptr;               \
+            count = 2;                   \
+        }                                \
+    } while (0)
+
+sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
+                                                      SkScalar startRadius,
+                                                      const SkPoint& end,
+                                                      SkScalar endRadius,
+                                                      const SkColor4f colors[],
+                                                      sk_sp<SkColorSpace> colorSpace,
+                                                      const SkScalar pos[],
+                                                      int colorCount,
+                                                      SkTileMode mode,
+                                                      const Interpolation& interpolation,
+                                                      const SkMatrix* localMatrix) {
+    if (startRadius < 0 || endRadius < 0) {
+        return nullptr;
+    }
+    if (!SkGradientBaseShader::ValidGradient(colors, colorCount, mode, interpolation)) {
+        return nullptr;
+    }
+    if (SkScalarNearlyZero((start - end).length(), SkGradientBaseShader::kDegenerateThreshold)) {
+        // If the center positions are the same, then the gradient is the radial variant of a 2 pt
+        // conical gradient, an actual radial gradient (startRadius == 0), or it is fully degenerate
+        // (startRadius == endRadius).
+        if (SkScalarNearlyEqual(
+                    startRadius, endRadius, SkGradientBaseShader::kDegenerateThreshold)) {
+            // Degenerate case, where the interpolation region area approaches zero. The proper
+            // behavior depends on the tile mode, which is consistent with the default degenerate
+            // gradient behavior, except when mode = clamp and the radii > 0.
+            if (mode == SkTileMode::kClamp &&
+                endRadius > SkGradientBaseShader::kDegenerateThreshold) {
+                // The interpolation region becomes an infinitely thin ring at the radius, so the
+                // final gradient will be the first color repeated from p=0 to 1, and then a hard
+                // stop switching to the last color at p=1.
+                static constexpr SkScalar circlePos[3] = {0, 1, 1};
+                SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]};
+                return MakeRadial(start,
+                                  endRadius,
+                                  reColors,
+                                  std::move(colorSpace),
+                                  circlePos,
+                                  3,
+                                  mode,
+                                  interpolation,
+                                  localMatrix);
+            } else {
+                // Otherwise use the default degenerate case
+                return SkGradientBaseShader::MakeDegenerateGradient(
+                        colors, pos, colorCount, std::move(colorSpace), mode);
+            }
+        } else if (SkScalarNearlyZero(startRadius, SkGradientBaseShader::kDegenerateThreshold)) {
+            // We can treat this gradient as radial, which is faster. If we got here, we know
+            // that endRadius is not equal to 0, so this produces a meaningful gradient
+            return MakeRadial(start,
+                              endRadius,
+                              colors,
+                              std::move(colorSpace),
+                              pos,
+                              colorCount,
+                              mode,
+                              interpolation,
+                              localMatrix);
+        }
+        // Else it's the 2pt conical radial variant with no degenerate radii, so fall through to the
+        // regular 2pt constructor.
+    }
+
+    if (localMatrix && !localMatrix->invert(nullptr)) {
+        return nullptr;
+    }
+    EXPAND_1_COLOR(colorCount);
+
+    SkGradientBaseShader::ColorStopOptimizer opt(colors, pos, colorCount, mode);
+
+    SkGradientBaseShader::Descriptor desc(
+            opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, interpolation);
+    return SkConicalGradient::Create(start, startRadius, end, endRadius, desc, localMatrix);
+}
+
+#undef EXPAND_1_COLOR
+
+sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
+                                                      SkScalar startRadius,
+                                                      const SkPoint& end,
+                                                      SkScalar endRadius,
+                                                      const SkColor colors[],
+                                                      const SkScalar pos[],
+                                                      int colorCount,
+                                                      SkTileMode mode,
+                                                      uint32_t flags,
+                                                      const SkMatrix* localMatrix) {
+    SkColorConverter converter(colors, colorCount);
+    return MakeTwoPointConical(start,
+                               startRadius,
+                               end,
+                               endRadius,
+                               converter.fColors4f.begin(),
+                               nullptr,
+                               pos,
+                               colorCount,
+                               mode,
+                               flags,
+                               localMatrix);
+}
+
+void SkRegisterConicalGradientShaderFlattenable() {
+    SK_REGISTER_FLATTENABLE(SkConicalGradient);
+    // Previous name
+    SkFlattenable::Register("SkTwoPointConicalGradient", SkConicalGradient::CreateProc);
+}
diff --git a/src/shaders/gradients/SkConicalGradient.h b/src/shaders/gradients/SkConicalGradient.h
new file mode 100644
index 0000000..e66d617
--- /dev/null
+++ b/src/shaders/gradients/SkConicalGradient.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkConicalGradient_DEFINED
+#define SkConicalGradient_DEFINED
+
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkScalar.h"
+#include "src/shaders/gradients/SkGradientBaseShader.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyContext.h"
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif
+
+class SkArenaAlloc;
+class SkMatrix;
+class SkRasterPipeline;
+class SkReadBuffer;
+class SkShader;
+class SkWriteBuffer;
+
+// Please see https://skia.org/dev/design/conical for how our shader works.
+class SkConicalGradient final : public SkGradientBaseShader {
+public:
+    // See https://skia.org/dev/design/conical for what focal data means and how our shader works.
+    // We make it public so the GPU shader can also use it.
+    struct FocalData {
+        SkScalar fR1;      // r1 after mapping focal point to (0, 0)
+        SkScalar fFocalX;  // f
+        bool fIsSwapped;   // whether we swapped r0, r1
+
+        // The input r0, r1 are the radii when we map centers to {(0, 0), (1, 0)}.
+        // We'll post concat matrix with our transformation matrix that maps focal point to (0, 0).
+        // Returns true if the set succeeded
+        bool set(SkScalar r0, SkScalar r1, SkMatrix* matrix);
+
+        // Whether the focal point (0, 0) is on the end circle with center (1, 0) and radius r1. If
+        // this is true, it's as if an aircraft is flying at Mach 1 and all circles (soundwaves)
+        // will go through the focal point (aircraft). In our previous implementations, this was
+        // known as the edge case where the inside circle touches the outside circle (on the focal
+        // point). If we were to solve for t bruteforcely using a quadratic equation, this case
+        // implies that the quadratic equation degenerates to a linear equation.
+        bool isFocalOnCircle() const { return SkScalarNearlyZero(1 - fR1); }
+
+        bool isSwapped() const { return fIsSwapped; }
+        bool isWellBehaved() const { return !this->isFocalOnCircle() && fR1 > 1; }
+        bool isNativelyFocal() const { return SkScalarNearlyZero(fFocalX); }
+    };
+
+    enum class Type { kRadial, kStrip, kFocal };
+
+    static sk_sp<SkShader> Create(const SkPoint& start,
+                                  SkScalar startRadius,
+                                  const SkPoint& end,
+                                  SkScalar endRadius,
+                                  const Descriptor&,
+                                  const SkMatrix* localMatrix);
+
+    GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override;
+#if defined(SK_GRAPHITE)
+    void addToKey(const skgpu::graphite::KeyContext&,
+                  skgpu::graphite::PaintParamsKeyBuilder*,
+                  skgpu::graphite::PipelineDataGatherer*) const override;
+#endif
+    bool isOpaque() const override;
+
+    SkScalar getCenterX1() const { return SkPoint::Distance(fCenter1, fCenter2); }
+    SkScalar getStartRadius() const { return fRadius1; }
+    SkScalar getDiffRadius() const { return fRadius2 - fRadius1; }
+    const SkPoint& getStartCenter() const { return fCenter1; }
+    const SkPoint& getEndCenter() const { return fCenter2; }
+    SkScalar getEndRadius() const { return fRadius2; }
+
+    Type getType() const { return fType; }
+    const FocalData& getFocalData() const { return fFocalData; }
+
+    SkConicalGradient(const SkPoint& c0,
+                      SkScalar r0,
+                      const SkPoint& c1,
+                      SkScalar r1,
+                      const Descriptor&,
+                      Type,
+                      const SkMatrix&,
+                      const FocalData&);
+
+protected:
+    void flatten(SkWriteBuffer& buffer) const override;
+
+    void appendGradientStages(SkArenaAlloc* alloc,
+                              SkRasterPipeline* tPipeline,
+                              SkRasterPipeline* postPipeline) const override;
+#if defined(SK_ENABLE_SKVM)
+    skvm::F32 transformT(skvm::Builder*,
+                         skvm::Uniforms*,
+                         skvm::Coord coord,
+                         skvm::I32* mask) const final;
+#endif
+
+private:
+    friend void ::SkRegisterConicalGradientShaderFlattenable();
+    SK_FLATTENABLE_HOOKS(SkConicalGradient)
+
+    SkPoint fCenter1;
+    SkPoint fCenter2;
+    SkScalar fRadius1;
+    SkScalar fRadius2;
+    Type fType;
+
+    FocalData fFocalData;
+};
+
+#endif
diff --git a/src/shaders/gradients/SkGradientShaderBase.cpp b/src/shaders/gradients/SkGradientBaseShader.cpp
similarity index 79%
rename from src/shaders/gradients/SkGradientShaderBase.cpp
rename to src/shaders/gradients/SkGradientBaseShader.cpp
index 29f643d..8f6d412 100644
--- a/src/shaders/gradients/SkGradientShaderBase.cpp
+++ b/src/shaders/gradients/SkGradientBaseShader.cpp
@@ -5,21 +5,33 @@
  * found in the LICENSE file.
  */
 
-#include "src/shaders/gradients/SkGradientShaderBase.h"
+#include "src/shaders/gradients/SkGradientBaseShader.h"
 
+#include "include/core/SkAlphaType.h"
 #include "include/core/SkColorSpace.h"
+#include "include/core/SkColorType.h"
+#include "include/core/SkData.h"
+#include "include/core/SkImageInfo.h"
+#include "include/core/SkShader.h"
+#include "include/core/SkTileMode.h"
+#include "include/private/base/SkFloatBits.h"
+#include "include/private/base/SkFloatingPoint.h"
+#include "include/private/base/SkMalloc.h"
+#include "include/private/base/SkTPin.h"
+#include "include/private/base/SkTo.h"
+#include "src/base/SkArenaAlloc.h"
 #include "src/base/SkVx.h"
 #include "src/core/SkColorSpacePriv.h"
 #include "src/core/SkColorSpaceXformSteps.h"
 #include "src/core/SkConvertPixels.h"
-#include "src/core/SkMatrixProvider.h"
+#include "src/core/SkEffectPriv.h"
+#include "src/core/SkPicturePriv.h"
 #include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpContexts.h"
+#include "src/core/SkRasterPipelineOpList.h"
 #include "src/core/SkReadBuffer.h"
-#include "src/core/SkVM.h"
 #include "src/core/SkWriteBuffer.h"
 
-using namespace skia_private;
-
 #if defined(SK_GRAPHITE)
 #include "src/base/SkHalf.h"
 #include "src/core/SkColorSpacePriv.h"
@@ -30,7 +42,12 @@
 #include "src/gpu/graphite/RecorderPriv.h"
 #endif
 
+#include <algorithm>
 #include <cmath>
+#include <optional>
+#include <utility>
+
+using namespace skia_private;
 
 enum GradientSerializationFlags {
     // Bits 29:31 used for various boolean flags
@@ -56,13 +73,13 @@
     kInterpolationInPremul_GSF = 0x1,
 };
 
-SkGradientShaderBase::Descriptor::Descriptor() {
+SkGradientBaseShader::Descriptor::Descriptor() {
     sk_bzero(this, sizeof(*this));
     fTileMode = SkTileMode::kClamp;
 }
-SkGradientShaderBase::Descriptor::~Descriptor() = default;
+SkGradientBaseShader::Descriptor::~Descriptor() = default;
 
-void SkGradientShaderBase::flatten(SkWriteBuffer& buffer) const {
+void SkGradientBaseShader::flatten(SkWriteBuffer& buffer) const {
     uint32_t flags = 0;
     if (fPositions) {
         flags |= kHasPosition_GSF;
@@ -117,7 +134,7 @@
     return true;
 }
 
-bool SkGradientShaderBase::DescriptorScope::unflatten(SkReadBuffer& buffer,
+bool SkGradientBaseShader::DescriptorScope::unflatten(SkReadBuffer& buffer,
                                                       SkMatrix* legacyLocalMatrix) {
     // New gradient format. Includes floating point color, color space, densely packed flags
     uint32_t flags = buffer.readUInt();
@@ -165,7 +182,7 @@
 
 ////////////////////////////////////////////////////////////////////////////////////////////
 
-SkGradientShaderBase::SkGradientShaderBase(const Descriptor& desc, const SkMatrix& ptsToUnit)
+SkGradientBaseShader::SkGradientBaseShader(const Descriptor& desc, const SkMatrix& ptsToUnit)
         : fPtsToUnit(ptsToUnit)
         , fColorSpace(desc.fColorSpace ? desc.fColorSpace : SkColorSpace::MakeSRGB())
         , fFirstStopIsImplicit(false)
@@ -200,7 +217,7 @@
 
     size_t storageSize =
             fColorCount * (sizeof(SkColor4f) + (desc.fPositions ? sizeof(SkScalar) : 0));
-    fColors    = reinterpret_cast<SkColor4f*>(fStorage.reset(storageSize));
+    fColors = reinterpret_cast<SkColor4f*>(fStorage.reset(storageSize));
     fPositions = desc.fPositions ? reinterpret_cast<SkScalar*>(fColors + fColorCount) : nullptr;
 
     // Now copy over the colors, adding the duplicates at t=0 and t=1 as needed
@@ -220,7 +237,7 @@
     if (desc.fPositions) {
         SkScalar prev = 0;
         SkScalar* positions = fPositions;
-        *positions++ = prev; // force the first pos to 0
+        *positions++ = prev;  // force the first pos to 0
 
         int startIndex = fFirstStopIsImplicit ? 0 : 1;
         int count = desc.fColorCount + fLastStopIsImplicit;
@@ -242,10 +259,12 @@
     }
 }
 
-SkGradientShaderBase::~SkGradientShaderBase() {}
+SkGradientBaseShader::~SkGradientBaseShader() {}
 
-static void add_stop_color(SkRasterPipeline_GradientCtx* ctx, size_t stop,
-                           SkPMColor4f Fs, SkPMColor4f Bs) {
+static void add_stop_color(SkRasterPipeline_GradientCtx* ctx,
+                           size_t stop,
+                           SkPMColor4f Fs,
+                           SkPMColor4f Bs) {
     (ctx->fs[0])[stop] = Fs.fR;
     (ctx->fs[1])[stop] = Fs.fG;
     (ctx->fs[2])[stop] = Fs.fB;
@@ -258,64 +277,70 @@
 }
 
 static void add_const_color(SkRasterPipeline_GradientCtx* ctx, size_t stop, SkPMColor4f color) {
-    add_stop_color(ctx, stop, { 0, 0, 0, 0 }, color);
+    add_stop_color(ctx, stop, {0, 0, 0, 0}, color);
 }
 
 // Calculate a factor F and a bias B so that color = F*t + B when t is in range of
 // the stop. Assume that the distance between stops is 1/gapCount.
-static void init_stop_evenly(SkRasterPipeline_GradientCtx* ctx, float gapCount, size_t stop,
-                             SkPMColor4f c_l, SkPMColor4f c_r) {
+static void init_stop_evenly(SkRasterPipeline_GradientCtx* ctx,
+                             float gapCount,
+                             size_t stop,
+                             SkPMColor4f c_l,
+                             SkPMColor4f c_r) {
     // Clankium's GCC 4.9 targeting ARMv7 is barfing when we use Sk4f math here, so go scalar...
     SkPMColor4f Fs = {
-        (c_r.fR - c_l.fR) * gapCount,
-        (c_r.fG - c_l.fG) * gapCount,
-        (c_r.fB - c_l.fB) * gapCount,
-        (c_r.fA - c_l.fA) * gapCount,
+            (c_r.fR - c_l.fR) * gapCount,
+            (c_r.fG - c_l.fG) * gapCount,
+            (c_r.fB - c_l.fB) * gapCount,
+            (c_r.fA - c_l.fA) * gapCount,
     };
     SkPMColor4f Bs = {
-        c_l.fR - Fs.fR*(stop/gapCount),
-        c_l.fG - Fs.fG*(stop/gapCount),
-        c_l.fB - Fs.fB*(stop/gapCount),
-        c_l.fA - Fs.fA*(stop/gapCount),
+            c_l.fR - Fs.fR * (stop / gapCount),
+            c_l.fG - Fs.fG * (stop / gapCount),
+            c_l.fB - Fs.fB * (stop / gapCount),
+            c_l.fA - Fs.fA * (stop / gapCount),
     };
     add_stop_color(ctx, stop, Fs, Bs);
 }
 
 // For each stop we calculate a bias B and a scale factor F, such that
 // for any t between stops n and n+1, the color we want is B[n] + F[n]*t.
-static void init_stop_pos(SkRasterPipeline_GradientCtx* ctx, size_t stop, float t_l, float t_r,
-                          SkPMColor4f c_l, SkPMColor4f c_r) {
+static void init_stop_pos(SkRasterPipeline_GradientCtx* ctx,
+                          size_t stop,
+                          float t_l,
+                          float t_r,
+                          SkPMColor4f c_l,
+                          SkPMColor4f c_r) {
     // See note about Clankium's old compiler in init_stop_evenly().
     SkPMColor4f Fs = {
-        (c_r.fR - c_l.fR) / (t_r - t_l),
-        (c_r.fG - c_l.fG) / (t_r - t_l),
-        (c_r.fB - c_l.fB) / (t_r - t_l),
-        (c_r.fA - c_l.fA) / (t_r - t_l),
+            (c_r.fR - c_l.fR) / (t_r - t_l),
+            (c_r.fG - c_l.fG) / (t_r - t_l),
+            (c_r.fB - c_l.fB) / (t_r - t_l),
+            (c_r.fA - c_l.fA) / (t_r - t_l),
     };
     SkPMColor4f Bs = {
-        c_l.fR - Fs.fR*t_l,
-        c_l.fG - Fs.fG*t_l,
-        c_l.fB - Fs.fB*t_l,
-        c_l.fA - Fs.fA*t_l,
+            c_l.fR - Fs.fR * t_l,
+            c_l.fG - Fs.fG * t_l,
+            c_l.fB - Fs.fB * t_l,
+            c_l.fA - Fs.fA * t_l,
     };
     ctx->ts[stop] = t_l;
     add_stop_color(ctx, stop, Fs, Bs);
 }
 
-void SkGradientShaderBase::AppendGradientFillStages(SkRasterPipeline* p,
+void SkGradientBaseShader::AppendGradientFillStages(SkRasterPipeline* p,
                                                     SkArenaAlloc* alloc,
                                                     const SkPMColor4f* pmColors,
                                                     const SkScalar* positions,
                                                     int count) {
     // The two-stop case with stops at 0 and 1.
     if (count == 2 && positions == nullptr) {
-        const SkPMColor4f c_l = pmColors[0],
-                          c_r = pmColors[1];
+        const SkPMColor4f c_l = pmColors[0], c_r = pmColors[1];
 
         // See F and B below.
         auto ctx = alloc->make<SkRasterPipeline_EvenlySpaced2StopGradientCtx>();
         (skvx::float4::Load(c_r.vec()) - skvx::float4::Load(c_l.vec())).store(ctx->f);
-        (                                skvx::float4::Load(c_l.vec())).store(ctx->b);
+        (skvx::float4::Load(c_l.vec())).store(ctx->b);
 
         p->append(SkRasterPipelineOp::evenly_spaced_2_stop_gradient, ctx);
     } else {
@@ -350,7 +375,7 @@
 
             ctx->ts = alloc->makeArray<float>(count + 1);
 
-            // Remove the default stops inserted by SkGradientShaderBase::SkGradientShaderBase
+            // Remove the default stops inserted by SkGradientBaseShader::SkGradientBaseShader
             // because they are naturally handled by the search method.
             int firstStop;
             int lastStop;
@@ -363,12 +388,12 @@
             }
 
             size_t stopCount = 0;
-            float  t_l = positions[firstStop];
+            float t_l = positions[firstStop];
             SkPMColor4f c_l = pmColors[firstStop];
             add_const_color(ctx, stopCount++, c_l);
             // N.B. lastStop is the index of the last stop, not one after.
             for (int i = firstStop; i < lastStop; i++) {
-                float  t_r = positions[i + 1];
+                float t_r = positions[i + 1];
                 SkPMColor4f c_r = pmColors[i + 1];
                 SkASSERT(t_l <= t_r);
                 if (t_l < t_r) {
@@ -388,12 +413,13 @@
     }
 }
 
-bool SkGradientShaderBase::appendStages(const SkStageRec& rec, const MatrixRec& mRec) const {
+bool SkGradientBaseShader::appendStages(const SkStageRec& rec,
+                                        const SkShaders::MatrixRec& mRec) const {
     SkRasterPipeline* p = rec.fPipeline;
     SkArenaAlloc* alloc = rec.fAlloc;
     SkRasterPipeline_DecalTileCtx* decal_ctx = nullptr;
 
-    std::optional<MatrixRec> newMRec = mRec.apply(rec, fPtsToUnit);
+    std::optional<SkShaders::MatrixRec> newMRec = mRec.apply(rec, fPtsToUnit);
     if (!newMRec.has_value()) {
         return false;
     }
@@ -402,9 +428,13 @@
 
     this->appendGradientStages(alloc, p, &postPipeline);
 
-    switch(fTileMode) {
-        case SkTileMode::kMirror: p->append(SkRasterPipelineOp::mirror_x_1); break;
-        case SkTileMode::kRepeat: p->append(SkRasterPipelineOp::repeat_x_1); break;
+    switch (fTileMode) {
+        case SkTileMode::kMirror:
+            p->append(SkRasterPipelineOp::mirror_x_1);
+            break;
+        case SkTileMode::kRepeat:
+            p->append(SkRasterPipelineOp::repeat_x_1);
+            break;
         case SkTileMode::kDecal:
             decal_ctx = alloc->make<SkRasterPipeline_DecalTileCtx>();
             decal_ctx->limit_x = SkBits2Float(SkFloat2Bits(1.0f) + 1);
@@ -445,7 +475,8 @@
                 p->append(SkRasterPipelineOp::unpremul_polar);
                 colorIsPremul = false;
                 break;
-            default: break;
+            default:
+                break;
         }
     }
 
@@ -500,59 +531,44 @@
     f[0] = (lab.g * (1 / 500.0f)) + f[1];
     f[2] = f[1] - (lab.b * (1 / 200.0f));
 
-    skvm::F32 f_cubed[3] = { f[0]*f[0]*f[0], f[1]*f[1]*f[1], f[2]*f[2]*f[2] };
+    skvm::F32 f_cubed[3] = {f[0] * f[0] * f[0], f[1] * f[1] * f[1], f[2] * f[2] * f[2]};
 
-    skvm::F32 xyz[3] = {
-        skvm::select(f_cubed[0] > e, f_cubed[0], (116 * f[0] - 16) * (1 / k)),
-        skvm::select(lab.r > k * e , f_cubed[1], lab.r * (1 / k)),
-        skvm::select(f_cubed[2] > e, f_cubed[2], (116 * f[2] - 16) * (1 / k))
-    };
+    skvm::F32 xyz[3] = {skvm::select(f_cubed[0] > e, f_cubed[0], (116 * f[0] - 16) * (1 / k)),
+                        skvm::select(lab.r > k * e, f_cubed[1], lab.r * (1 / k)),
+                        skvm::select(f_cubed[2] > e, f_cubed[2], (116 * f[2] - 16) * (1 / k))};
 
-    constexpr float D50[3] = { 0.3457f / 0.3585f, 1.0f, (1.0f - 0.3457f - 0.3585f) / 0.3585f };
-    return skvm::Color { xyz[0]*D50[0], xyz[1]*D50[1], xyz[2]*D50[2], lab.a };
+    constexpr float D50[3] = {0.3457f / 0.3585f, 1.0f, (1.0f - 0.3457f - 0.3585f) / 0.3585f};
+    return skvm::Color{xyz[0] * D50[0], xyz[1] * D50[1], xyz[2] * D50[2], lab.a};
 }
 
 // Skia stores all polar colors with hue in the first component, so this "LCH -> Lab" transform
 // actually takes "HCL". This is also used to do the same polar transform for OkHCL to OkLAB.
 static skvm::Color css_hcl_to_lab(skvm::Color hcl) {
     skvm::F32 hueRadians = hcl.r * (SK_FloatPI / 180);
-    return skvm::Color {
-        hcl.b,
-        hcl.g * approx_cos(hueRadians),
-        hcl.g * approx_sin(hueRadians),
-        hcl.a
-    };
+    return skvm::Color{
+            hcl.b, hcl.g * approx_cos(hueRadians), hcl.g * approx_sin(hueRadians), hcl.a};
 }
 
-static skvm::Color css_hcl_to_xyz(skvm::Color hcl) {
-    return css_lab_to_xyz(css_hcl_to_lab(hcl));
-}
+static skvm::Color css_hcl_to_xyz(skvm::Color hcl) { return css_lab_to_xyz(css_hcl_to_lab(hcl)); }
 
 static skvm::Color css_oklab_to_linear_srgb(skvm::Color oklab) {
     skvm::F32 l_ = oklab.r + 0.3963377774f * oklab.g + 0.2158037573f * oklab.b,
               m_ = oklab.r - 0.1055613458f * oklab.g - 0.0638541728f * oklab.b,
               s_ = oklab.r - 0.0894841775f * oklab.g - 1.2914855480f * oklab.b;
 
-    skvm::F32 l = l_*l_*l_,
-              m = m_*m_*m_,
-              s = s_*s_*s_;
+    skvm::F32 l = l_ * l_ * l_, m = m_ * m_ * m_, s = s_ * s_ * s_;
 
-    return skvm::Color {
-        +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
-        -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
-        -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
-        oklab.a
-    };
-
+    return skvm::Color{+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
+                       -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
+                       -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
+                       oklab.a};
 }
 
 static skvm::Color css_okhcl_to_linear_srgb(skvm::Color okhcl) {
     return css_oklab_to_linear_srgb(css_hcl_to_lab(okhcl));
 }
 
-static skvm::F32 mod_f(skvm::F32 x, float y) {
-    return x - y * skvm::floor(x * (1 / y));
-}
+static skvm::F32 mod_f(skvm::F32 x, float y) { return x - y * skvm::floor(x * (1 / y)); }
 
 static skvm::Color css_hsl_to_srgb(skvm::Color hsl) {
     hsl.r = mod_f(hsl.r, 360);
@@ -562,17 +578,15 @@
     hsl.b *= 0.01f;
 
     skvm::F32 k[3] = {
-        mod_f(0 + hsl.r * (1 / 30.0f), 12),
-        mod_f(8 + hsl.r * (1 / 30.0f), 12),
-        mod_f(4 + hsl.r * (1 / 30.0f), 12),
+            mod_f(0 + hsl.r * (1 / 30.0f), 12),
+            mod_f(8 + hsl.r * (1 / 30.0f), 12),
+            mod_f(4 + hsl.r * (1 / 30.0f), 12),
     };
     skvm::F32 a = hsl.g * min(hsl.b, 1 - hsl.b);
-    return skvm::Color {
-        hsl.b - a * clamp(min(k[0] - 3, 9 - k[0]), -1, 1),
-        hsl.b - a * clamp(min(k[1] - 3, 9 - k[1]), -1, 1),
-        hsl.b - a * clamp(min(k[2] - 3, 9 - k[2]), -1, 1),
-        hsl.a
-    };
+    return skvm::Color{hsl.b - a * clamp(min(k[0] - 3, 9 - k[0]), -1, 1),
+                       hsl.b - a * clamp(min(k[1] - 3, 9 - k[1]), -1, 1),
+                       hsl.b - a * clamp(min(k[2] - 3, 9 - k[2]), -1, 1),
+                       hsl.a};
 }
 
 static skvm::Color css_hwb_to_srgb(skvm::Color hwb, skvm::Builder* p) {
@@ -588,19 +602,17 @@
 
     skvm::I32 isGray = (hwb.g + hwb.b) >= 1;
 
-    return skvm::Color {
-        select(isGray, gray, rgb.r),
-        select(isGray, gray, rgb.g),
-        select(isGray, gray, rgb.b),
-        hwb.a
-    };
+    return skvm::Color{select(isGray, gray, rgb.r),
+                       select(isGray, gray, rgb.g),
+                       select(isGray, gray, rgb.b),
+                       hwb.a};
 }
 
-skvm::Color SkGradientShaderBase::program(skvm::Builder* p,
+skvm::Color SkGradientBaseShader::program(skvm::Builder* p,
                                           skvm::Coord device,
                                           skvm::Coord local,
                                           skvm::Color /*paint*/,
-                                          const MatrixRec& mRec,
+                                          const SkShaders::MatrixRec& mRec,
                                           const SkColorInfo& dstInfo,
                                           skvm::Uniforms* uniforms,
                                           SkArenaAlloc* alloc) const {
@@ -609,13 +621,13 @@
     }
 
     skvm::I32 mask = p->splat(~0);
-    skvm::F32 t = this->transformT(p,uniforms, local, &mask);
+    skvm::F32 t = this->transformT(p, uniforms, local, &mask);
 
     // Perhaps unexpectedly, clamping is handled naturally by our search, so we
     // don't explicitly clamp t to [0,1].  That clamp would break hard stops
     // right at 0 or 1 boundaries in kClamp mode.  (kRepeat and kMirror always
     // produce values in [0,1].)
-    switch(fTileMode) {
+    switch (fTileMode) {
         case SkTileMode::kClamp:
             break;
 
@@ -630,8 +642,7 @@
         case SkTileMode::kMirror: {
             // t = | (t-1) - 2*(floor( (t-1)*0.5 )) - 1 |
             //       {-A-}      {--------B-------}
-            skvm::F32 A = t - 1.0f,
-                      B = floor(A * 0.5f);
+            skvm::F32 A = t - 1.0f, B = floor(A * 0.5f);
             t = abs(A - (B + B) - 1.0f);
         } break;
     }
@@ -642,8 +653,10 @@
 
     // Transform our colors into a scale factor f and bias b such that for
     // any t between stops i and i+1, the color we want is mad(t, f[i], b[i]).
-    using F4 = skvx::Vec<4,float>;
-    struct FB { F4 f,b; };
+    using F4 = skvx::Vec<4, float>;
+    struct FB {
+        F4 f, b;
+    };
     skvm::Color color;
 
     auto uniformF = [&](float x) { return p->uniformF(uniforms->pushF(x)); };
@@ -653,17 +666,15 @@
         SkASSERT(fPositions == nullptr);
 
         // With 2 stops, we upload the single FB as uniforms and interpolate directly with t.
-        F4 lo = F4::Load(rgba + 0),
-           hi = F4::Load(rgba + 1);
-        F4 F = hi - lo,
-           B = lo;
+        F4 lo = F4::Load(rgba + 0), hi = F4::Load(rgba + 1);
+        F4 F = hi - lo, B = lo;
 
         auto T = clamp01(t);
         color = {
-            T * uniformF(F[0]) + uniformF(B[0]),
-            T * uniformF(F[1]) + uniformF(B[1]),
-            T * uniformF(F[2]) + uniformF(B[2]),
-            T * uniformF(F[3]) + uniformF(B[3]),
+                T * uniformF(F[0]) + uniformF(B[0]),
+                T * uniformF(F[1]) + uniformF(B[1]),
+                T * uniformF(F[2]) + uniformF(B[2]),
+                T * uniformF(F[3]) + uniformF(B[3]),
         };
     } else {
         // To handle clamps in search we add a conceptual stop at t=-inf, so we
@@ -673,28 +684,27 @@
         //   stops:  (-inf)        t0            t1            t2  ...
         //
         // Both these arrays could end up shorter if any hard stops share the same t.
-        FB* fb = alloc->makeArrayDefault<FB>(fColorCount+1);
+        FB* fb = alloc->makeArrayDefault<FB>(fColorCount + 1);
         std::vector<float> stops;  // TODO: SkSTArray?
         stops.reserve(fColorCount);
 
         // Here's our conceptual stop at t=-inf covering all t<=0, clamping to our first color.
-        float  t_lo = this->getPos(0);
+        float t_lo = this->getPos(0);
         F4 color_lo = F4::Load(rgba);
-        fb[0] = { 0.0f, color_lo };
+        fb[0] = {0.0f, color_lo};
         // N.B. No stops[] entry for this implicit -inf.
 
         // Now the non-edge cases, calculating scale and bias between adjacent normal stops.
         for (int i = 1; i < fColorCount; i++) {
-            float  t_hi = this->getPos(i);
+            float t_hi = this->getPos(i);
             F4 color_hi = F4::Load(rgba + i);
 
             // If t_lo == t_hi, we're on a hard stop, and transition immediately to the next color.
             SkASSERT(t_lo <= t_hi);
             if (t_lo < t_hi) {
-                F4 f = (color_hi - color_lo) / (t_hi - t_lo),
-                   b = color_lo - f*t_lo;
+                F4 f = (color_hi - color_lo) / (t_hi - t_lo), b = color_lo - f * t_lo;
                 stops.push_back(t_lo);
-                fb[stops.size()] = {f,b};
+                fb[stops.size()] = {f, b};
             }
 
             t_lo = t_hi;
@@ -702,7 +712,7 @@
         }
         // Anything >= our final t clamps to our final color.
         stops.push_back(t_lo);
-        fb[stops.size()] = { 0.0f, color_lo };
+        fb[stops.size()] = {0.0f, color_lo};
 
         // We'll gather FBs from that array we just created.
         skvm::Uniform fbs = uniforms->pushPtr(fb);
@@ -742,10 +752,10 @@
 
         // This is what we've been building towards!
         color = {
-            t * Fr + Br,
-            t * Fg + Bg,
-            t * Fb + Bb,
-            t * Fa + Ba,
+                t * Fr + Br,
+                t * Fg + Bg,
+                t * Fb + Bb,
+                t * Fa + Ba,
         };
     }
 
@@ -770,19 +780,20 @@
                 color.r = hue;
                 colorIsPremul = false;
             } break;
-            default: break;
+            default:
+                break;
         }
     }
 
     // Convert colors in exotic spaces back to their intermediate SkColorSpace
     switch (fInterpolation.fColorSpace) {
-            case ColorSpace::kLab:   color = css_lab_to_xyz(color);           break;
-            case ColorSpace::kOKLab: color = css_oklab_to_linear_srgb(color); break;
-            case ColorSpace::kLCH:   color = css_hcl_to_xyz(color);           break;
-            case ColorSpace::kOKLCH: color = css_okhcl_to_linear_srgb(color); break;
-            case ColorSpace::kHSL:   color = css_hsl_to_srgb(color);          break;
-            case ColorSpace::kHWB:   color = css_hwb_to_srgb(color, p);       break;
-            default: break;
+        case ColorSpace::kLab:   color = css_lab_to_xyz(color);           break;
+        case ColorSpace::kOKLab: color = css_oklab_to_linear_srgb(color); break;
+        case ColorSpace::kLCH:   color = css_hcl_to_xyz(color);           break;
+        case ColorSpace::kOKLCH: color = css_okhcl_to_linear_srgb(color); break;
+        case ColorSpace::kHSL:   color = css_hsl_to_srgb(color);          break;
+        case ColorSpace::kHWB:   color = css_hwb_to_srgb(color, p);       break;
+        default: break;
     }
 
     // Now transform from intermediate to destination color space.
@@ -802,15 +813,15 @@
                     .program(p, uniforms, color);
 
     return {
-        pun_to_F32(mask & pun_to_I32(color.r)),
-        pun_to_F32(mask & pun_to_I32(color.g)),
-        pun_to_F32(mask & pun_to_I32(color.b)),
-        pun_to_F32(mask & pun_to_I32(color.a)),
+            pun_to_F32(mask & pun_to_I32(color.r)),
+            pun_to_F32(mask & pun_to_I32(color.g)),
+            pun_to_F32(mask & pun_to_I32(color.b)),
+            pun_to_F32(mask & pun_to_I32(color.a)),
     };
 }
 #endif  // defined(SK_ENABLE_SKVM)
 
-bool SkGradientShaderBase::isOpaque() const {
+bool SkGradientBaseShader::isOpaque() const {
     return fColorsAreOpaque && (this->getTileMode() != SkTileMode::kDecal);
 }
 
@@ -818,7 +829,7 @@
     return (numer + (denom >> 1)) / denom;
 }
 
-bool SkGradientShaderBase::onAsLuminanceColor(SkColor* lum) const {
+bool SkGradientBaseShader::onAsLuminanceColor(SkColor* lum) const {
     // we just compute an average color.
     // possibly we could weight this based on the proportional width for each color
     //   assuming they are not evenly distributed in the fPos array.
@@ -841,15 +852,18 @@
                                                     SkColorSpace* dst) {
     using ColorSpace = SkGradientShader::Interpolation::ColorSpace;
     switch (cs) {
-        case ColorSpace::kDestination: return sk_ref_sp(dst);
+        case ColorSpace::kDestination:
+            return sk_ref_sp(dst);
 
         // css-color-4 allows XYZD50 and XYZD65. For gradients, those are redundant. Interpolating
         // in any linear RGB space, (regardless of white point), gives the same answer.
-        case ColorSpace::kSRGBLinear: return SkColorSpace::MakeSRGBLinear();
+        case ColorSpace::kSRGBLinear:
+            return SkColorSpace::MakeSRGBLinear();
 
         case ColorSpace::kSRGB:
         case ColorSpace::kHSL:
-        case ColorSpace::kHWB: return SkColorSpace::MakeSRGB();
+        case ColorSpace::kHWB:
+            return SkColorSpace::MakeSRGB();
 
         case ColorSpace::kLab:
         case ColorSpace::kLCH:
@@ -889,18 +903,18 @@
 
         hue *= 60;
     }
-    return { hue, sat * 100, light * 100, rgb.fA };
+    return {hue, sat * 100, light * 100, rgb.fA};
 }
 
 static SkPMColor4f srgb_to_hwb(SkPMColor4f rgb) {
     SkPMColor4f hsl = srgb_to_hsl(rgb);
-    float white =     std::min({rgb.fR, rgb.fG, rgb.fB});
+    float white = std::min({rgb.fR, rgb.fG, rgb.fB});
     float black = 1 - std::max({rgb.fR, rgb.fG, rgb.fB});
-    return { hsl.fR, white * 100, black * 100, rgb.fA };
+    return {hsl.fR, white * 100, black * 100, rgb.fA};
 }
 
 static SkPMColor4f xyzd50_to_lab(SkPMColor4f xyz) {
-    constexpr float D50[3] = { 0.3457f / 0.3585f, 1.0f, (1.0f - 0.3457f - 0.3585f) / 0.3585f };
+    constexpr float D50[3] = {0.3457f / 0.3585f, 1.0f, (1.0f - 0.3457f - 0.3585f) / 0.3585f};
 
     constexpr float e = 216.0f / 24389;
     constexpr float k = 24389.0f / 27;
@@ -911,7 +925,7 @@
         f[i] = (v > e) ? std::cbrtf(v) : (k * v + 16) / 116;
     }
 
-    return { (116 * f[1]) - 16, 500 * (f[0] - f[1]), 200 * (f[1] - f[2]), xyz.fA };
+    return {(116 * f[1]) - 16, 500 * (f[0] - f[1]), 200 * (f[1] - f[2]), xyz.fA};
 }
 
 // The color space is technically LCH, but we produce HCL, so that all polar spaces have hue in the
@@ -919,10 +933,7 @@
 static SkPMColor4f xyzd50_to_hcl(SkPMColor4f xyz) {
     SkPMColor4f Lab = xyzd50_to_lab(xyz);
     float hue = sk_float_radians_to_degrees(atan2f(Lab[2], Lab[1]));
-    return {hue >= 0 ? hue : hue + 360,
-            sqrtf(Lab[1] * Lab[1] + Lab[2] * Lab[2]),
-            Lab[0],
-            xyz.fA};
+    return {hue >= 0 ? hue : hue + 360, sqrtf(Lab[1] * Lab[1] + Lab[2] * Lab[2]), Lab[0], xyz.fA};
 }
 
 // https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
@@ -933,12 +944,10 @@
     l = std::cbrtf(l);
     m = std::cbrtf(m);
     s = std::cbrtf(s);
-    return {
-        0.2104542553f*l + 0.7936177850f*m - 0.0040720468f*s,
-        1.9779984951f*l - 2.4285922050f*m + 0.4505937099f*s,
-        0.0259040371f*l + 0.7827717662f*m - 0.8086757660f*s,
-        rgb.fA
-    };
+    return {0.2104542553f * l + 0.7936177850f * m - 0.0040720468f * s,
+            1.9779984951f * l - 2.4285922050f * m + 0.4505937099f * s,
+            0.0259040371f * l + 0.7827717662f * m - 0.8086757660f * s,
+            rgb.fA};
 }
 
 // The color space is technically OkLCH, but we produce HCL, so that all polar spaces have hue in
@@ -953,11 +962,11 @@
 }
 
 static SkPMColor4f premul_polar(SkPMColor4f hsl) {
-    return { hsl.fR, hsl.fG * hsl.fA, hsl.fB * hsl.fA, hsl.fA };
+    return {hsl.fR, hsl.fG * hsl.fA, hsl.fB * hsl.fA, hsl.fA};
 }
 
 static SkPMColor4f premul_rgb(SkPMColor4f rgb) {
-    return { rgb.fR * rgb.fA, rgb.fG * rgb.fA, rgb.fB * rgb.fA, rgb.fA };
+    return {rgb.fR * rgb.fA, rgb.fG * rgb.fA, rgb.fB * rgb.fA, rgb.fA};
 }
 
 static bool color_space_is_polar(SkGradientShader::Interpolation::ColorSpace cs) {
@@ -996,7 +1005,7 @@
 //    Two have hue as the first component, and two have it as the third component. To reduce
 //    complexity, we always store hue in the first component, swapping it with luminance for
 //    LCH and Oklch. The backend code (eg, shaders) needs to know about this.
-SkColor4fXformer::SkColor4fXformer(const SkGradientShaderBase* shader, SkColorSpace* dst) {
+SkColor4fXformer::SkColor4fXformer(const SkGradientBaseShader* shader, SkColorSpace* dst) {
     using ColorSpace = SkGradientShader::Interpolation::ColorSpace;
     using HueMethod = SkGradientShader::Interpolation::HueMethod;
 
@@ -1013,8 +1022,12 @@
     auto srcInfo = info.makeColorSpace(shader->fColorSpace);
 
     fColors.reset(colorCount);
-    SkAssertResult(SkConvertPixels(dstInfo, fColors.begin(), info.minRowBytes(),
-                                   srcInfo, shader->fColors, info.minRowBytes()));
+    SkAssertResult(SkConvertPixels(dstInfo,
+                                   fColors.begin(),
+                                   info.minRowBytes(),
+                                   srcInfo,
+                                   shader->fColors,
+                                   info.minRowBytes()));
 
     // 3) Transform to the interpolation color space (if it's special)
     ConvertColorProc convertFn = nullptr;
@@ -1043,8 +1056,8 @@
     if (color_space_is_polar(interpolation.fColorSpace)) {
         float delta = 0;
         for (int i = 0; i < colorCount - 1; ++i) {
-            float  h1 = fColors[i].fR;
-            float& h2 = fColors[i+1].fR;
+            float h1 = fColors[i].fR;
+            float& h2 = fColors[i + 1].fR;
             h2 += delta;
             switch (interpolation.fHueMethod) {
                 case HueMethod::kShorter:
@@ -1092,8 +1105,12 @@
             case ColorSpace::kHSL:
             case ColorSpace::kHWB:
             case ColorSpace::kLCH:
-            case ColorSpace::kOKLCH: premulFn = premul_polar; break;
-            default:                 premulFn = premul_rgb;   break;
+            case ColorSpace::kOKLCH:
+                premulFn = premul_polar;
+                break;
+            default:
+                premulFn = premul_rgb;
+                break;
         }
     }
 
@@ -1107,14 +1124,14 @@
 SkColorConverter::SkColorConverter(const SkColor* colors, int count) {
     const float ONE_OVER_255 = 1.f / 255;
     for (int i = 0; i < count; ++i) {
-        fColors4f.push_back({ SkColorGetR(colors[i]) * ONE_OVER_255,
-                              SkColorGetG(colors[i]) * ONE_OVER_255,
-                              SkColorGetB(colors[i]) * ONE_OVER_255,
-                              SkColorGetA(colors[i]) * ONE_OVER_255 });
+        fColors4f.push_back({SkColorGetR(colors[i]) * ONE_OVER_255,
+                             SkColorGetG(colors[i]) * ONE_OVER_255,
+                             SkColorGetB(colors[i]) * ONE_OVER_255,
+                             SkColorGetA(colors[i]) * ONE_OVER_255});
     }
 }
 
-void SkGradientShaderBase::commonAsAGradient(GradientInfo* info) const {
+void SkGradientBaseShader::commonAsAGradient(GradientInfo* info) const {
     if (info) {
         if (info->fColorCount >= fColorCount) {
             if (info->fColors) {
@@ -1138,14 +1155,16 @@
 
 // Return true if these parameters are valid/legal/safe to construct a gradient
 //
-bool SkGradientShaderBase::ValidGradient(const SkColor4f colors[], int count, SkTileMode tileMode,
+bool SkGradientBaseShader::ValidGradient(const SkColor4f colors[],
+                                         int count,
+                                         SkTileMode tileMode,
                                          const Interpolation& interpolation) {
     return nullptr != colors && count >= 1 && (unsigned)tileMode < kSkTileModeCount &&
            (unsigned)interpolation.fColorSpace < Interpolation::kColorSpaceCount &&
            (unsigned)interpolation.fHueMethod < Interpolation::kHueMethodCount;
 }
 
-SkGradientShaderBase::Descriptor::Descriptor(const SkColor4f colors[],
+SkGradientBaseShader::Descriptor::Descriptor(const SkColor4f colors[],
                                              sk_sp<SkColorSpace> colorSpace,
                                              const SkScalar positions[],
                                              int colorCount,
@@ -1160,7 +1179,8 @@
     SkASSERT(fColorCount > 1);
 }
 
-static SkColor4f average_gradient_color(const SkColor4f colors[], const SkScalar pos[],
+static SkColor4f average_gradient_color(const SkColor4f colors[],
+                                        const SkScalar pos[],
                                         int colorCount) {
     // The gradient is a piecewise linear interpolation between colors. For a given interval,
     // the integral between the two endpoints is 0.5 * (ci + cj) * (pj - pi), which provides that
@@ -1214,12 +1234,12 @@
 // Except for special circumstances of clamped gradients, every gradient shape--when degenerate--
 // can be mapped to the same fallbacks. The specific shape factories must account for special
 // clamped conditions separately, this will always return the last color for clamped gradients.
-sk_sp<SkShader> SkGradientShaderBase::MakeDegenerateGradient(const SkColor4f colors[],
+sk_sp<SkShader> SkGradientBaseShader::MakeDegenerateGradient(const SkColor4f colors[],
                                                              const SkScalar pos[],
                                                              int colorCount,
                                                              sk_sp<SkColorSpace> colorSpace,
                                                              SkTileMode mode) {
-    switch(mode) {
+    switch (mode) {
         case SkTileMode::kDecal:
             // normally this would reject the area outside of the interpolation region, so since
             // inside region is empty when the radii are equal, the entire draw region is empty
@@ -1229,8 +1249,8 @@
             // repeat and mirror are treated the same: the border colors are never visible,
             // but approximate the final color as infinite repetitions of the colors, so
             // it can be represented as the average color of the gradient.
-            return SkShaders::Color(
-                    average_gradient_color(colors, pos, colorCount), std::move(colorSpace));
+            return SkShaders::Color(average_gradient_color(colors, pos, colorCount),
+                                    std::move(colorSpace));
         case SkTileMode::kClamp:
             // Depending on how the gradient shape degenerates, there may be a more specialized
             // fallback representation for the factories to use, but this is a reasonable default.
@@ -1240,39 +1260,28 @@
     return nullptr;
 }
 
-SkGradientShaderBase::ColorStopOptimizer::ColorStopOptimizer(const SkColor4f* colors,
+SkGradientBaseShader::ColorStopOptimizer::ColorStopOptimizer(const SkColor4f* colors,
                                                              const SkScalar* pos,
                                                              int count,
                                                              SkTileMode mode)
-        : fColors(colors)
-        , fPos(pos)
-        , fCount(count) {
-
+        : fColors(colors), fPos(pos), fCount(count) {
     if (!pos || count != 3) {
         return;
     }
 
-    if (SkScalarNearlyEqual(pos[0], 0.0f) &&
-        SkScalarNearlyEqual(pos[1], 0.0f) &&
+    if (SkScalarNearlyEqual(pos[0], 0.0f) && SkScalarNearlyEqual(pos[1], 0.0f) &&
         SkScalarNearlyEqual(pos[2], 1.0f)) {
-
-        if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode ||
-            colors[0] == colors[1]) {
-
+        if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode || colors[0] == colors[1]) {
             // Ignore the leftmost color/pos.
             fColors += 1;
-            fPos    += 1;
-            fCount   = 2;
+            fPos += 1;
+            fCount = 2;
         }
-    } else if (SkScalarNearlyEqual(pos[0], 0.0f) &&
-               SkScalarNearlyEqual(pos[1], 1.0f) &&
+    } else if (SkScalarNearlyEqual(pos[0], 0.0f) && SkScalarNearlyEqual(pos[1], 1.0f) &&
                SkScalarNearlyEqual(pos[2], 1.0f)) {
-
-        if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode ||
-            colors[1] == colors[2]) {
-
+        if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode || colors[1] == colors[2]) {
             // Ignore the rightmost color/pos.
-            fCount  = 2;
+            fCount = 2;
         }
     }
 }
@@ -1287,7 +1296,7 @@
                               skgpu::graphite::PaintParamsKeyBuilder* builder,
                               skgpu::graphite::PipelineDataGatherer* gatherer,
                               const skgpu::graphite::GradientShaderBlocks::GradientData& gradData,
-                              const SkGradientShaderBase::Interpolation& interp,
+                              const SkGradientBaseShader::Interpolation& interp,
                               SkColorSpace* intermediateCS) {
     using ColorSpace = SkGradientShader::Interpolation::ColorSpace;
     using namespace skgpu::graphite;
@@ -1309,25 +1318,24 @@
 
     const SkColorInfo& dstColorInfo = keyContext.dstColorInfo();
 
-    SkColorSpace* dstColorSpace = dstColorInfo.colorSpace() ? dstColorInfo.colorSpace()
-                                                            : sk_srgb_singleton();
+    SkColorSpace* dstColorSpace =
+            dstColorInfo.colorSpace() ? dstColorInfo.colorSpace() : sk_srgb_singleton();
 
-    SkAlphaType intermediateAlphaType = inputPremul ? kPremul_SkAlphaType
-                                                    : kUnpremul_SkAlphaType;
+    SkAlphaType intermediateAlphaType = inputPremul ? kPremul_SkAlphaType : kUnpremul_SkAlphaType;
 
-    ColorSpaceTransformBlock::ColorSpaceTransformData data(intermediateCS, intermediateAlphaType,
-                                                           dstColorSpace, dstColorInfo.alphaType());
+    ColorSpaceTransformBlock::ColorSpaceTransformData data(
+            intermediateCS, intermediateAlphaType, dstColorSpace, dstColorInfo.alphaType());
 
     // The gradient block and colorSpace conversion block need to be combined together
     // (via the colorFilterShader block) so that the localMatrix block can treat them as
     // one child.
     ColorFilterShaderBlock::BeginBlock(keyContext, builder, gatherer);
 
-        GradientShaderBlocks::BeginBlock(keyContext, builder, gatherer, gradData);
-        builder->endBlock();
+    GradientShaderBlocks::BeginBlock(keyContext, builder, gatherer, gradData);
+    builder->endBlock();
 
-        ColorSpaceTransformBlock::BeginBlock(keyContext, builder, gatherer, &data);
-        builder->endBlock();
+    ColorSpaceTransformBlock::BeginBlock(keyContext, builder, gatherer, &data);
+    builder->endBlock();
 
     builder->endBlock();
 }
@@ -1337,25 +1345,23 @@
                                         const float* offsets) {
     SkBitmap colorsAndOffsetsBitmap;
 
-    colorsAndOffsetsBitmap.allocPixels(SkImageInfo::Make(numStops, 2,
-                                                         kRGBA_F16_SkColorType,
-                                                         kPremul_SkAlphaType));
+    colorsAndOffsetsBitmap.allocPixels(
+            SkImageInfo::Make(numStops, 2, kRGBA_F16_SkColorType, kPremul_SkAlphaType));
 
     for (int i = 0; i < numStops; i++) {
         // TODO: there should be a way to directly set a premul pixel in a bitmap with
         // a premul color.
         SkColor4f unpremulColor = colors[i].unpremul();
-        colorsAndOffsetsBitmap.erase(unpremulColor,
-                                     SkIRect::MakeXYWH(i, 0, 1, 1));
+        colorsAndOffsetsBitmap.erase(unpremulColor, SkIRect::MakeXYWH(i, 0, 1, 1));
 
-        float offset = offsets ? offsets[i] : SkIntToFloat(i) / (numStops-1);
+        float offset = offsets ? offsets[i] : SkIntToFloat(i) / (numStops - 1);
         SkASSERT(offset >= 0.0f && offset <= 1.0f);
 
         int exponent;
         float mantissa = frexp(offset, &exponent);
 
         SkHalf halfE = SkFloatToHalf(exponent);
-        if ((int) SkHalfToFloat(halfE) != exponent) {
+        if ((int)SkHalfToFloat(halfE) != exponent) {
             SKGPU_LOG_W("Encoding gradient to f16 failed");
             return {};
         }
@@ -1363,29 +1369,32 @@
 #if defined(SK_DEBUG)
         SkHalf halfM = SkFloatToHalf(mantissa);
 
-        float restored = ldexp(SkHalfToFloat(halfM), (int) SkHalfToFloat(halfE));
+        float restored = ldexp(SkHalfToFloat(halfM), (int)SkHalfToFloat(halfE));
         float error = abs(restored - offset);
         SkASSERT(error < 0.001f);
 #endif
 
         // TODO: we're only using 2 of the f16s here. The encoding could be altered to better
         // preserve precision. This encoding yields < 0.001f error for 2^20 evenly spaced stops.
-        colorsAndOffsetsBitmap.erase(SkColor4f{mantissa, (float) exponent, 0, 1},
+        colorsAndOffsetsBitmap.erase(SkColor4f{mantissa, (float)exponent, 0, 1},
                                      SkIRect::MakeXYWH(i, 1, 1, 1));
     }
 
     return colorsAndOffsetsBitmap;
 }
 
-} // anonymous namespace
+}  // anonymous namespace
 
-void SkGradientShaderBase::addToKeyCommon(const skgpu::graphite::KeyContext& keyContext,
+void SkGradientBaseShader::addToKeyCommon(const skgpu::graphite::KeyContext& keyContext,
                                           skgpu::graphite::PaintParamsKeyBuilder* builder,
                                           skgpu::graphite::PipelineDataGatherer* gatherer,
                                           GradientType type,
-                                          SkPoint point0, SkPoint point1,
-                                          float radius0, float radius1,
-                                          float bias, float scale) const {
+                                          SkPoint point0,
+                                          SkPoint point1,
+                                          float radius0,
+                                          float radius1,
+                                          float bias,
+                                          float scale) const {
     using namespace skgpu::graphite;
 
     SkColor4fXformer xformedColors(this, keyContext.dstColorInfo().colorSpace());
@@ -1395,9 +1404,8 @@
 
     if (fColorCount > GradientShaderBlocks::GradientData::kNumInternalStorageStops) {
         if (fColorsAndOffsetsBitmap.empty()) {
-            fColorsAndOffsetsBitmap = create_color_and_offset_bitmap(fColorCount,
-                                                                     colors,
-                                                                     fPositions);
+            fColorsAndOffsetsBitmap =
+                    create_color_and_offset_bitmap(fColorCount, colors, fPositions);
             if (fColorsAndOffsetsBitmap.empty()) {
                 SKGPU_LOG_W("Couldn't create GradientShader's color and offset bitmap");
 
@@ -1418,9 +1426,12 @@
     }
 
     GradientShaderBlocks::GradientData data(type,
-                                            point0, point1,
-                                            radius0, radius1,
-                                            bias, scale,
+                                            point0,
+                                            point1,
+                                            radius0,
+                                            radius1,
+                                            bias,
+                                            scale,
                                             fTileMode,
                                             fColorCount,
                                             colors,
@@ -1428,9 +1439,12 @@
                                             std::move(proxy),
                                             fInterpolation);
 
-    make_interpolated_to_dst(keyContext, builder, gatherer,
-                             data, fInterpolation,
+    make_interpolated_to_dst(keyContext,
+                             builder,
+                             gatherer,
+                             data,
+                             fInterpolation,
                              xformedColors.fIntermediateColorSpace.get());
 }
 
-#endif // defined(SK_GRAPHITE)
+#endif  // defined(SK_GRAPHITE)
diff --git a/src/shaders/gradients/SkGradientShaderBase.h b/src/shaders/gradients/SkGradientBaseShader.h
similarity index 63%
rename from src/shaders/gradients/SkGradientShaderBase.h
rename to src/shaders/gradients/SkGradientBaseShader.h
index b103ea4..017a126 100644
--- a/src/shaders/gradients/SkGradientShaderBase.h
+++ b/src/shaders/gradients/SkGradientBaseShader.h
@@ -8,25 +8,34 @@
 #ifndef SkGradientShaderPriv_DEFINED
 #define SkGradientShaderPriv_DEFINED
 
-#include "include/effects/SkGradientShader.h"
-
+#include "include/core/SkColor.h"
+#include "include/core/SkColorSpace.h"
 #include "include/core/SkMatrix.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkScalar.h"
+#include "include/effects/SkGradientShader.h"
+#include "include/private/SkColorData.h"
+#include "include/private/base/SkAssert.h"
 #include "include/private/base/SkTArray.h"
 #include "include/private/base/SkTemplates.h"
-#include "src/core/SkVM.h"
 #include "src/shaders/SkShaderBase.h"
 
 #if defined(SK_GRAPHITE)
 #include "src/gpu/graphite/KeyHelpers.h"
 #endif
 
+#include <cstddef>
+#include <cstdint>
+
 class SkArenaAlloc;
-class SkColorSpace;
 class SkRasterPipeline;
 class SkReadBuffer;
+class SkShader;
 class SkWriteBuffer;
+enum class SkTileMode;
+struct SkStageRec;
 
-class SkGradientShaderBase : public SkShaderBase {
+class SkGradientBaseShader : public SkShaderBase {
 public:
     using Interpolation = SkGradientShader::Interpolation;
 
@@ -41,12 +50,12 @@
                    SkTileMode mode,
                    const Interpolation& interpolation);
 
-        const SkColor4f*    fColors;
+        const SkColor4f* fColors;
         sk_sp<SkColorSpace> fColorSpace;
-        const SkScalar*     fPositions;
-        int                 fColorCount;  // length of fColors (and fPositions, if not nullptr)
-        SkTileMode          fTileMode;
-        Interpolation       fInterpolation;
+        const SkScalar* fPositions;
+        int fColorCount;  // length of fColors (and fPositions, if not nullptr)
+        SkTileMode fTileMode;
+        Interpolation fInterpolation;
     };
 
     class DescriptorScope : public Descriptor {
@@ -57,11 +66,13 @@
 
     private:
         skia_private::STArray<16, SkColor4f, true> fColorStorage;
-        skia_private::STArray<16, SkScalar , true> fPositionStorage;
+        skia_private::STArray<16, SkScalar, true> fPositionStorage;
     };
 
-    SkGradientShaderBase(const Descriptor& desc, const SkMatrix& ptsToUnit);
-    ~SkGradientShaderBase() override;
+    SkGradientBaseShader(const Descriptor& desc, const SkMatrix& ptsToUnit);
+    ~SkGradientBaseShader() override;
+
+    ShaderType type() const final { return ShaderType::kGradientBase; }
 
     bool isOpaque() const override;
 
@@ -71,20 +82,26 @@
 
     const SkMatrix& getGradientMatrix() const { return fPtsToUnit; }
 
-    static bool ValidGradient(const SkColor4f colors[], int count, SkTileMode tileMode,
+    static bool ValidGradient(const SkColor4f colors[],
+                              int count,
+                              SkTileMode tileMode,
                               const Interpolation& interpolation);
 
-    static sk_sp<SkShader> MakeDegenerateGradient(const SkColor4f colors[], const SkScalar pos[],
-                                                  int colorCount, sk_sp<SkColorSpace> colorSpace,
+    static sk_sp<SkShader> MakeDegenerateGradient(const SkColor4f colors[],
+                                                  const SkScalar pos[],
+                                                  int colorCount,
+                                                  sk_sp<SkColorSpace> colorSpace,
                                                   SkTileMode mode);
 
     struct ColorStopOptimizer {
-        ColorStopOptimizer(const SkColor4f* colors, const SkScalar* pos, int count,
+        ColorStopOptimizer(const SkColor4f* colors,
+                           const SkScalar* pos,
+                           int count,
                            SkTileMode mode);
 
         const SkColor4f* fColors;
-        const SkScalar*  fPos;
-        int              fCount;
+        const SkScalar* fPos;
+        int fCount;
     };
 
     // The default SkScalarNearlyZero threshold of .0024 is too big and causes regressions for svg
@@ -98,29 +115,32 @@
 
     bool onAsLuminanceColor(SkColor*) const override;
 
-    bool appendStages(const SkStageRec&, const MatrixRec&) const override;
+    bool appendStages(const SkStageRec&, const SkShaders::MatrixRec&) const override;
 
 #if defined(SK_ENABLE_SKVM)
     skvm::Color program(skvm::Builder*,
                         skvm::Coord device,
                         skvm::Coord local,
                         skvm::Color paint,
-                        const MatrixRec&,
+                        const SkShaders::MatrixRec&,
                         const SkColorInfo& dstCS,
                         skvm::Uniforms* uniforms,
                         SkArenaAlloc* alloc) const override;
 #endif
 
-    virtual void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline,
+    virtual void appendGradientStages(SkArenaAlloc* alloc,
+                                      SkRasterPipeline* tPipeline,
                                       SkRasterPipeline* postPipeline) const = 0;
 #if defined(SK_ENABLE_SKVM)
     // Produce t from (x,y), modifying mask if it should be anything other than ~0.
-    virtual skvm::F32 transformT(skvm::Builder*, skvm::Uniforms*,
-                                 skvm::Coord coord, skvm::I32* mask) const = 0;
+    virtual skvm::F32 transformT(skvm::Builder*,
+                                 skvm::Uniforms*,
+                                 skvm::Coord coord,
+                                 skvm::I32* mask) const = 0;
 #endif
 
     const SkMatrix fPtsToUnit;
-    SkTileMode     fTileMode;
+    SkTileMode fTileMode;
 
 #if defined(SK_GRAPHITE)
     // When the number of stops exceeds Graphite's uniform-based limit the colors and offsets
@@ -132,9 +152,12 @@
                         skgpu::graphite::PaintParamsKeyBuilder*,
                         skgpu::graphite::PipelineDataGatherer*,
                         GradientType,
-                        SkPoint point0, SkPoint point1,
-                        float radius0, float radius1,
-                        float bias, float scale) const;
+                        SkPoint point0,
+                        SkPoint point1,
+                        float radius0,
+                        float radius1,
+                        float bias,
+                        float scale) const;
 #endif
 
 public:
@@ -154,13 +177,13 @@
         return fColors[i].toSkColor();
     }
 
-    SkColor4f*          fColors;       // points into fStorage
-    SkScalar*           fPositions;    // points into fStorage, or nullptr
-    int                 fColorCount;   // length of fColors (and fPositions, if not nullptr)
-    sk_sp<SkColorSpace> fColorSpace;   // color space of gradient stops
-    Interpolation       fInterpolation;
-    bool                fFirstStopIsImplicit;
-    bool                fLastStopIsImplicit;
+    SkColor4f* fColors;               // points into fStorage
+    SkScalar* fPositions;             // points into fStorage, or nullptr
+    int fColorCount;                  // length of fColors (and fPositions, if not nullptr)
+    sk_sp<SkColorSpace> fColorSpace;  // color space of gradient stops
+    Interpolation fInterpolation;
+    bool fFirstStopIsImplicit;
+    bool fLastStopIsImplicit;
 
     bool colorsAreOpaque() const { return fColorsAreOpaque; }
 
@@ -168,20 +191,18 @@
 
 private:
     // Reserve inline space for up to 4 stops.
-    inline static constexpr size_t kInlineStopCount   = 4;
-    inline static constexpr size_t kInlineStorageSize = (sizeof(SkColor4f) + sizeof(SkScalar))
-                                               * kInlineStopCount;
+    inline static constexpr size_t kInlineStopCount = 4;
+    inline static constexpr size_t kInlineStorageSize =
+            (sizeof(SkColor4f) + sizeof(SkScalar)) * kInlineStopCount;
     skia_private::AutoSTMalloc<kInlineStorageSize, uint8_t> fStorage;
 
-    bool                                        fColorsAreOpaque;
-
-    using INHERITED = SkShaderBase;
+    bool fColorsAreOpaque;
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 
 struct SkColor4fXformer {
-    SkColor4fXformer(const SkGradientShaderBase* shader, SkColorSpace* dst);
+    SkColor4fXformer(const SkGradientBaseShader* shader, SkColorSpace* dst);
 
     skia_private::STArray<4, SkPMColor4f, true> fColors;
     sk_sp<SkColorSpace> fIntermediateColorSpace;
@@ -193,9 +214,9 @@
     skia_private::STArray<2, SkColor4f, true> fColors4f;
 };
 
+void SkRegisterConicalGradientShaderFlattenable();
 void SkRegisterLinearGradientShaderFlattenable();
 void SkRegisterRadialGradientShaderFlattenable();
 void SkRegisterSweepGradientShaderFlattenable();
-void SkRegisterTwoPointConicalGradientShaderFlattenable();
 
 #endif
diff --git a/src/shaders/gradients/SkGradientShader.cpp b/src/shaders/gradients/SkGradientShader.cpp
deleted file mode 100644
index 7040d5d..0000000
--- a/src/shaders/gradients/SkGradientShader.cpp
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
- * Copyright 2006 The Android Open Source Project
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
diff --git a/src/shaders/gradients/SkLinearGradient.cpp b/src/shaders/gradients/SkLinearGradient.cpp
index 3839f54..079e597 100644
--- a/src/shaders/gradients/SkLinearGradient.cpp
+++ b/src/shaders/gradients/SkLinearGradient.cpp
@@ -7,9 +7,18 @@
 
 #include "src/shaders/gradients/SkLinearGradient.h"
 
+#include "include/core/SkColor.h"
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkScalar.h"
+#include "include/core/SkShader.h"
+#include "include/effects/SkGradientShader.h"
+#include "include/private/base/SkTArray.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkWriteBuffer.h"
 #include "src/shaders/SkLocalMatrixShader.h"
+#include "src/shaders/SkShaderBase.h"
 
 #if defined(SK_GRAPHITE)
 #include "src/gpu/graphite/KeyContext.h"
@@ -17,6 +26,13 @@
 #include "src/gpu/graphite/PaintParamsKey.h"
 #endif
 
+#include <cstdint>
+#include <utility>
+
+class SkArenaAlloc;
+class SkRasterPipeline;
+enum class SkTileMode;
+
 static SkMatrix pts_to_unit_matrix(const SkPoint pts[2]) {
     SkVector    vec = pts[1] - pts[0];
     SkScalar    mag = vec.length();
@@ -33,10 +49,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 SkLinearGradient::SkLinearGradient(const SkPoint pts[2], const Descriptor& desc)
-    : SkGradientShaderBase(desc, pts_to_unit_matrix(pts))
-    , fStart(pts[0])
-    , fEnd(pts[1]) {
-}
+        : SkGradientBaseShader(desc, pts_to_unit_matrix(pts)), fStart(pts[0]), fEnd(pts[1]) {}
 
 sk_sp<SkFlattenable> SkLinearGradient::CreateProc(SkReadBuffer& buffer) {
     DescriptorScope desc;
@@ -89,19 +102,6 @@
     return GradientType::kLinear;
 }
 
-/////////////////////////////////////////////////////////////////////
-
-#if defined(SK_GANESH)
-
-#include "src/gpu/ganesh/gradients/GrGradientShader.h"
-
-std::unique_ptr<GrFragmentProcessor> SkLinearGradient::asFragmentProcessor(
-        const GrFPArgs& args, const MatrixRec& mRec) const {
-    return GrGradientShader::MakeLinear(*this, args, mRec);
-}
-
-#endif
-
 #if defined(SK_GRAPHITE)
 void SkLinearGradient::addToKey(const skgpu::graphite::KeyContext& keyContext,
                                 skgpu::graphite::PaintParamsKeyBuilder* builder,
@@ -125,7 +125,7 @@
     if (!pts || !SkScalarIsFinite((pts[1] - pts[0]).length())) {
         return nullptr;
     }
-    if (!SkGradientShaderBase::ValidGradient(colors, colorCount, mode, interpolation)) {
+    if (!SkGradientBaseShader::ValidGradient(colors, colorCount, mode, interpolation)) {
         return nullptr;
     }
     if (1 == colorCount) {
@@ -136,19 +136,19 @@
     }
 
     if (SkScalarNearlyZero((pts[1] - pts[0]).length(),
-                           SkGradientShaderBase::kDegenerateThreshold)) {
+                           SkGradientBaseShader::kDegenerateThreshold)) {
         // Degenerate gradient, the only tricky complication is when in clamp mode, the limit of
         // the gradient approaches two half planes of solid color (first and last). However, they
         // are divided by the line perpendicular to the start and end point, which becomes undefined
         // once start and end are exactly the same, so just use the end color for a stable solution.
-        return SkGradientShaderBase::MakeDegenerateGradient(colors, pos, colorCount,
-                                                            std::move(colorSpace), mode);
+        return SkGradientBaseShader::MakeDegenerateGradient(
+                colors, pos, colorCount, std::move(colorSpace), mode);
     }
 
-    SkGradientShaderBase::ColorStopOptimizer opt(colors, pos, colorCount, mode);
+    SkGradientBaseShader::ColorStopOptimizer opt(colors, pos, colorCount, mode);
 
-    SkGradientShaderBase::Descriptor desc(opt.fColors, std::move(colorSpace), opt.fPos,
-                                          opt.fCount, mode, interpolation);
+    SkGradientBaseShader::Descriptor desc(
+            opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, interpolation);
     return SkLocalMatrixShader::MakeWrapped<SkLinearGradient>(localMatrix, pts, desc);
 }
 
diff --git a/src/shaders/gradients/SkLinearGradient.h b/src/shaders/gradients/SkLinearGradient.h
index e6efec5..572ebf1 100644
--- a/src/shaders/gradients/SkLinearGradient.h
+++ b/src/shaders/gradients/SkLinearGradient.h
@@ -8,17 +8,21 @@
 #ifndef SkLinearGradient_DEFINED
 #define SkLinearGradient_DEFINED
 
-#include "src/shaders/gradients/SkGradientShaderBase.h"
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkPoint.h"
+#include "src/shaders/gradients/SkGradientBaseShader.h"
 
-class SkLinearGradient final : public SkGradientShaderBase {
+class SkArenaAlloc;
+class SkMatrix;
+class SkRasterPipeline;
+class SkReadBuffer;
+class SkWriteBuffer;
+
+class SkLinearGradient final : public SkGradientBaseShader {
 public:
     SkLinearGradient(const SkPoint pts[2], const Descriptor&);
 
     GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override;
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
 #if defined(SK_GRAPHITE)
     void addToKey(const skgpu::graphite::KeyContext&,
                   skgpu::graphite::PaintParamsKeyBuilder*,
@@ -43,7 +47,7 @@
     class LinearGradient4fContext;
 
     friend class SkGradientShader;
-    using INHERITED = SkGradientShaderBase;
+    using INHERITED = SkGradientBaseShader;
     const SkPoint fStart;
     const SkPoint fEnd;
 };
diff --git a/src/shaders/gradients/SkRadialGradient.cpp b/src/shaders/gradients/SkRadialGradient.cpp
index 77adb28..c72f367 100644
--- a/src/shaders/gradients/SkRadialGradient.cpp
+++ b/src/shaders/gradients/SkRadialGradient.cpp
@@ -4,11 +4,22 @@
  * Use of this source code is governed by a BSD-style license that can be
  * found in the LICENSE file.
  */
+#include "src/shaders/gradients/SkRadialGradient.h"
 
+#include "include/core/SkColor.h"
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkShader.h"
+#include "include/effects/SkGradientShader.h"
+#include "include/private/base/SkTArray.h"
 #include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpList.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkWriteBuffer.h"
 #include "src/shaders/SkLocalMatrixShader.h"
+#include "src/shaders/SkShaderBase.h"
+#include "src/shaders/gradients/SkGradientBaseShader.h"
 
 #if defined(SK_GRAPHITE)
 #include "src/gpu/graphite/KeyContext.h"
@@ -16,12 +27,14 @@
 #include "src/gpu/graphite/PaintParamsKey.h"
 #endif
 
-#include "src/shaders/gradients/SkGradientShaderBase.h"
+#include <cstdint>
+#include <utility>
 
-namespace {
+class SkArenaAlloc;
+enum class SkTileMode;
 
-SkMatrix rad_to_unit_matrix(const SkPoint& center, SkScalar radius) {
-    SkScalar    inv = SkScalarInvert(radius);
+static SkMatrix rad_to_unit_matrix(const SkPoint& center, SkScalar radius) {
+    SkScalar inv = SkScalarInvert(radius);
 
     SkMatrix matrix;
     matrix.setTranslate(-center.fX, -center.fY);
@@ -29,47 +42,10 @@
     return matrix;
 }
 
-}  // namespace
-
-/////////////////////////////////////////////////////////////////////
-class SkRadialGradient final : public SkGradientShaderBase {
-public:
-    SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor&);
-
-    GradientType asGradient(GradientInfo* info, SkMatrix* matrix) const override;
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
-#if defined(SK_GRAPHITE)
-    void addToKey(const skgpu::graphite::KeyContext&,
-                  skgpu::graphite::PaintParamsKeyBuilder*,
-                  skgpu::graphite::PipelineDataGatherer*) const override;
-#endif
-protected:
-    SkRadialGradient(SkReadBuffer& buffer);
-    void flatten(SkWriteBuffer& buffer) const override;
-
-    void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline,
-                              SkRasterPipeline* postPipeline) const override;
-#if defined(SK_ENABLE_SKVM)
-    skvm::F32 transformT(skvm::Builder*, skvm::Uniforms*,
-                         skvm::Coord coord, skvm::I32* mask) const final;
-#endif
-
-private:
-    friend void ::SkRegisterRadialGradientShaderFlattenable();
-    SK_FLATTENABLE_HOOKS(SkRadialGradient)
-
-    const SkPoint fCenter;
-    const SkScalar fRadius;
-};
-
 SkRadialGradient::SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor& desc)
-    : SkGradientShaderBase(desc, rad_to_unit_matrix(center, radius))
-    , fCenter(center)
-    , fRadius(radius) {
-}
+        : SkGradientBaseShader(desc, rad_to_unit_matrix(center, radius))
+        , fCenter(center)
+        , fRadius(radius) {}
 
 SkShaderBase::GradientType SkRadialGradient::asGradient(GradientInfo* info,
                                                         SkMatrix* localMatrix) const {
@@ -104,7 +80,7 @@
 }
 
 void SkRadialGradient::flatten(SkWriteBuffer& buffer) const {
-    this->SkGradientShaderBase::flatten(buffer);
+    this->SkGradientBaseShader::flatten(buffer);
     buffer.writePoint(fCenter);
     buffer.writeScalar(fRadius);
 }
@@ -121,29 +97,6 @@
 }
 #endif
 
-/////////////////////////////////////////////////////////////////////
-
-#if defined(SK_GANESH)
-
-#include "src/core/SkRuntimeEffectPriv.h"
-#include "src/gpu/ganesh/effects/GrSkSLFP.h"
-#include "src/gpu/ganesh/gradients/GrGradientShader.h"
-
-std::unique_ptr<GrFragmentProcessor>
-SkRadialGradient::asFragmentProcessor(const GrFPArgs& args, const MatrixRec& mRec) const {
-    static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
-        "half4 main(float2 coord) {"
-            "return half4(half(length(coord)), 1, 0, 0);" // y = 1 for always valid
-        "}"
-    );
-    // The radial gradient never rejects a pixel so it doesn't change opacity
-    auto fp = GrSkSLFP::Make(effect, "RadialLayout", /*inputFP=*/nullptr,
-                             GrSkSLFP::OptFlags::kPreservesOpaqueInput);
-    return GrGradientShader::MakeGradientFP(*this, args, mRec, std::move(fp));
-}
-
-#endif
-
 #if defined(SK_GRAPHITE)
 void SkRadialGradient::addToKey(const skgpu::graphite::KeyContext& keyContext,
                                 skgpu::graphite::PaintParamsKeyBuilder* builder,
@@ -167,7 +120,7 @@
     if (radius < 0) {
         return nullptr;
     }
-    if (!SkGradientShaderBase::ValidGradient(colors, colorCount, mode, interpolation)) {
+    if (!SkGradientBaseShader::ValidGradient(colors, colorCount, mode, interpolation)) {
         return nullptr;
     }
     if (1 == colorCount) {
@@ -177,16 +130,16 @@
         return nullptr;
     }
 
-    if (SkScalarNearlyZero(radius, SkGradientShaderBase::kDegenerateThreshold)) {
+    if (SkScalarNearlyZero(radius, SkGradientBaseShader::kDegenerateThreshold)) {
         // Degenerate gradient optimization, and no special logic needed for clamped radial gradient
-        return SkGradientShaderBase::MakeDegenerateGradient(colors, pos, colorCount,
-                                                            std::move(colorSpace), mode);
+        return SkGradientBaseShader::MakeDegenerateGradient(
+                colors, pos, colorCount, std::move(colorSpace), mode);
     }
 
-    SkGradientShaderBase::ColorStopOptimizer opt(colors, pos, colorCount, mode);
+    SkGradientBaseShader::ColorStopOptimizer opt(colors, pos, colorCount, mode);
 
-    SkGradientShaderBase::Descriptor desc(opt.fColors, std::move(colorSpace), opt.fPos,
-                                          opt.fCount, mode, interpolation);
+    SkGradientBaseShader::Descriptor desc(
+            opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, interpolation);
     return SkLocalMatrixShader::MakeWrapped<SkRadialGradient>(localMatrix, center, radius, desc);
 }
 
diff --git a/src/shaders/gradients/SkRadialGradient.h b/src/shaders/gradients/SkRadialGradient.h
new file mode 100644
index 0000000..0f882d1
--- /dev/null
+++ b/src/shaders/gradients/SkRadialGradient.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkRadialGradient_DEFINED
+#define SkRadialGradient_DEFINED
+
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkScalar.h"
+#include "src/shaders/gradients/SkGradientBaseShader.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyContext.h"
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif
+
+class SkArenaAlloc;
+class SkMatrix;
+class SkRasterPipeline;
+class SkReadBuffer;
+class SkWriteBuffer;
+
+class SkRadialGradient final : public SkGradientBaseShader {
+public:
+    SkRadialGradient(const SkPoint& center, SkScalar radius, const Descriptor&);
+
+    GradientType asGradient(GradientInfo* info, SkMatrix* matrix) const override;
+#if defined(SK_GRAPHITE)
+    void addToKey(const skgpu::graphite::KeyContext&,
+                  skgpu::graphite::PaintParamsKeyBuilder*,
+                  skgpu::graphite::PipelineDataGatherer*) const override;
+#endif
+protected:
+    SkRadialGradient(SkReadBuffer& buffer);
+    void flatten(SkWriteBuffer& buffer) const override;
+
+    void appendGradientStages(SkArenaAlloc* alloc,
+                              SkRasterPipeline* tPipeline,
+                              SkRasterPipeline* postPipeline) const override;
+#if defined(SK_ENABLE_SKVM)
+    skvm::F32 transformT(skvm::Builder*,
+                         skvm::Uniforms*,
+                         skvm::Coord coord,
+                         skvm::I32* mask) const final;
+#endif
+
+private:
+    friend void ::SkRegisterRadialGradientShaderFlattenable();
+    SK_FLATTENABLE_HOOKS(SkRadialGradient)
+
+    const SkPoint fCenter;
+    const SkScalar fRadius;
+};
+
+#endif
diff --git a/src/shaders/gradients/SkSweepGradient.cpp b/src/shaders/gradients/SkSweepGradient.cpp
index dbcfa94..ce5af4a 100644
--- a/src/shaders/gradients/SkSweepGradient.cpp
+++ b/src/shaders/gradients/SkSweepGradient.cpp
@@ -5,11 +5,25 @@
  * found in the LICENSE file.
  */
 
+#include "src/shaders/gradients/SkSweepGradient.h"
+
+#include "include/core/SkColor.h"
+#include "include/core/SkColorSpace.h"
+#include "include/core/SkMatrix.h"
+#include "include/core/SkRefCnt.h"
+#include "include/core/SkShader.h"
+#include "include/core/SkTileMode.h"
+#include "include/effects/SkGradientShader.h"
+#include "include/private/base/SkAssert.h"
 #include "include/private/base/SkFloatingPoint.h"
+#include "include/private/base/SkTArray.h"
 #include "src/core/SkRasterPipeline.h"
+#include "src/core/SkRasterPipelineOpList.h"
 #include "src/core/SkReadBuffer.h"
 #include "src/core/SkWriteBuffer.h"
 #include "src/shaders/SkLocalMatrixShader.h"
+#include "src/shaders/SkShaderBase.h"
+#include "src/shaders/gradients/SkGradientBaseShader.h"
 
 #if defined(SK_GRAPHITE)
 #include "src/gpu/graphite/KeyContext.h"
@@ -17,50 +31,20 @@
 #include "src/gpu/graphite/PaintParamsKey.h"
 #endif
 
-#include "src/shaders/gradients/SkGradientShaderBase.h"
+#include <cstdint>
+#include <tuple>
+#include <utility>
 
-class SkSweepGradient final : public SkGradientShaderBase {
-public:
-    SkSweepGradient(const SkPoint& center, SkScalar t0, SkScalar t1, const Descriptor&);
+class SkArenaAlloc;
 
-    GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override;
-
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
-#if defined(SK_GRAPHITE)
-    void addToKey(const skgpu::graphite::KeyContext&,
-                  skgpu::graphite::PaintParamsKeyBuilder*,
-                  skgpu::graphite::PipelineDataGatherer*) const override;
-#endif
-
-protected:
-    void flatten(SkWriteBuffer& buffer) const override;
-
-    void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline,
-                              SkRasterPipeline* postPipeline) const override;
-#if defined(SK_ENABLE_SKVM)
-    skvm::F32 transformT(skvm::Builder*, skvm::Uniforms*,
-                         skvm::Coord coord, skvm::I32* mask) const final;
-#endif
-
-private:
-    friend void ::SkRegisterSweepGradientShaderFlattenable();
-    SK_FLATTENABLE_HOOKS(SkSweepGradient)
-
-    const SkPoint  fCenter;
-    const SkScalar fTBias;
-    const SkScalar fTScale;
-};
-
-SkSweepGradient::SkSweepGradient(const SkPoint& center, SkScalar t0, SkScalar t1,
+SkSweepGradient::SkSweepGradient(const SkPoint& center,
+                                 SkScalar t0,
+                                 SkScalar t1,
                                  const Descriptor& desc)
-    : SkGradientShaderBase(desc, SkMatrix::Translate(-center.x(), -center.y()))
-    , fCenter(center)
-    , fTBias(-t0)
-    , fTScale(1 / (t1 - t0))
-{
+        : SkGradientBaseShader(desc, SkMatrix::Translate(-center.x(), -center.y()))
+        , fCenter(center)
+        , fTBias(-t0)
+        , fTScale(1 / (t1 - t0)) {
     SkASSERT(t0 < t1);
 }
 
@@ -105,7 +89,7 @@
 }
 
 void SkSweepGradient::flatten(SkWriteBuffer& buffer) const {
-    this->SkGradientShaderBase::flatten(buffer);
+    this->SkGradientBaseShader::flatten(buffer);
     buffer.writePoint(fCenter);
     buffer.writeScalar(fTBias);
     buffer.writeScalar(fTScale);
@@ -148,52 +132,6 @@
 }
 #endif
 
-/////////////////////////////////////////////////////////////////////
-
-#if defined(SK_GANESH)
-
-#include "src/core/SkRuntimeEffectPriv.h"
-#include "src/gpu/ganesh/GrCaps.h"
-#include "src/gpu/ganesh/GrRecordingContextPriv.h"
-#include "src/gpu/ganesh/effects/GrSkSLFP.h"
-#include "src/gpu/ganesh/gradients/GrGradientShader.h"
-
-std::unique_ptr<GrFragmentProcessor> SkSweepGradient::asFragmentProcessor(
-        const GrFPArgs& args, const MatrixRec& mRec) const {
-    // On some devices they incorrectly implement atan2(y,x) as atan(y/x). In actuality it is
-    // atan2(y,x) = 2 * atan(y / (sqrt(x^2 + y^2) + x)). So to work around this we pass in (sqrt(x^2
-    // + y^2) + x) as the second parameter to atan2 in these cases. We let the device handle the
-    // undefined behavior of the second paramenter being 0 instead of doing the divide ourselves and
-    // using atan instead.
-    int useAtanWorkaround =
-            args.fContext->priv().caps()->shaderCaps()->fAtan2ImplementedAsAtanYOverX;
-    static const SkRuntimeEffect* effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
-        "uniform half bias;"
-        "uniform half scale;"
-        "uniform int useAtanWorkaround;"  // specialized
-
-        "half4 main(float2 coord) {"
-            "half angle = bool(useAtanWorkaround)"
-                    "? half(2 * atan(-coord.y, length(coord) - coord.x))"
-                    ": half(atan(-coord.y, -coord.x));"
-
-            // 0.1591549430918 is 1/(2*pi), used since atan returns values [-pi, pi]
-            "half t = (angle * 0.1591549430918 + 0.5 + bias) * scale;"
-            "return half4(t, 1, 0, 0);" // y = 1 for always valid
-        "}"
-    );
-
-    // The sweep gradient never rejects a pixel so it doesn't change opacity
-    auto fp = GrSkSLFP::Make(effect, "SweepLayout", /*inputFP=*/nullptr,
-                             GrSkSLFP::OptFlags::kPreservesOpaqueInput,
-                             "bias", fTBias,
-                             "scale", fTScale,
-                             "useAtanWorkaround", GrSkSLFP::Specialize(useAtanWorkaround));
-    return GrGradientShader::MakeGradientFP(*this, args, mRec, std::move(fp));
-}
-
-#endif
-
 #if defined(SK_GRAPHITE)
 void SkSweepGradient::addToKey(const skgpu::graphite::KeyContext& keyContext,
                                skgpu::graphite::PaintParamsKeyBuilder* builder,
@@ -216,7 +154,7 @@
                                             SkScalar endAngle,
                                             const Interpolation& interpolation,
                                             const SkMatrix* localMatrix) {
-    if (!SkGradientShaderBase::ValidGradient(colors, colorCount, mode, interpolation)) {
+    if (!SkGradientBaseShader::ValidGradient(colors, colorCount, mode, interpolation)) {
         return nullptr;
     }
     if (1 == colorCount) {
@@ -229,10 +167,10 @@
         return nullptr;
     }
 
-    if (SkScalarNearlyEqual(startAngle, endAngle, SkGradientShaderBase::kDegenerateThreshold)) {
+    if (SkScalarNearlyEqual(startAngle, endAngle, SkGradientBaseShader::kDegenerateThreshold)) {
         // Degenerate gradient, which should follow default degenerate behavior unless it is
         // clamped and the angle is greater than 0.
-        if (mode == SkTileMode::kClamp && endAngle > SkGradientShaderBase::kDegenerateThreshold) {
+        if (mode == SkTileMode::kClamp && endAngle > SkGradientBaseShader::kDegenerateThreshold) {
             // In this case, the first color is repeated from 0 to the angle, then a hardstop
             // switches to the last color (all other colors are compressed to the infinitely thin
             // interpolation region).
@@ -241,8 +179,8 @@
             return MakeSweep(cx, cy, reColors, std::move(colorSpace), clampPos, 3, mode, 0,
                              endAngle, interpolation, localMatrix);
         } else {
-            return SkGradientShaderBase::MakeDegenerateGradient(colors, pos, colorCount,
-                                                                std::move(colorSpace), mode);
+            return SkGradientBaseShader::MakeDegenerateGradient(
+                    colors, pos, colorCount, std::move(colorSpace), mode);
         }
     }
 
@@ -251,10 +189,10 @@
         mode = SkTileMode::kClamp;
     }
 
-    SkGradientShaderBase::ColorStopOptimizer opt(colors, pos, colorCount, mode);
+    SkGradientBaseShader::ColorStopOptimizer opt(colors, pos, colorCount, mode);
 
-    SkGradientShaderBase::Descriptor desc(opt.fColors, std::move(colorSpace), opt.fPos,
-                                          opt.fCount, mode, interpolation);
+    SkGradientBaseShader::Descriptor desc(
+            opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, interpolation);
 
     const SkScalar t0 = startAngle / 360,
                    t1 =   endAngle / 360;
diff --git a/src/shaders/gradients/SkSweepGradient.h b/src/shaders/gradients/SkSweepGradient.h
new file mode 100644
index 0000000..997435c
--- /dev/null
+++ b/src/shaders/gradients/SkSweepGradient.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkSweepGradientShader_DEFINED
+#define SkSweepGradientShader_DEFINED
+
+#include "include/core/SkFlattenable.h"
+#include "include/core/SkPoint.h"
+#include "include/core/SkScalar.h"
+#include "src/shaders/gradients/SkGradientBaseShader.h"
+
+#if defined(SK_GRAPHITE)
+#include "src/gpu/graphite/KeyContext.h"
+#include "src/gpu/graphite/KeyHelpers.h"
+#include "src/gpu/graphite/PaintParamsKey.h"
+#endif
+
+class SkArenaAlloc;
+class SkMatrix;
+class SkRasterPipeline;
+class SkReadBuffer;
+class SkWriteBuffer;
+
+class SkSweepGradient final : public SkGradientBaseShader {
+public:
+    SkSweepGradient(const SkPoint& center, SkScalar t0, SkScalar t1, const Descriptor&);
+
+    GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override;
+
+#if defined(SK_GRAPHITE)
+    void addToKey(const skgpu::graphite::KeyContext&,
+                  skgpu::graphite::PaintParamsKeyBuilder*,
+                  skgpu::graphite::PipelineDataGatherer*) const override;
+#endif
+
+    SkScalar tBias() const { return fTBias; }
+    SkScalar tScale() const { return fTScale; }
+
+protected:
+    void flatten(SkWriteBuffer& buffer) const override;
+
+    void appendGradientStages(SkArenaAlloc* alloc,
+                              SkRasterPipeline* tPipeline,
+                              SkRasterPipeline* postPipeline) const override;
+#if defined(SK_ENABLE_SKVM)
+    skvm::F32 transformT(skvm::Builder*,
+                         skvm::Uniforms*,
+                         skvm::Coord coord,
+                         skvm::I32* mask) const final;
+#endif
+
+private:
+    friend void ::SkRegisterSweepGradientShaderFlattenable();
+    SK_FLATTENABLE_HOOKS(SkSweepGradient)
+
+    const SkPoint fCenter;
+    const SkScalar fTBias;
+    const SkScalar fTScale;
+};
+
+#endif
diff --git a/src/shaders/gradients/SkTwoPointConicalGradient.cpp b/src/shaders/gradients/SkTwoPointConicalGradient.cpp
deleted file mode 100644
index 8639452..0000000
--- a/src/shaders/gradients/SkTwoPointConicalGradient.cpp
+++ /dev/null
@@ -1,647 +0,0 @@
-/*
- * Copyright 2012 Google Inc.
- *
- * Use of this source code is governed by a BSD-style license that can be
- * found in the LICENSE file.
- */
-
-#include "include/private/base/SkFloatingPoint.h"
-#include "src/core/SkRasterPipeline.h"
-#include "src/core/SkReadBuffer.h"
-#include "src/core/SkWriteBuffer.h"
-#include "src/shaders/SkLocalMatrixShader.h"
-#include "src/shaders/gradients/SkGradientShaderBase.h"
-
-#include <utility>
-
-#if defined(SK_GRAPHITE)
-#include "src/gpu/graphite/KeyContext.h"
-#include "src/gpu/graphite/KeyHelpers.h"
-#include "src/gpu/graphite/PaintParamsKey.h"
-#endif
-
-// Please see https://skia.org/dev/design/conical for how our shader works.
-
-class SkTwoPointConicalGradient final : public SkGradientShaderBase {
-public:
-    // See https://skia.org/dev/design/conical for what focal data means and how our shader works.
-    // We make it public so the GPU shader can also use it.
-    struct FocalData {
-        SkScalar    fR1;        // r1 after mapping focal point to (0, 0)
-        SkScalar    fFocalX;    // f
-        bool        fIsSwapped; // whether we swapped r0, r1
-
-        // The input r0, r1 are the radii when we map centers to {(0, 0), (1, 0)}.
-        // We'll post concat matrix with our transformation matrix that maps focal point to (0, 0).
-        // Returns true if the set succeeded
-        bool set(SkScalar r0, SkScalar r1, SkMatrix* matrix);
-
-        // Whether the focal point (0, 0) is on the end circle with center (1, 0) and radius r1. If
-        // this is true, it's as if an aircraft is flying at Mach 1 and all circles (soundwaves)
-        // will go through the focal point (aircraft). In our previous implementations, this was
-        // known as the edge case where the inside circle touches the outside circle (on the focal
-        // point). If we were to solve for t bruteforcely using a quadratic equation, this case
-        // implies that the quadratic equation degenerates to a linear equation.
-        bool isFocalOnCircle() const { return SkScalarNearlyZero(1 - fR1); }
-
-        bool isSwapped() const { return fIsSwapped; }
-        bool isWellBehaved() const { return !this->isFocalOnCircle() && fR1 > 1; }
-        bool isNativelyFocal() const { return SkScalarNearlyZero(fFocalX); }
-    };
-
-    enum class Type {
-        kRadial,
-        kStrip,
-        kFocal
-    };
-
-    static sk_sp<SkShader> Create(const SkPoint& start, SkScalar startRadius,
-                                  const SkPoint& end,   SkScalar endRadius,
-                                  const Descriptor&, const SkMatrix* localMatrix);
-
-    GradientType asGradient(GradientInfo* info, SkMatrix* localMatrix) const override;
-#if defined(SK_GANESH)
-    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(const GrFPArgs&,
-                                                             const MatrixRec&) const override;
-#endif
-#if defined(SK_GRAPHITE)
-    void addToKey(const skgpu::graphite::KeyContext&,
-                  skgpu::graphite::PaintParamsKeyBuilder*,
-                  skgpu::graphite::PipelineDataGatherer*) const override;
-#endif
-    bool isOpaque() const override;
-
-    SkScalar getCenterX1() const { return SkPoint::Distance(fCenter1, fCenter2); }
-    SkScalar getStartRadius() const { return fRadius1; }
-    SkScalar getDiffRadius() const { return fRadius2 - fRadius1; }
-    const SkPoint& getStartCenter() const { return fCenter1; }
-    const SkPoint& getEndCenter() const { return fCenter2; }
-    SkScalar getEndRadius() const { return fRadius2; }
-
-    Type getType() const { return fType; }
-    const FocalData& getFocalData() const { return fFocalData; }
-
-    SkTwoPointConicalGradient(const SkPoint& c0, SkScalar r0,
-                              const SkPoint& c1, SkScalar r1,
-                              const Descriptor&, Type, const SkMatrix&, const FocalData&);
-
-protected:
-    void flatten(SkWriteBuffer& buffer) const override;
-
-    void appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* tPipeline,
-                              SkRasterPipeline* postPipeline) const override;
-#if defined(SK_ENABLE_SKVM)
-    skvm::F32 transformT(skvm::Builder*, skvm::Uniforms*,
-                         skvm::Coord coord, skvm::I32* mask) const final;
-#endif
-
-private:
-    friend void ::SkRegisterTwoPointConicalGradientShaderFlattenable();
-    SK_FLATTENABLE_HOOKS(SkTwoPointConicalGradient)
-
-    SkPoint  fCenter1;
-    SkPoint  fCenter2;
-    SkScalar fRadius1;
-    SkScalar fRadius2;
-    Type     fType;
-
-    FocalData fFocalData;
-};
-
-bool SkTwoPointConicalGradient::FocalData::set(SkScalar r0, SkScalar r1, SkMatrix* matrix) {
-    fIsSwapped = false;
-    fFocalX = sk_ieee_float_divide(r0, (r0 - r1));
-    if (SkScalarNearlyZero(fFocalX - 1)) {
-        // swap r0, r1
-        matrix->postTranslate(-1, 0);
-        matrix->postScale(-1, 1);
-        std::swap(r0, r1);
-        fFocalX = 0; // because r0 is now 0
-        fIsSwapped = true;
-    }
-
-    // Map {focal point, (1, 0)} to {(0, 0), (1, 0)}
-    const SkPoint from[2]   = { {fFocalX, 0}, {1, 0} };
-    const SkPoint to[2]     = { {0, 0}, {1, 0} };
-    SkMatrix focalMatrix;
-    if (!focalMatrix.setPolyToPoly(from, to, 2)) {
-        return false;
-    }
-    matrix->postConcat(focalMatrix);
-    fR1 = r1 / SkScalarAbs(1 - fFocalX); // focalMatrix has a scale of 1/(1-f)
-
-    // The following transformations are just to accelerate the shader computation by saving
-    // some arithmatic operations.
-    if (this->isFocalOnCircle()) {
-        matrix->postScale(0.5, 0.5);
-    } else {
-        matrix->postScale(fR1 / (fR1 * fR1 - 1), 1 / sqrt(SkScalarAbs(fR1 * fR1 - 1)));
-    }
-    matrix->postScale(SkScalarAbs(1 - fFocalX), SkScalarAbs(1 - fFocalX)); // scale |1 - f|
-    return true;
-}
-
-sk_sp<SkShader> SkTwoPointConicalGradient::Create(const SkPoint& c0, SkScalar r0,
-                                                  const SkPoint& c1, SkScalar r1,
-                                                  const Descriptor& desc,
-                                                  const SkMatrix* localMatrix) {
-    SkMatrix gradientMatrix;
-    Type     gradientType;
-
-    if (SkScalarNearlyZero((c0 - c1).length())) {
-        if (SkScalarNearlyZero(std::max(r0, r1)) || SkScalarNearlyEqual(r0, r1)) {
-            // Degenerate case; avoid dividing by zero. Should have been caught by caller but
-            // just in case, recheck here.
-            return nullptr;
-        }
-        // Concentric case: we can pretend we're radial (with a tiny twist).
-        const SkScalar scale = sk_ieee_float_divide(1, std::max(r0, r1));
-        gradientMatrix = SkMatrix::Translate(-c1.x(), -c1.y());
-        gradientMatrix.postScale(scale, scale);
-
-        gradientType = Type::kRadial;
-    } else {
-        const SkPoint centers[2] = { c0    , c1     };
-        const SkPoint unitvec[2] = { {0, 0}, {1, 0} };
-
-        if (!gradientMatrix.setPolyToPoly(centers, unitvec, 2)) {
-            // Degenerate case.
-            return nullptr;
-        }
-
-        gradientType = SkScalarNearlyZero(r1 - r0) ? Type::kStrip : Type::kFocal;
-    }
-
-    FocalData focalData;
-    if (gradientType == Type::kFocal) {
-        const auto dCenter = (c0 - c1).length();
-        if (!focalData.set(r0 / dCenter, r1 / dCenter, &gradientMatrix)) {
-            return nullptr;
-        }
-    }
-    return SkLocalMatrixShader::MakeWrapped<SkTwoPointConicalGradient>(localMatrix,
-                                                                       c0, r0,
-                                                                       c1, r1,
-                                                                       desc,
-                                                                       gradientType,
-                                                                       gradientMatrix,
-                                                                       focalData);
-}
-
-SkTwoPointConicalGradient::SkTwoPointConicalGradient(
-        const SkPoint& start, SkScalar startRadius,
-        const SkPoint& end, SkScalar endRadius,
-        const Descriptor& desc, Type type, const SkMatrix& gradientMatrix, const FocalData& data)
-    : SkGradientShaderBase(desc, gradientMatrix)
-    , fCenter1(start)
-    , fCenter2(end)
-    , fRadius1(startRadius)
-    , fRadius2(endRadius)
-    , fType(type)
-{
-    // this is degenerate, and should be caught by our caller
-    SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
-    if (type == Type::kFocal) {
-        fFocalData = data;
-    }
-}
-
-bool SkTwoPointConicalGradient::isOpaque() const {
-    // Because areas outside the cone are left untouched, we cannot treat the
-    // shader as opaque even if the gradient itself is opaque.
-    // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
-    return false;
-}
-
-// Returns the original non-sorted version of the gradient
-SkShaderBase::GradientType SkTwoPointConicalGradient::asGradient(GradientInfo* info,
-                                                                 SkMatrix* localMatrix) const {
-    if (info) {
-        commonAsAGradient(info);
-        info->fPoint[0] = fCenter1;
-        info->fPoint[1] = fCenter2;
-        info->fRadius[0] = fRadius1;
-        info->fRadius[1] = fRadius2;
-    }
-    if (localMatrix) {
-        *localMatrix = SkMatrix::I();
-    }
-    return GradientType::kConical;
-}
-
-sk_sp<SkFlattenable> SkTwoPointConicalGradient::CreateProc(SkReadBuffer& buffer) {
-    DescriptorScope desc;
-    SkMatrix legacyLocalMatrix;
-    if (!desc.unflatten(buffer, &legacyLocalMatrix)) {
-        return nullptr;
-    }
-    SkPoint c1 = buffer.readPoint();
-    SkPoint c2 = buffer.readPoint();
-    SkScalar r1 = buffer.readScalar();
-    SkScalar r2 = buffer.readScalar();
-
-    if (!buffer.isValid()) {
-        return nullptr;
-    }
-    return SkGradientShader::MakeTwoPointConical(c1, r1,
-                                                 c2, r2,
-                                                 desc.fColors,
-                                                 std::move(desc.fColorSpace),
-                                                 desc.fPositions,
-                                                 desc.fColorCount,
-                                                 desc.fTileMode,
-                                                 desc.fInterpolation,
-                                                 &legacyLocalMatrix);
-}
-
-void SkTwoPointConicalGradient::flatten(SkWriteBuffer& buffer) const {
-    this->SkGradientShaderBase::flatten(buffer);
-    buffer.writePoint(fCenter1);
-    buffer.writePoint(fCenter2);
-    buffer.writeScalar(fRadius1);
-    buffer.writeScalar(fRadius2);
-}
-
-void SkTwoPointConicalGradient::appendGradientStages(SkArenaAlloc* alloc, SkRasterPipeline* p,
-                                                     SkRasterPipeline* postPipeline) const {
-    const auto dRadius = fRadius2 - fRadius1;
-
-    if (fType == Type::kRadial) {
-        p->append(SkRasterPipelineOp::xy_to_radius);
-
-        // Tiny twist: radial computes a t for [0, r2], but we want a t for [r1, r2].
-        auto scale =  std::max(fRadius1, fRadius2) / dRadius;
-        auto bias  = -fRadius1 / dRadius;
-
-        p->append_matrix(alloc, SkMatrix::Translate(bias, 0) * SkMatrix::Scale(scale, 1));
-        return;
-    }
-
-    if (fType == Type::kStrip) {
-        auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
-        SkScalar scaledR0 = fRadius1 / this->getCenterX1();
-        ctx->fP0 = scaledR0 * scaledR0;
-        p->append(SkRasterPipelineOp::xy_to_2pt_conical_strip, ctx);
-        p->append(SkRasterPipelineOp::mask_2pt_conical_nan, ctx);
-        postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask);
-        return;
-    }
-
-    auto* ctx = alloc->make<SkRasterPipeline_2PtConicalCtx>();
-    ctx->fP0 = 1/fFocalData.fR1;
-    ctx->fP1 = fFocalData.fFocalX;
-
-    if (fFocalData.isFocalOnCircle()) {
-        p->append(SkRasterPipelineOp::xy_to_2pt_conical_focal_on_circle);
-    } else if (fFocalData.isWellBehaved()) {
-        p->append(SkRasterPipelineOp::xy_to_2pt_conical_well_behaved, ctx);
-    } else if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
-        p->append(SkRasterPipelineOp::xy_to_2pt_conical_smaller, ctx);
-    } else {
-        p->append(SkRasterPipelineOp::xy_to_2pt_conical_greater, ctx);
-    }
-
-    if (!fFocalData.isWellBehaved()) {
-        p->append(SkRasterPipelineOp::mask_2pt_conical_degenerates, ctx);
-    }
-    if (1 - fFocalData.fFocalX < 0) {
-        p->append(SkRasterPipelineOp::negate_x);
-    }
-    if (!fFocalData.isNativelyFocal()) {
-        p->append(SkRasterPipelineOp::alter_2pt_conical_compensate_focal, ctx);
-    }
-    if (fFocalData.isSwapped()) {
-        p->append(SkRasterPipelineOp::alter_2pt_conical_unswap);
-    }
-    if (!fFocalData.isWellBehaved()) {
-        postPipeline->append(SkRasterPipelineOp::apply_vector_mask, &ctx->fMask);
-    }
-}
-
-#if defined(SK_ENABLE_SKVM)
-skvm::F32 SkTwoPointConicalGradient::transformT(skvm::Builder* p, skvm::Uniforms* uniforms,
-                                                skvm::Coord coord, skvm::I32* mask) const {
-    auto mag = [](skvm::F32 x, skvm::F32 y) { return sqrt(x*x + y*y); };
-
-    // See https://skia.org/dev/design/conical, and appendStages() above.
-    // There's a lot going on here, and I'm not really sure what's independent
-    // or disjoint, what can be reordered, simplified, etc.  Tweak carefully.
-
-    const skvm::F32 x = coord.x,
-                    y = coord.y;
-    if (fType == Type::kRadial) {
-        float denom = 1.0f / (fRadius2 - fRadius1),
-              scale = std::max(fRadius1, fRadius2) * denom,
-               bias =                  -fRadius1 * denom;
-        return mag(x,y) * p->uniformF(uniforms->pushF(scale))
-                        + p->uniformF(uniforms->pushF(bias ));
-    }
-
-    if (fType == Type::kStrip) {
-        float r = fRadius1 / this->getCenterX1();
-        skvm::F32 t = x + sqrt(p->uniformF(uniforms->pushF(r*r)) - y*y);
-
-        *mask = (t == t);   // t != NaN
-        return t;
-    }
-
-    const skvm::F32 invR1 = p->uniformF(uniforms->pushF(1 / fFocalData.fR1));
-
-    skvm::F32 t;
-    if (fFocalData.isFocalOnCircle()) {
-        t = (y/x) * y + x;       // (x^2 + y^2) / x  ~~>  x + y^2/x  ~~>  y/x * y + x
-    } else if (fFocalData.isWellBehaved()) {
-        t = mag(x,y) - x*invR1;
-    } else {
-        skvm::F32 k = sqrt(x*x - y*y);
-        if (fFocalData.isSwapped() || 1 - fFocalData.fFocalX < 0) {
-            k = -k;
-        }
-        t = k - x*invR1;
-    }
-
-    if (!fFocalData.isWellBehaved()) {
-        // TODO: not sure why we consider t == 0 degenerate
-        *mask = (t > 0.0f);  // and implicitly, t != NaN
-    }
-
-    const skvm::F32 focalX = p->uniformF(uniforms->pushF(fFocalData.fFocalX));
-    if (1 - fFocalData.fFocalX < 0)    { t = -t; }
-    if (!fFocalData.isNativelyFocal()) { t += focalX; }
-    if ( fFocalData.isSwapped())       { t = 1.0f - t; }
-    return t;
-}
-#endif
-
-/////////////////////////////////////////////////////////////////////
-
-#if defined(SK_GANESH)
-
-#include "src/core/SkRuntimeEffectPriv.h"
-#include "src/gpu/ganesh/effects/GrSkSLFP.h"
-#include "src/gpu/ganesh/gradients/GrGradientShader.h"
-
-std::unique_ptr<GrFragmentProcessor>
-SkTwoPointConicalGradient::asFragmentProcessor(const GrFPArgs& args, const MatrixRec& mRec) const {
-    // The 2 point conical gradient can reject a pixel so it does change opacity even if the input
-    // was opaque. Thus, all of these layout FPs disable that optimization.
-    std::unique_ptr<GrFragmentProcessor> fp;
-    SkTLazy<SkMatrix> matrix;
-    switch (this->getType()) {
-        case SkTwoPointConicalGradient::Type::kStrip: {
-            static const SkRuntimeEffect* kEffect =
-                SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
-                        "uniform half r0_2;"
-                        "half4 main(float2 p) {"
-                            "half v = 1;" // validation flag,set to negative to discard fragment later
-                            "float t = r0_2 - p.y * p.y;"
-                            "if (t >= 0) {"
-                                "t = p.x + sqrt(t);"
-                            "} else {"
-                                "v = -1;"
-                            "}"
-                            "return half4(half(t), v, 0, 0);"
-                        "}"
-                    );
-            float r0 = this->getStartRadius() / this->getCenterX1();
-            fp = GrSkSLFP::Make(kEffect, "TwoPointConicalStripLayout", /*inputFP=*/nullptr,
-                                GrSkSLFP::OptFlags::kNone,
-                                "r0_2", r0 * r0);
-        } break;
-
-        case SkTwoPointConicalGradient::Type::kRadial: {
-            static const SkRuntimeEffect* kEffect =
-                SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
-                        "uniform half r0;"
-                        "uniform half lengthScale;"
-                        "half4 main(float2 p) {"
-                            "half v = 1;" // validation flag,set to negative to discard fragment later
-                            "float t = length(p) * lengthScale - r0;"
-                            "return half4(half(t), v, 0, 0);"
-                        "}"
-                    );
-            float dr = this->getDiffRadius();
-            float r0 = this->getStartRadius() / dr;
-            bool isRadiusIncreasing = dr >= 0;
-            fp = GrSkSLFP::Make(kEffect, "TwoPointConicalRadialLayout", /*inputFP=*/nullptr,
-                                GrSkSLFP::OptFlags::kNone,
-                                "r0", r0,
-                                "lengthScale", isRadiusIncreasing ? 1.0f : -1.0f);
-
-            // GPU radial matrix is different from the original matrix, since we map the diff radius
-            // to have |dr| = 1, so manually compute the final gradient matrix here.
-
-            // Map center to (0, 0)
-            matrix.set(SkMatrix::Translate(-this->getStartCenter().fX,
-                                           -this->getStartCenter().fY));
-            // scale |diffRadius| to 1
-            matrix->postScale(1 / dr, 1 / dr);
-        } break;
-
-        case SkTwoPointConicalGradient::Type::kFocal: {
-            static const SkRuntimeEffect* kEffect =
-                SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader,
-                        // Optimization flags, all specialized:
-                        "uniform int isRadiusIncreasing;"
-                        "uniform int isFocalOnCircle;"
-                        "uniform int isWellBehaved;"
-                        "uniform int isSwapped;"
-                        "uniform int isNativelyFocal;"
-
-                        "uniform half invR1;"  // 1/r1
-                        "uniform half fx;"     // focalX = r0/(r0-r1)
-
-                        "half4 main(float2 p) {"
-                            "float t = -1;"
-                            "half v = 1;" // validation flag,set to negative to discard fragment later
-
-                            "float x_t = -1;"
-                            "if (bool(isFocalOnCircle)) {"
-                                "x_t = dot(p, p) / p.x;"
-                            "} else if (bool(isWellBehaved)) {"
-                                "x_t = length(p) - p.x * invR1;"
-                            "} else {"
-                                "float temp = p.x * p.x - p.y * p.y;"
-
-                                // Only do sqrt if temp >= 0; this is significantly slower than
-                                // checking temp >= 0 in the if statement that checks r(t) >= 0.
-                                // But GPU may break if we sqrt a negative float. (Although I
-                                // haven't observed that on any devices so far, and the old
-                                // approach also does sqrt negative value without a check.) If
-                                // the performance is really critical, maybe we should just
-                                // compute the area where temp and x_t are always valid and drop
-                                // all these ifs.
-                                "if (temp >= 0) {"
-                                    "if (bool(isSwapped) || !bool(isRadiusIncreasing)) {"
-                                        "x_t = -sqrt(temp) - p.x * invR1;"
-                                    "} else {"
-                                        "x_t = sqrt(temp) - p.x * invR1;"
-                                    "}"
-                                "}"
-                            "}"
-
-                            // The final calculation of t from x_t has lots of static
-                            // optimizations but only do them when x_t is positive (which
-                            // can be assumed true if isWellBehaved is true)
-                            "if (!bool(isWellBehaved)) {"
-                                // This will still calculate t even though it will be ignored
-                                // later in the pipeline to avoid a branch
-                                "if (x_t <= 0.0) {"
-                                    "v = -1;"
-                                "}"
-                            "}"
-                            "if (bool(isRadiusIncreasing)) {"
-                                "if (bool(isNativelyFocal)) {"
-                                    "t = x_t;"
-                                "} else {"
-                                    "t = x_t + fx;"
-                                "}"
-                            "} else {"
-                                "if (bool(isNativelyFocal)) {"
-                                    "t = -x_t;"
-                                "} else {"
-                                    "t = -x_t + fx;"
-                                "}"
-                            "}"
-
-                            "if (bool(isSwapped)) {"
-                                "t = 1 - t;"
-                            "}"
-
-                            "return half4(half(t), v, 0, 0);"
-                        "}"
-                    );
-
-            const SkTwoPointConicalGradient::FocalData& focalData = this->getFocalData();
-            bool isRadiusIncreasing = (1 - focalData.fFocalX) > 0,
-                    isFocalOnCircle    = focalData.isFocalOnCircle(),
-                    isWellBehaved      = focalData.isWellBehaved(),
-                    isSwapped          = focalData.isSwapped(),
-                    isNativelyFocal    = focalData.isNativelyFocal();
-
-            fp = GrSkSLFP::Make(kEffect, "TwoPointConicalFocalLayout", /*inputFP=*/nullptr,
-                                GrSkSLFP::OptFlags::kNone,
-                                "isRadiusIncreasing", GrSkSLFP::Specialize<int>(isRadiusIncreasing),
-                                "isFocalOnCircle",    GrSkSLFP::Specialize<int>(isFocalOnCircle),
-                                "isWellBehaved",      GrSkSLFP::Specialize<int>(isWellBehaved),
-                                "isSwapped",          GrSkSLFP::Specialize<int>(isSwapped),
-                                "isNativelyFocal",    GrSkSLFP::Specialize<int>(isNativelyFocal),
-                                "invR1", 1.0f / focalData.fR1,
-                                "fx", focalData.fFocalX);
-        } break;
-    }
-    return GrGradientShader::MakeGradientFP(*this,
-                                            args,
-                                            mRec,
-                                            std::move(fp),
-                                            matrix.getMaybeNull());
-}
-
-#endif
-
-#if defined(SK_GRAPHITE)
-void SkTwoPointConicalGradient::addToKey(const skgpu::graphite::KeyContext& keyContext,
-                                         skgpu::graphite::PaintParamsKeyBuilder* builder,
-                                         skgpu::graphite::PipelineDataGatherer* gatherer) const {
-    this->addToKeyCommon(keyContext, builder, gatherer,
-                         GradientType::kConical,
-                         fCenter1, fCenter2,
-                         fRadius1, fRadius2,
-                         0.0f, 0.0f);
-}
-#endif
-
-// assumes colors is SkColor4f* and pos is SkScalar*
-#define EXPAND_1_COLOR(count)                \
-     SkColor4f tmp[2];                       \
-     do {                                    \
-         if (1 == count) {                   \
-             tmp[0] = tmp[1] = colors[0];    \
-             colors = tmp;                   \
-             pos = nullptr;                  \
-             count = 2;                      \
-         }                                   \
-     } while (0)
-
-sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
-                                                      SkScalar startRadius,
-                                                      const SkPoint& end,
-                                                      SkScalar endRadius,
-                                                      const SkColor4f colors[],
-                                                      sk_sp<SkColorSpace> colorSpace,
-                                                      const SkScalar pos[],
-                                                      int colorCount,
-                                                      SkTileMode mode,
-                                                      const Interpolation& interpolation,
-                                                      const SkMatrix* localMatrix) {
-    if (startRadius < 0 || endRadius < 0) {
-        return nullptr;
-    }
-    if (!SkGradientShaderBase::ValidGradient(colors, colorCount, mode, interpolation)) {
-        return nullptr;
-    }
-    if (SkScalarNearlyZero((start - end).length(), SkGradientShaderBase::kDegenerateThreshold)) {
-        // If the center positions are the same, then the gradient is the radial variant of a 2 pt
-        // conical gradient, an actual radial gradient (startRadius == 0), or it is fully degenerate
-        // (startRadius == endRadius).
-        if (SkScalarNearlyEqual(startRadius, endRadius,
-                                SkGradientShaderBase::kDegenerateThreshold)) {
-            // Degenerate case, where the interpolation region area approaches zero. The proper
-            // behavior depends on the tile mode, which is consistent with the default degenerate
-            // gradient behavior, except when mode = clamp and the radii > 0.
-            if (mode == SkTileMode::kClamp &&
-                endRadius > SkGradientShaderBase::kDegenerateThreshold) {
-                // The interpolation region becomes an infinitely thin ring at the radius, so the
-                // final gradient will be the first color repeated from p=0 to 1, and then a hard
-                // stop switching to the last color at p=1.
-                static constexpr SkScalar circlePos[3] = {0, 1, 1};
-                SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]};
-                return MakeRadial(start, endRadius, reColors, std::move(colorSpace),
-                                  circlePos, 3, mode, interpolation, localMatrix);
-            } else {
-                // Otherwise use the default degenerate case
-                return SkGradientShaderBase::MakeDegenerateGradient(colors, pos, colorCount,
-                                                                    std::move(colorSpace), mode);
-            }
-        } else if (SkScalarNearlyZero(startRadius, SkGradientShaderBase::kDegenerateThreshold)) {
-            // We can treat this gradient as radial, which is faster. If we got here, we know
-            // that endRadius is not equal to 0, so this produces a meaningful gradient
-            return MakeRadial(start, endRadius, colors, std::move(colorSpace), pos, colorCount,
-                              mode, interpolation, localMatrix);
-        }
-        // Else it's the 2pt conical radial variant with no degenerate radii, so fall through to the
-        // regular 2pt constructor.
-    }
-
-    if (localMatrix && !localMatrix->invert(nullptr)) {
-        return nullptr;
-    }
-    EXPAND_1_COLOR(colorCount);
-
-    SkGradientShaderBase::ColorStopOptimizer opt(colors, pos, colorCount, mode);
-
-    SkGradientShaderBase::Descriptor desc(opt.fColors, std::move(colorSpace), opt.fPos,
-                                          opt.fCount, mode, interpolation);
-    return SkTwoPointConicalGradient::Create(start, startRadius, end, endRadius, desc, localMatrix);
-}
-
-#undef EXPAND_1_COLOR
-
-sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
-                                                      SkScalar startRadius,
-                                                      const SkPoint& end,
-                                                      SkScalar endRadius,
-                                                      const SkColor colors[],
-                                                      const SkScalar pos[],
-                                                      int colorCount,
-                                                      SkTileMode mode,
-                                                      uint32_t flags,
-                                                      const SkMatrix* localMatrix) {
-    SkColorConverter converter(colors, colorCount);
-    return MakeTwoPointConical(start, startRadius, end, endRadius, converter.fColors4f.begin(),
-                               nullptr, pos, colorCount, mode, flags, localMatrix);
-}
-
-void SkRegisterTwoPointConicalGradientShaderFlattenable() {
-    SK_REGISTER_FLATTENABLE(SkTwoPointConicalGradient);
-}
diff --git a/src/svg/SkSVGDevice.cpp b/src/svg/SkSVGDevice.cpp
index d3ec053..2f245fa 100644
--- a/src/svg/SkSVGDevice.cpp
+++ b/src/svg/SkSVGDevice.cpp
@@ -52,6 +52,7 @@
 #include "src/core/SkFontPriv.h"
 #include "src/core/SkTHash.h"
 #include "src/image/SkImage_Base.h"
+#include "src/shaders/SkColorShader.h"
 #include "src/shaders/SkShaderBase.h"
 #include "src/text/GlyphRun.h"
 #include "src/xml/SkXMLWriter.h"
@@ -437,11 +438,17 @@
 void SkSVGDevice::AutoElement::addGradientShaderResources(const SkShader* shader,
                                                           const SkPaint& paint,
                                                           Resources* resources) {
+    SkASSERT(shader);
+    if (as_SB(shader)->type() == SkShaderBase::ShaderType::kColor) {
+        auto colorShader = static_cast<const SkColorShader*>(shader);
+        resources->fPaintServer = svg_color(colorShader->color());
+        return;
+    }
+
     SkShaderBase::GradientInfo grInfo;
     const auto gradient_type = as_SB(shader)->asGradient(&grInfo);
 
-    if (gradient_type != SkShaderBase::GradientType::kColor &&
-        gradient_type != SkShaderBase::GradientType::kLinear) {
+    if (gradient_type != SkShaderBase::GradientType::kLinear) {
         // TODO: other gradient support
         return;
     }
@@ -458,9 +465,8 @@
     SkASSERT(grInfo.fColorCount <= grOffsets.count());
 
     SkASSERT(grColors.size() > 0);
-    resources->fPaintServer = gradient_type == SkShaderBase::GradientType::kColor
-            ? svg_color(grColors[0])
-            : SkStringPrintf("url(#%s)", addLinearGradientDef(grInfo, shader, localMatrix).c_str());
+    resources->fPaintServer =
+            SkStringPrintf("url(#%s)", addLinearGradientDef(grInfo, shader, localMatrix).c_str());
 }
 
 void SkSVGDevice::AutoElement::addColorFilterResources(const SkColorFilter& cf,
@@ -608,7 +614,9 @@
     const SkShader* shader = paint.getShader();
     SkASSERT(shader);
 
-    if (as_SB(shader)->asGradient() != SkShaderBase::GradientType::kNone) {
+    auto shaderType = as_SB(shader)->type();
+    if (shaderType == SkShaderBase::ShaderType::kColor ||
+        shaderType == SkShaderBase::ShaderType::kGradientBase) {
         this->addGradientShaderResources(shader, paint, resources);
     } else if (shader->isAImage()) {
         this->addImageShaderResources(shader, paint, resources);
diff --git a/src/xps/SkXPSDevice.cpp b/src/xps/SkXPSDevice.cpp
index 7516d7a..a832101 100644
--- a/src/xps/SkXPSDevice.cpp
+++ b/src/xps/SkXPSDevice.cpp
@@ -49,6 +49,7 @@
 #include "src/image/SkImage_Base.h"
 #include "src/sfnt/SkSFNTHeader.h"
 #include "src/sfnt/SkTTCFHeader.h"
+#include "src/shaders/SkColorShader.h"
 #include "src/shaders/SkShaderBase.h"
 #include "src/text/GlyphRun.h"
 #include "src/utils/SkClipStackUtils.h"
@@ -976,22 +977,16 @@
     }
 
     //Gradient shaders.
-    SkShaderBase::GradientInfo info;
-    SkShaderBase::GradientType gradientType = as_SB(shader)->asGradient(&info);
+    auto shaderBase = as_SB(shader);
 
-    if (gradientType == SkShaderBase::GradientType::kNone) {
-        //Nothing to see, move along.
-
-    } else if (gradientType == SkShaderBase::GradientType::kColor) {
-        SkASSERT(1 == info.fColorCount);
-        SkColor color;
-        info.fColors = &color;
-        as_SB(shader)->asGradient(&info);
+    if (shaderBase->type() == SkShaderBase::ShaderType::kColor) {
+        auto colorShader = static_cast<const SkColorShader*>(shader);
         SkAlpha alpha = skPaint.getAlpha();
-        HR(this->createXpsSolidColorBrush(color, alpha, brush));
+        HR(this->createXpsSolidColorBrush(colorShader->color(), alpha, brush));
         return S_OK;
-
-    } else {
+    } else if (shaderBase->type() == SkShaderBase::ShaderType::kGradientBase) {
+        SkShaderBase::GradientInfo info;
+        SkShaderBase::GradientType gradientType = shaderBase->asGradient(&info);
         if (info.fColorCount == 0) {
             const SkColor color = skPaint.getColor();
             HR(this->createXpsSolidColorBrush(color, 0xFF, brush));
@@ -1003,7 +998,7 @@
         AutoTArray<SkScalar> colorOffsets(info.fColorCount);
         info.fColors = colors.get();
         info.fColorOffsets = colorOffsets.get();
-        as_SB(shader)->asGradient(&info, &localMatrix);
+        shaderBase->asGradient(&info, &localMatrix);
 
         if (1 == info.fColorCount) {
             SkColor color = info.fColors[0];
diff --git a/tests/BlurTest.cpp b/tests/BlurTest.cpp
index 42c2b4e..e89c0f7 100644
--- a/tests/BlurTest.cpp
+++ b/tests/BlurTest.cpp
@@ -505,7 +505,7 @@
         { 1, 1, 1 }, 0, 127, 127
     };
     p.setMaskFilter(SkEmbossMaskFilter::Make(1, light));
-    p.setShader(SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 2, 0.0f));
+    p.setShader(SkShaders::MakeFractalNoise(1.0f, 1.0f, 2, 0.0f));
 
     sk_sp<SkSurface> surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(100, 100));
     surface->getCanvas()->drawPaint(p);
diff --git a/tests/GradientTest.cpp b/tests/GradientTest.cpp
index 8bd3ffd..0552e60 100644
--- a/tests/GradientTest.cpp
+++ b/tests/GradientTest.cpp
@@ -33,6 +33,7 @@
 #include "src/base/SkTLazy.h"
 #include "src/gpu/ganesh/GrColorInfo.h"
 #include "src/gpu/ganesh/GrFPArgs.h"
+#include "src/gpu/ganesh/GrFragmentProcessors.h"
 #include "src/shaders/SkShaderBase.h"
 #include "tests/Test.h"
 
@@ -105,11 +106,7 @@
 
 static void color_gradproc(skiatest::Reporter* reporter, const GradRec& rec, const GradRec&) {
     sk_sp<SkShader> s(SkShaders::Color(rec.fColors[0]));
-    REPORTER_ASSERT(reporter, SkShaderBase::GradientType::kColor == as_SB(s)->asGradient());
-
-    SkShaderBase::GradientInfo info;
-    as_SB(s)->asGradient(&info);
-    REPORTER_ASSERT(reporter, 1 == info.fColorCount);
+    REPORTER_ASSERT(reporter, SkShaderBase::GradientType::kNone == as_SB(s)->asGradient());
 }
 
 static void linear_gradproc(skiatest::Reporter* reporter, const GradRec& buildRec,
@@ -446,7 +443,7 @@
     auto context = GrDirectContext::MakeMock(&options);
 
     GrFPArgs args(context.get(), &dstColorInfo, props);
-    as_SB(gradient)->asRootFragmentProcessor(args, SkMatrix::I());
+    GrFragmentProcessors::Make(gradient.get(), args, SkMatrix::I());
 }
 
 // "Interesting" fuzzer values.
diff --git a/tests/ImageFilterTest.cpp b/tests/ImageFilterTest.cpp
index 4d2e0e1..6a5e42e 100644
--- a/tests/ImageFilterTest.cpp
+++ b/tests/ImageFilterTest.cpp
@@ -256,7 +256,7 @@
         }
         {
             sk_sp<SkImageFilter> paintFilter(SkImageFilters::Shader(
-                    SkPerlinNoiseShader::MakeTurbulence(SK_Scalar1, SK_Scalar1, 1, 0)));
+                    SkShaders::MakeTurbulence(SK_Scalar1, SK_Scalar1, 1, 0)));
 
             this->addFilter("paint and blur", SkImageFilters::Blur(
                     kBlurSigma, kBlurSigma,  std::move(paintFilter), cropRect));
diff --git a/tests/PDFPrimitivesTest.cpp b/tests/PDFPrimitivesTest.cpp
index 111ce6d..c78c367 100644
--- a/tests/PDFPrimitivesTest.cpp
+++ b/tests/PDFPrimitivesTest.cpp
@@ -481,7 +481,7 @@
 
     SkPaint paint;
     paint.setBlendMode(SkBlendMode::kDarken);
-    paint.setShader(SkPerlinNoiseShader::MakeFractalNoise(0, 0, 2, 0, nullptr));
+    paint.setShader(SkShaders::MakeFractalNoise(0, 0, 2, 0, nullptr));
     paint.setColor4f(SkColor4f{0, 0, 0 ,0});
 
     canvas->drawPath(SkPath(), paint);
diff --git a/tests/ShaderTest.cpp b/tests/ShaderTest.cpp
index 26b05b3..9dc97b4 100644
--- a/tests/ShaderTest.cpp
+++ b/tests/ShaderTest.cpp
@@ -65,10 +65,9 @@
     srcBitmap.eraseColor(SK_ColorRED);
     SkCanvas canvas(srcBitmap);
     SkPaint p;
-    p.setShader(
-        SkShaders::Blend(SkBlendMode::kClear,
-        SkShaders::Empty(),
-        SkPerlinNoiseShader::MakeFractalNoise(1.0f, 1.0f, 2, 0.0f)));
+    p.setShader(SkShaders::Blend(SkBlendMode::kClear,
+                                 SkShaders::Empty(),
+                                 SkShaders::MakeFractalNoise(1.0f, 1.0f, 2, 0.0f)));
     SkRRect rr;
     SkVector rd[] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}};
     rr.setRectRadii({0, 0, 0, 0}, rd);
diff --git a/tests/TriangulatingPathRendererTests.cpp b/tests/TriangulatingPathRendererTests.cpp
index 8d61c0c..4844123 100644
--- a/tests/TriangulatingPathRendererTests.cpp
+++ b/tests/TriangulatingPathRendererTests.cpp
@@ -35,6 +35,7 @@
 #include "src/gpu/ganesh/GrColorInfo.h"
 #include "src/gpu/ganesh/GrEagerVertexAllocator.h"
 #include "src/gpu/ganesh/GrFragmentProcessor.h"
+#include "src/gpu/ganesh/GrFragmentProcessors.h"
 #include "src/gpu/ganesh/GrPaint.h"
 #include "src/gpu/ganesh/GrStyle.h"
 #include "src/gpu/ganesh/GrUserStencilSettings.h"
@@ -46,7 +47,6 @@
 #include "src/gpu/ganesh/geometry/GrStyledShape.h"
 #include "src/gpu/ganesh/geometry/GrTriangulator.h"
 #include "src/gpu/ganesh/ops/TriangulatingPathRenderer.h"
-#include "src/shaders/SkShaderBase.h"
 #include "tests/CtsEnforcement.h"
 #include "tests/Test.h"
 #include "tools/ToolUtils.h"
@@ -828,7 +828,7 @@
         pts, colors, nullptr, std::size(colors), SkTileMode::kClamp);
     GrColorInfo colorInfo(GrColorType::kRGBA_8888, kPremul_SkAlphaType, nullptr);
     SkSurfaceProps props; // default props for testing
-    return as_SB(shader)->asRootFragmentProcessor({rContext, &colorInfo, props}, ctm);
+    return GrFragmentProcessors::Make(shader.get(), {rContext, &colorInfo, props}, ctm);
 }
 
 static void test_path(GrRecordingContext* rContext,
diff --git a/tests/graphite/ImageShaderTest.cpp b/tests/graphite/ImageShaderTest.cpp
index 66860ef..85a0ec9 100644
--- a/tests/graphite/ImageShaderTest.cpp
+++ b/tests/graphite/ImageShaderTest.cpp
@@ -8,6 +8,7 @@
 #include "tests/Test.h"
 
 #include "include/core/SkBitmap.h"
+#include "include/core/SkTileMode.h"
 #include "include/gpu/graphite/Context.h"
 #include "include/gpu/graphite/Surface.h"
 #include "src/gpu/graphite/Surface_Graphite.h"
diff --git a/tests/graphite/PaintParamsKeyTest.cpp b/tests/graphite/PaintParamsKeyTest.cpp
index 9641c66..22b5702 100644
--- a/tests/graphite/PaintParamsKeyTest.cpp
+++ b/tests/graphite/PaintParamsKeyTest.cpp
@@ -256,8 +256,7 @@
             o = PrecompileShaders::TwoPointConicalGradient();
             break;
         case SkShaderBase::GradientType::kNone:
-        case SkShaderBase::GradientType::kColor:
-            SkASSERT(0);
+            SkDEBUGFAIL("Gradient shader says its type is none");
             break;
     }
 
diff --git a/toolchain/linux_trampolines/clang_trampoline_linux.sh b/toolchain/linux_trampolines/clang_trampoline_linux.sh
index f6b5923..97304a9 100755
--- a/toolchain/linux_trampolines/clang_trampoline_linux.sh
+++ b/toolchain/linux_trampolines/clang_trampoline_linux.sh
@@ -36,6 +36,7 @@
   "src/gpu/ganesh/surface/"
   "src/image/"
   "src/pathops/"
+  "src/shaders/"
   "src/sksl/"
   "src/svg/"
   "src/text/"
@@ -117,6 +118,7 @@
   "src/gpu/ganesh/GrSurfaceProxyView.cpp"
   "src/gpu/ganesh/GrTextureProxy.cpp"
   "src/gpu/ganesh/SkGr.cpp"
+  "src/gpu/ganesh/effects/GrPerlinNoise2Effect.cpp"
   "src/pdf/SkJpeg"
 
   # See //bazel/generate_cpp_files_for_headers.bzl and //include/BUILD.bazel for more.
diff --git a/tools/skpbench/skpbench.cpp b/tools/skpbench/skpbench.cpp
index e7d5555..ca09d64 100644
--- a/tools/skpbench/skpbench.cpp
+++ b/tools/skpbench/skpbench.cpp
@@ -699,7 +699,7 @@
 
     // Use a perlin shader to warmup the GPU.
     SkPaint perlin;
-    perlin.setShader(SkPerlinNoiseShader::MakeTurbulence(0.1f, 0.1f, 1, 0, nullptr));
+    perlin.setShader(SkShaders::MakeTurbulence(0.1f, 0.1f, 1, 0, nullptr));
     recording->drawRect(bounds, perlin);
 
     return recorder.finishRecordingAsPicture();