Revert "Drop support for unused MSAA extensions"

This reverts commit 7df27465c4ecc8a4a0cdd95e9785c342903c2de9.

Reason for revert: experimental revert to see if this is the cause of the tree redness

Original change's description:
> Drop support for unused MSAA extensions
> 
> Bug: skia:
> Change-Id: I113b80e3f991f195155148625ceb29242ea82776
> Reviewed-on: https://skia-review.googlesource.com/101403
> Reviewed-by: Brian Salomon <bsalomon@google.com>
> Commit-Queue: Chris Dalton <csmartdalton@google.com>

TBR=bsalomon@google.com,csmartdalton@google.com,ethannicholas@google.com

Change-Id: I4fa4123e2d176bef88cd76a09a14053d9ac5809f
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: skia:
Reviewed-on: https://skia-review.googlesource.com/101680
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: Robert Phillips <robertphillips@google.com>
diff --git a/gn/tests.gni b/gn/tests.gni
index 36eebf3..e4519d9 100644
--- a/gn/tests.gni
+++ b/gn/tests.gni
@@ -89,6 +89,7 @@
   "$_tests/GpuDrawPathTest.cpp",
   "$_tests/GpuLayerCacheTest.cpp",
   "$_tests/GpuRectanizerTest.cpp",
+  "$_tests/GpuSampleLocationsTest.cpp",
   "$_tests/GradientTest.cpp",
   "$_tests/GrAllocatorTest.cpp",
   "$_tests/GrCCPRTest.cpp",
diff --git a/include/gpu/GrCaps.h b/include/gpu/GrCaps.h
index c828922..f4d6e06 100644
--- a/include/gpu/GrCaps.h
+++ b/include/gpu/GrCaps.h
@@ -52,6 +52,7 @@
     bool gpuTracingSupport() const { return fGpuTracingSupport; }
     bool oversizedStencilSupport() const { return fOversizedStencilSupport; }
     bool textureBarrierSupport() const { return fTextureBarrierSupport; }
+    bool sampleLocationsSupport() const { return fSampleLocationsSupport; }
     bool multisampleDisableSupport() const { return fMultisampleDisableSupport; }
     bool instanceAttribSupport() const { return fInstanceAttribSupport; }
     bool usesMixedSamples() const { return fUsesMixedSamples; }
@@ -206,6 +207,7 @@
     bool fGpuTracingSupport                          : 1;
     bool fOversizedStencilSupport                    : 1;
     bool fTextureBarrierSupport                      : 1;
+    bool fSampleLocationsSupport                     : 1;
     bool fMultisampleDisableSupport                  : 1;
     bool fInstanceAttribSupport                      : 1;
     bool fUsesMixedSamples                           : 1;
diff --git a/include/gpu/GrRenderTarget.h b/include/gpu/GrRenderTarget.h
index ee43dd3..4eee7f6 100644
--- a/include/gpu/GrRenderTarget.h
+++ b/include/gpu/GrRenderTarget.h
@@ -134,6 +134,7 @@
 
     int                  fSampleCnt;
     GrStencilAttachment* fStencilAttachment;
+    uint8_t              fMultisampleSpecsID;
     GrRenderTargetFlags  fFlags;
 
     SkIRect              fResolveRect;
diff --git a/include/gpu/GrShaderCaps.h b/include/gpu/GrShaderCaps.h
index 62100c1..9dd2f96 100644
--- a/include/gpu/GrShaderCaps.h
+++ b/include/gpu/GrShaderCaps.h
@@ -72,6 +72,12 @@
 
     bool noperspectiveInterpolationSupport() const { return fNoPerspectiveInterpolationSupport; }
 
+    bool multisampleInterpolationSupport() const { return fMultisampleInterpolationSupport; }
+
+    bool sampleVariablesSupport() const { return fSampleVariablesSupport; }
+
+    bool sampleMaskOverrideCoverageSupport() const { return fSampleMaskOverrideCoverageSupport; }
+
     bool externalTextureSupport() const { return fExternalTextureSupport; }
 
     bool texelFetchSupport() const { return fTexelFetchSupport; }
@@ -186,6 +192,16 @@
         return fNoPerspectiveInterpolationExtensionString;
     }
 
+    const char* multisampleInterpolationExtensionString() const {
+        SkASSERT(this->multisampleInterpolationSupport());
+        return fMultisampleInterpolationExtensionString;
+    }
+
+    const char* sampleVariablesExtensionString() const {
+        SkASSERT(this->sampleVariablesSupport());
+        return fSampleVariablesExtensionString;
+    }
+
     const char* imageLoadStoreExtensionString() const {
         SkASSERT(this->imageLoadStoreSupport());
         return fImageLoadStoreExtensionString;
@@ -246,6 +262,9 @@
     bool fFlatInterpolationSupport : 1;
     bool fPreferFlatInterpolation : 1;
     bool fNoPerspectiveInterpolationSupport : 1;
+    bool fMultisampleInterpolationSupport : 1;
+    bool fSampleVariablesSupport : 1;
+    bool fSampleMaskOverrideCoverageSupport : 1;
     bool fExternalTextureSupport : 1;
     bool fTexelFetchSupport : 1;
     bool fVertexIDSupport : 1;
@@ -275,6 +294,8 @@
     const char* fExternalTextureExtensionString;
     const char* fTexelBufferExtensionString;
     const char* fNoPerspectiveInterpolationExtensionString;
+    const char* fMultisampleInterpolationExtensionString;
+    const char* fSampleVariablesExtensionString;
     const char* fImageLoadStoreExtensionString;
 
     const char* fFBFetchColorName;
diff --git a/src/gpu/GrCaps.cpp b/src/gpu/GrCaps.cpp
index 7f07550..427b204 100644
--- a/src/gpu/GrCaps.cpp
+++ b/src/gpu/GrCaps.cpp
@@ -50,6 +50,7 @@
     fGpuTracingSupport = false;
     fOversizedStencilSupport = false;
     fTextureBarrierSupport = false;
+    fSampleLocationsSupport = false;
     fMultisampleDisableSupport = false;
     fInstanceAttribSupport = false;
     fUsesMixedSamples = false;
@@ -154,6 +155,7 @@
     writer->appendBool("Gpu Tracing Support", fGpuTracingSupport);
     writer->appendBool("Oversized Stencil Support", fOversizedStencilSupport);
     writer->appendBool("Texture Barrier Support", fTextureBarrierSupport);
+    writer->appendBool("Sample Locations Support", fSampleLocationsSupport);
     writer->appendBool("Multisample disable support", fMultisampleDisableSupport);
     writer->appendBool("Instance Attrib Support", fInstanceAttribSupport);
     writer->appendBool("Uses Mixed Samples", fUsesMixedSamples);
diff --git a/src/gpu/GrDefaultGeoProcFactory.cpp b/src/gpu/GrDefaultGeoProcFactory.cpp
index 8f4b959..37967e4 100644
--- a/src/gpu/GrDefaultGeoProcFactory.cpp
+++ b/src/gpu/GrDefaultGeoProcFactory.cpp
@@ -73,7 +73,7 @@
         void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
             const DefaultGeoProc& gp = args.fGP.cast<DefaultGeoProc>();
             GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
-            GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+            GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
             GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
             GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
 
diff --git a/src/gpu/GrFragmentProcessor.cpp b/src/gpu/GrFragmentProcessor.cpp
index 12ae2b1..0c7c06f 100644
--- a/src/gpu/GrFragmentProcessor.cpp
+++ b/src/gpu/GrFragmentProcessor.cpp
@@ -78,6 +78,8 @@
 }
 
 int GrFragmentProcessor::registerChildProcessor(std::unique_ptr<GrFragmentProcessor> child) {
+    this->combineRequiredFeatures(*child);
+
     if (child->usesLocalCoords()) {
         fFlags |= kUsesLocalCoords_Flag;
     }
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index a8b022a..eb005d1 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -36,6 +36,7 @@
     : fResetTimestamp(kExpiredTimestamp+1)
     , fResetBits(kAll_GrBackendState)
     , fContext(context) {
+    fMultisampleSpecs.emplace_back(0, 0, nullptr); // Index 0 is an invalid unique id.
 }
 
 GrGpu::~GrGpu() {}
@@ -422,6 +423,48 @@
     }
 }
 
+const GrGpu::MultisampleSpecs& GrGpu::queryMultisampleSpecs(const GrPipeline& pipeline) {
+    GrRenderTarget* rt = pipeline.renderTarget();
+    SkASSERT(rt->numStencilSamples() > 1);
+
+    GrStencilSettings stencil;
+    if (pipeline.isStencilEnabled()) {
+        // TODO: attach stencil and create settings during render target flush.
+        SkASSERT(rt->renderTargetPriv().getStencilAttachment());
+        stencil.reset(*pipeline.getUserStencil(), pipeline.hasStencilClip(),
+                      rt->renderTargetPriv().numStencilBits());
+    }
+
+    int effectiveSampleCnt;
+    SkSTArray<16, SkPoint, true> pattern;
+    this->onQueryMultisampleSpecs(rt, pipeline.proxy()->origin(), stencil,
+                                  &effectiveSampleCnt, &pattern);
+    SkASSERT(effectiveSampleCnt >= rt->numStencilSamples());
+
+    uint8_t id;
+    if (this->caps()->sampleLocationsSupport()) {
+        SkASSERT(pattern.count() == effectiveSampleCnt);
+        const auto& insertResult = fMultisampleSpecsIdMap.insert(
+            MultisampleSpecsIdMap::value_type(pattern, SkTMin(fMultisampleSpecs.count(), 255)));
+        id = insertResult.first->second;
+        if (insertResult.second) {
+            // This means the insert did not find the pattern in the map already, and therefore an
+            // actual insertion took place. (We don't expect to see many unique sample patterns.)
+            const SkPoint* sampleLocations = insertResult.first->first.begin();
+            SkASSERT(id == fMultisampleSpecs.count());
+            fMultisampleSpecs.emplace_back(id, effectiveSampleCnt, sampleLocations);
+        }
+    } else {
+        id = effectiveSampleCnt;
+        for (int i = fMultisampleSpecs.count(); i <= id; ++i) {
+            fMultisampleSpecs.emplace_back(i, i, nullptr);
+        }
+    }
+    SkASSERT(id > 0);
+
+    return fMultisampleSpecs[id];
+}
+
 bool GrGpu::SamplePatternComparator::operator()(const SamplePattern& a,
                                                 const SamplePattern& b) const {
     if (a.count() != b.count()) {
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index d03c081..2f12073 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -328,6 +328,33 @@
                      const SkIRect& srcRect,
                      const SkIPoint& dstPoint);
 
+    struct MultisampleSpecs {
+        MultisampleSpecs(uint8_t uniqueID, int effectiveSampleCnt, const SkPoint* locations)
+            : fUniqueID(uniqueID),
+              fEffectiveSampleCnt(effectiveSampleCnt),
+              fSampleLocations(locations) {}
+
+        // Nonzero ID that uniquely identifies these multisample specs.
+        uint8_t          fUniqueID;
+        // The actual number of samples the GPU will run. NOTE: this value can be greater than the
+        // the render target's sample count.
+        int              fEffectiveSampleCnt;
+        // If sample locations are supported, points to the subpixel locations at which the GPU will
+        // sample. Pixel center is at (.5, .5), and (0, 0) indicates the top left corner.
+        const SkPoint*   fSampleLocations;
+    };
+
+    // Finds a render target's multisample specs. The pipeline is only needed in case we need to
+    // flush the draw state prior to querying multisample info. The pipeline is not expected to
+    // affect the multisample information itself.
+    const MultisampleSpecs& queryMultisampleSpecs(const GrPipeline&);
+
+    // Finds the multisample specs with a given unique id.
+    const MultisampleSpecs& getMultisampleSpecs(uint8_t uniqueID) {
+        SkASSERT(uniqueID > 0 && uniqueID < fMultisampleSpecs.count());
+        return fMultisampleSpecs[uniqueID];
+    }
+
     // Creates a GrGpuRTCommandBuffer which GrOpLists send draw commands to instead of directly
     // to the Gpu object.
     virtual GrGpuRTCommandBuffer* createCommandBuffer(
@@ -572,6 +599,11 @@
                                GrSurface* src, GrSurfaceOrigin srcOrigin,
                                const SkIRect& srcRect, const SkIPoint& dstPoint) = 0;
 
+    // overridden by backend specific derived class to perform the multisample queries
+    virtual void onQueryMultisampleSpecs(GrRenderTarget*, GrSurfaceOrigin rtOrigin,
+                                         const GrStencilSettings&,
+                                         int* effectiveSampleCnt, SamplePattern*) = 0;
+
     virtual void onFinishFlush(bool insertedSemaphores) = 0;
 
     virtual void onDumpJSON(SkJSONWriter*) const {}
@@ -586,8 +618,12 @@
         bool operator()(const SamplePattern&, const SamplePattern&) const;
     };
 
+    typedef std::map<SamplePattern, uint8_t, SamplePatternComparator> MultisampleSpecsIdMap;
+
     ResetTimestamp                         fResetTimestamp;
     uint32_t                               fResetBits;
+    MultisampleSpecsIdMap                  fMultisampleSpecsIdMap;
+    SkSTArray<1, MultisampleSpecs, true>   fMultisampleSpecs;
     // The context owns us, not vice-versa, so this ptr is not ref'ed by Gpu.
     GrContext*                             fContext;
 
diff --git a/src/gpu/GrPathProcessor.cpp b/src/gpu/GrPathProcessor.cpp
index bae3e80..d75ac73 100644
--- a/src/gpu/GrPathProcessor.cpp
+++ b/src/gpu/GrPathProcessor.cpp
@@ -24,7 +24,7 @@
     }
 
     void emitCode(EmitArgs& args) override {
-        GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+        GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
         const GrPathProcessor& pathProc = args.fGP.cast<GrPathProcessor>();
 
         if (!pathProc.viewMatrix().hasPerspective()) {
diff --git a/src/gpu/GrProcessor.h b/src/gpu/GrProcessor.h
index 690a1f6..9fae1b0 100644
--- a/src/gpu/GrProcessor.h
+++ b/src/gpu/GrProcessor.h
@@ -166,6 +166,18 @@
         return str;
     }
 
+    /**
+     * Platform specific built-in features that a processor can request for the fragment shader.
+     */
+    enum RequiredFeatures {
+        kNone_RequiredFeatures             = 0,
+        kSampleLocations_RequiredFeature   = 1 << 0
+    };
+
+    GR_DECL_BITFIELD_OPS_FRIENDS(RequiredFeatures);
+
+    RequiredFeatures requiredFeatures() const { return fRequiredFeatures; }
+
     void* operator new(size_t size);
     void operator delete(void* target);
 
@@ -183,15 +195,30 @@
 
 protected:
     GrProcessor(ClassID classID)
-    : fClassID(classID) {}
+    : fClassID(classID)
+    , fRequiredFeatures(kNone_RequiredFeatures) {}
+
+    /**
+     * If the prcoessor will generate code that uses platform specific built-in features, then it
+     * must call these methods from its constructor. Otherwise, requests to use these features will
+     * be denied.
+     */
+    void setWillUseSampleLocations() { fRequiredFeatures |= kSampleLocations_RequiredFeature; }
+
+    void combineRequiredFeatures(const GrProcessor& other) {
+        fRequiredFeatures |= other.fRequiredFeatures;
+    }
 
 private:
     GrProcessor(const GrProcessor&) = delete;
     GrProcessor& operator=(const GrProcessor&) = delete;
 
-    ClassID fClassID;
+    ClassID          fClassID;
+    RequiredFeatures fRequiredFeatures;
 };
 
+GR_MAKE_BITFIELD_OPS(GrProcessor::RequiredFeatures);
+
 /** A GrProcessor with the ability to access textures, buffers, and image storages. */
 class GrResourceIOProcessor : public GrProcessor {
 public:
diff --git a/src/gpu/GrProgramDesc.cpp b/src/gpu/GrProgramDesc.cpp
index c3d6198..9c0add6 100644
--- a/src/gpu/GrProgramDesc.cpp
+++ b/src/gpu/GrProgramDesc.cpp
@@ -169,6 +169,7 @@
         desc->key().reset();
         return false;
     }
+    GrProcessor::RequiredFeatures requiredFeatures = primProc.requiredFeatures();
 
     for (int i = 0; i < pipeline.numFragmentProcessors(); ++i) {
         const GrFragmentProcessor& fp = pipeline.getFragmentProcessor(i);
@@ -176,6 +177,7 @@
             desc->key().reset();
             return false;
         }
+        requiredFeatures |= fp.requiredFeatures();
     }
 
     const GrXferProcessor& xp = pipeline.getXferProcessor();
@@ -190,6 +192,7 @@
         desc->key().reset();
         return false;
     }
+    requiredFeatures |= xp.requiredFeatures();
 
     // --------DO NOT MOVE HEADER ABOVE THIS LINE--------------------------------------------------
     // Because header is a pointer into the dynamic array, we can't push any new data into the key
@@ -201,6 +204,16 @@
 
     GrRenderTargetProxy* proxy = pipeline.proxy();
 
+    if (requiredFeatures & GrProcessor::kSampleLocations_RequiredFeature) {
+        SkASSERT(pipeline.isHWAntialiasState());
+
+        GrRenderTarget* rt = pipeline.renderTarget();
+        header->fSamplePatternKey =
+            rt->renderTargetPriv().getMultisampleSpecs(pipeline).fUniqueID;
+    } else {
+        header->fSamplePatternKey = 0;
+    }
+
     header->fOutputSwizzle = shaderCaps.configOutputSwizzle(proxy->config()).asKey();
 
     header->fSnapVerticesToPixelCenters = pipeline.snapVerticesToPixelCenters();
diff --git a/src/gpu/GrProgramDesc.h b/src/gpu/GrProgramDesc.h
index fc7a54e..a20cdfc 100644
--- a/src/gpu/GrProgramDesc.h
+++ b/src/gpu/GrProgramDesc.h
@@ -81,13 +81,9 @@
         return !(*this == other);
     }
 
-    void setSurfaceOriginKey(GrSurfaceOrigin origin) {
-        SkASSERT(kTopLeft_GrSurfaceOrigin == origin || kBottomLeft_GrSurfaceOrigin == origin);
+    void setSurfaceOriginKey(int key) {
         KeyHeader* header = this->atOffset<KeyHeader, kHeaderOffset>();
-        header->fSurfaceOriginKey = origin;
-
-        GR_STATIC_ASSERT(0 == kTopLeft_GrSurfaceOrigin);
-        GR_STATIC_ASSERT(1 == kBottomLeft_GrSurfaceOrigin);
+        header->fSurfaceOriginKey = key;
     }
 
     static bool Less(const GrProgramDesc& a, const GrProgramDesc& b) {
@@ -104,15 +100,18 @@
     }
 
     struct KeyHeader {
+        // Set to uniquely identify the sample pattern, or 0 if the shader doesn't use sample
+        // locations.
+        uint8_t                     fSamplePatternKey;
         // Set to uniquely idenitify any swizzling of the shader's output color(s).
-        uint8_t fOutputSwizzle;
-        uint8_t fColorFragmentProcessorCnt; // Can be packed to 4 bits if required.
-        uint8_t fCoverageFragmentProcessorCnt;
+        uint8_t                     fOutputSwizzle;
+        uint8_t                     fColorFragmentProcessorCnt : 4;
+        uint8_t                     fCoverageFragmentProcessorCnt : 4;
         // Set to uniquely identify the rt's origin, or 0 if the shader does not require this info.
-        bool fSurfaceOriginKey : 1; // Can be packed to 2 bits if required.
-        bool fSnapVerticesToPixelCenters : 1;
-        bool fHasPointSize : 1;
-        uint8_t fPad : 5;
+        uint8_t                     fSurfaceOriginKey : 2;
+        uint8_t                     fSnapVerticesToPixelCenters : 1;
+        uint8_t                     fHasPointSize : 1;
+        uint8_t                     fPad : 4;
     };
     GR_STATIC_ASSERT(sizeof(KeyHeader) == 4);
 
diff --git a/src/gpu/GrRenderTarget.cpp b/src/gpu/GrRenderTarget.cpp
index fa56ae7..3090219 100644
--- a/src/gpu/GrRenderTarget.cpp
+++ b/src/gpu/GrRenderTarget.cpp
@@ -24,6 +24,7 @@
         : INHERITED(gpu, desc)
         , fSampleCnt(desc.fSampleCnt)
         , fStencilAttachment(stencil)
+        , fMultisampleSpecsID(0)
         , fFlags(flags) {
     SkASSERT(desc.fFlags & kRenderTarget_GrSurfaceFlag);
     SkASSERT(!(fFlags & GrRenderTargetFlags::kMixedSampled) || fSampleCnt > 0);
@@ -93,3 +94,16 @@
     return this->getStencilAttachment()->bits();
 }
 
+const GrGpu::MultisampleSpecs&
+GrRenderTargetPriv::getMultisampleSpecs(const GrPipeline& pipeline) const {
+    SkASSERT(fRenderTarget == pipeline.renderTarget()); // TODO: remove RT from pipeline.
+    GrGpu* gpu = fRenderTarget->getGpu();
+    if (auto id = fRenderTarget->fMultisampleSpecsID) {
+        SkASSERT(gpu->queryMultisampleSpecs(pipeline).fUniqueID == id);
+        return gpu->getMultisampleSpecs(id);
+    }
+    const GrGpu::MultisampleSpecs& specs = gpu->queryMultisampleSpecs(pipeline);
+    fRenderTarget->fMultisampleSpecsID = specs.fUniqueID;
+    return specs;
+}
+
diff --git a/src/gpu/GrRenderTargetPriv.h b/src/gpu/GrRenderTargetPriv.h
index 9822e7a..e228518 100644
--- a/src/gpu/GrRenderTargetPriv.h
+++ b/src/gpu/GrRenderTargetPriv.h
@@ -32,6 +32,11 @@
 
     int numStencilBits() const;
 
+    // Finds a render target's multisample specs. The pipeline is only needed in case the info isn't
+    // cached and we need to flush the draw state in order to query it. The pipeline is not expected
+    // to affect the multisample information itself.
+    const GrGpu::MultisampleSpecs& getMultisampleSpecs(const GrPipeline&) const;
+
     GrRenderTargetFlags flags() const { return fRenderTarget->fFlags; }
 
 private:
diff --git a/src/gpu/GrShaderCaps.cpp b/src/gpu/GrShaderCaps.cpp
index 9c82c21..a53ea92 100644
--- a/src/gpu/GrShaderCaps.cpp
+++ b/src/gpu/GrShaderCaps.cpp
@@ -43,6 +43,9 @@
     fFlatInterpolationSupport = false;
     fPreferFlatInterpolation = false;
     fNoPerspectiveInterpolationSupport = false;
+    fMultisampleInterpolationSupport = false;
+    fSampleVariablesSupport = false;
+    fSampleMaskOverrideCoverageSupport = false;
     fExternalTextureSupport = false;
     fTexelFetchSupport = false;
     fVertexIDSupport = false;
@@ -58,6 +61,8 @@
     fExternalTextureExtensionString = nullptr;
     fTexelBufferExtensionString = nullptr;
     fNoPerspectiveInterpolationExtensionString = nullptr;
+    fMultisampleInterpolationExtensionString = nullptr;
+    fSampleVariablesExtensionString = nullptr;
     fFBFetchColorName = nullptr;
     fFBFetchExtensionString = nullptr;
     fImageLoadStoreExtensionString = nullptr;
@@ -115,6 +120,9 @@
     writer->appendBool("Flat interpolation support", fFlatInterpolationSupport);
     writer->appendBool("Prefer flat interpolation", fPreferFlatInterpolation);
     writer->appendBool("No perspective interpolation support", fNoPerspectiveInterpolationSupport);
+    writer->appendBool("Multisample interpolation support", fMultisampleInterpolationSupport);
+    writer->appendBool("Sample variables support", fSampleVariablesSupport);
+    writer->appendBool("Sample mask override coverage support", fSampleMaskOverrideCoverageSupport);
     writer->appendBool("External texture support", fExternalTextureSupport);
     writer->appendBool("texelFetch support", fTexelFetchSupport);
     writer->appendBool("sk_VertexID support", fVertexIDSupport);
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.cpp b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
index b81952f..032d177 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.cpp
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.cpp
@@ -30,7 +30,7 @@
 }
 
 void GrCCCoverageProcessor::Shader::emitFragmentCode(const GrCCCoverageProcessor& proc,
-                                                     GrGLSLFragmentBuilder* f,
+                                                     GrGLSLPPFragmentBuilder* f,
                                                      const char* skOutputColor,
                                                      const char* skOutputCoverage) const {
     f->codeAppendf("half coverage = 0;");
@@ -62,7 +62,7 @@
     s->codeAppendf("%s = float3(-n, dot(n, %s) - .5);", outputDistanceEquation, leftPt);
 }
 
-int GrCCCoverageProcessor::Shader::DefineSoftSampleLocations(GrGLSLFragmentBuilder* f,
+int GrCCCoverageProcessor::Shader::DefineSoftSampleLocations(GrGLSLPPFragmentBuilder* f,
                                                              const char* samplesName) {
     // Standard DX11 sample locations.
 #if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_IOS)
diff --git a/src/gpu/ccpr/GrCCCoverageProcessor.h b/src/gpu/ccpr/GrCCCoverageProcessor.h
index d3183e8..eeb2ace 100644
--- a/src/gpu/ccpr/GrCCCoverageProcessor.h
+++ b/src/gpu/ccpr/GrCCCoverageProcessor.h
@@ -15,7 +15,7 @@
 #include "glsl/GrGLSLGeometryProcessor.h"
 #include "glsl/GrGLSLVarying.h"
 
-class GrGLSLFragmentBuilder;
+class GrGLSLPPFragmentBuilder;
 class GrGLSLVertexGeoBuilder;
 class GrMesh;
 
@@ -165,7 +165,7 @@
         void emitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
                           const char* position, const char* coverage, const char* wind);
 
-        void emitFragmentCode(const GrCCCoverageProcessor& proc, GrGLSLFragmentBuilder*,
+        void emitFragmentCode(const GrCCCoverageProcessor& proc, GrGLSLPPFragmentBuilder*,
                               const char* skOutputColor, const char* skOutputCoverage) const;
 
         // Defines an equation ("dot(float3(pt, 1), distance_equation)") that is -1 on the outside
@@ -197,7 +197,7 @@
 
         // Emits the fragment code that calculates a pixel's coverage value. If using
         // WindHandling::kHandled, this value must be signed appropriately.
-        virtual void onEmitFragmentCode(GrGLSLFragmentBuilder*,
+        virtual void onEmitFragmentCode(GrGLSLPPFragmentBuilder*,
                                         const char* outputCoverage) const = 0;
 
         // Returns the name of a Shader's internal varying at the point where where its value is
@@ -212,7 +212,7 @@
         // center. Subclasses can use this for software multisampling.
         //
         // Returns the number of samples.
-        static int DefineSoftSampleLocations(GrGLSLFragmentBuilder* f, const char* samplesName);
+        static int DefineSoftSampleLocations(GrGLSLPPFragmentBuilder* f, const char* samplesName);
 
     private:
         GrGLSLVarying fWind;
diff --git a/src/gpu/ccpr/GrCCCubicShader.cpp b/src/gpu/ccpr/GrCCCubicShader.cpp
index f952ef0..295de0c 100644
--- a/src/gpu/ccpr/GrCCCubicShader.cpp
+++ b/src/gpu/ccpr/GrCCCubicShader.cpp
@@ -101,7 +101,7 @@
                     OutName(fGradMatrix), fKLMMatrix.c_str(), fKLMMatrix.c_str());
 }
 
-void GrCCCubicHullShader::onEmitFragmentCode(GrGLSLFragmentBuilder* f,
+void GrCCCubicHullShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
                                              const char* outputCoverage) const {
     f->codeAppendf("float k = %s.x, l = %s.y, m = %s.z, d = %s.w;",
                    fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn(), fKLMD.fsIn());
@@ -132,7 +132,7 @@
                   fKLMMatrix.c_str(), fEdgeDistanceEquation.c_str());
 }
 
-void GrCCCubicCornerShader::onEmitFragmentCode(GrGLSLFragmentBuilder* f,
+void GrCCCubicCornerShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
                                                const char* outputCoverage) const {
     f->codeAppendf("float2x4 grad_klmd = float2x4(%s, %s);", fdKLMDdx.fsIn(), fdKLMDdy.fsIn());
 
diff --git a/src/gpu/ccpr/GrCCCubicShader.h b/src/gpu/ccpr/GrCCCubicShader.h
index 836781a..a9875b6 100644
--- a/src/gpu/ccpr/GrCCCubicShader.h
+++ b/src/gpu/ccpr/GrCCCubicShader.h
@@ -41,7 +41,7 @@
 
 class GrCCCubicHullShader : public GrCCCubicShader {
     void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code) override;
-    void onEmitFragmentCode(GrGLSLFragmentBuilder*, const char* outputCoverage) const override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
 
     GrGLSLVarying fGradMatrix;
 };
@@ -50,7 +50,7 @@
     void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
                          GeometryVars*) const override;
     void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code) override;
-    void onEmitFragmentCode(GrGLSLFragmentBuilder*, const char* outputCoverage) const override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
 
     GrGLSLVarying fdKLMDdx;
     GrGLSLVarying fdKLMDdy;
diff --git a/src/gpu/ccpr/GrCCPathProcessor.cpp b/src/gpu/ccpr/GrCCPathProcessor.cpp
index 62aa36e..f93a0bd 100644
--- a/src/gpu/ccpr/GrCCPathProcessor.cpp
+++ b/src/gpu/ccpr/GrCCPathProcessor.cpp
@@ -217,7 +217,7 @@
                          args.fFPCoordTransformHandler);
 
     // Fragment shader.
-    GrGLSLFragmentBuilder* f = args.fFragBuilder;
+    GrGLSLPPFragmentBuilder* f = args.fFragBuilder;
 
     f->codeAppend ("half coverage_count = ");
     f->appendTextureLookup(args.fTexSamplers[0], texcoord.fsIn(), kFloat2_GrSLType);
diff --git a/src/gpu/ccpr/GrCCQuadraticShader.cpp b/src/gpu/ccpr/GrCCQuadraticShader.cpp
index 3b6a62d..d08026a 100644
--- a/src/gpu/ccpr/GrCCQuadraticShader.cpp
+++ b/src/gpu/ccpr/GrCCQuadraticShader.cpp
@@ -79,7 +79,7 @@
                   OutName(fGrad), OutName(fXYD), fCanonicalMatrix.c_str());
 }
 
-void GrCCQuadraticHullShader::onEmitFragmentCode(GrGLSLFragmentBuilder* f,
+void GrCCQuadraticHullShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
                                                  const char* outputCoverage) const {
     f->codeAppendf("float d = (%s.x * %s.x - %s.y) * inversesqrt(dot(%s, %s));",
                    fXYD.fsIn(), fXYD.fsIn(), fXYD.fsIn(), fGrad.fsIn(), fGrad.fsIn());
@@ -109,7 +109,7 @@
                   fEdgeDistanceEquation.c_str());
 }
 
-void GrCCQuadraticCornerShader::onEmitFragmentCode(GrGLSLFragmentBuilder* f,
+void GrCCQuadraticCornerShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
                                                    const char* outputCoverage) const {
     f->codeAppendf("float x = %s.x, y = %s.y, d = %s.z;",
                    fXYD.fsIn(), fXYD.fsIn(), fXYD.fsIn());
diff --git a/src/gpu/ccpr/GrCCQuadraticShader.h b/src/gpu/ccpr/GrCCQuadraticShader.h
index 91c2fa1..09fe01c 100644
--- a/src/gpu/ccpr/GrCCQuadraticShader.h
+++ b/src/gpu/ccpr/GrCCQuadraticShader.h
@@ -48,7 +48,7 @@
     void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
                          GeometryVars*) const override;
     void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code) override;
-    void onEmitFragmentCode(GrGLSLFragmentBuilder*, const char* outputCoverage) const override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
 
     GrGLSLVarying fGrad;
 };
@@ -60,7 +60,7 @@
     void onEmitSetupCode(GrGLSLVertexGeoBuilder*, const char* pts, const char* repetitionID,
                          GeometryVars*) const override;
     void onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code) override;
-    void onEmitFragmentCode(GrGLSLFragmentBuilder*, const char* outputCoverage) const override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
 
     GrGLSLVarying fdXYDdx;
     GrGLSLVarying fdXYDdy;
diff --git a/src/gpu/ccpr/GrCCTriangleShader.cpp b/src/gpu/ccpr/GrCCTriangleShader.cpp
index 5aa7413..b507b37 100644
--- a/src/gpu/ccpr/GrCCTriangleShader.cpp
+++ b/src/gpu/ccpr/GrCCTriangleShader.cpp
@@ -27,7 +27,7 @@
     return WindHandling::kHandled;
 }
 
-void GrCCTriangleShader::onEmitFragmentCode(GrGLSLFragmentBuilder* f,
+void GrCCTriangleShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
                                             const char* outputCoverage) const {
     f->codeAppendf("%s = %s;", outputCoverage, fCoverageTimesWind.fsIn());
 }
@@ -109,7 +109,7 @@
     return WindHandling::kNotHandled;
 }
 
-void GrCCTriangleCornerShader::onEmitFragmentCode(GrGLSLFragmentBuilder* f,
+void GrCCTriangleCornerShader::onEmitFragmentCode(GrGLSLPPFragmentBuilder* f,
                                                   const char* outputCoverage) const {
     // By the time we reach this shader, the pixel is in the following state:
     //
diff --git a/src/gpu/ccpr/GrCCTriangleShader.h b/src/gpu/ccpr/GrCCTriangleShader.h
index d86f860..d40c236 100644
--- a/src/gpu/ccpr/GrCCTriangleShader.h
+++ b/src/gpu/ccpr/GrCCTriangleShader.h
@@ -20,7 +20,7 @@
     WindHandling onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
                                 const char* position, const char* coverage,
                                 const char* wind) override;
-    void onEmitFragmentCode(GrGLSLFragmentBuilder*, const char* outputCoverage) const override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder*, const char* outputCoverage) const override;
 
     GrGLSLVarying fCoverageTimesWind;
 };
@@ -36,7 +36,7 @@
     WindHandling onEmitVaryings(GrGLSLVaryingHandler*, GrGLSLVarying::Scope, SkString* code,
                                 const char* position, const char* coverage,
                                 const char* wind) override;
-    void onEmitFragmentCode(GrGLSLFragmentBuilder* f, const char* outputCoverage) const override;
+    void onEmitFragmentCode(GrGLSLPPFragmentBuilder* f, const char* outputCoverage) const override;
 
     GrShaderVar fAABoxMatrices{"aa_box_matrices", kFloat2x2_GrSLType, 2};
     GrShaderVar fAABoxTranslates{"aa_box_translates", kFloat2_GrSLType, 2};
diff --git a/src/gpu/ddl/GrDDLGpu.h b/src/gpu/ddl/GrDDLGpu.h
index 36c4f04..16deab0 100644
--- a/src/gpu/ddl/GrDDLGpu.h
+++ b/src/gpu/ddl/GrDDLGpu.h
@@ -43,6 +43,12 @@
         return true;
     }
 
+    void onQueryMultisampleSpecs(GrRenderTarget* rt, GrSurfaceOrigin, const GrStencilSettings&,
+                                 int* effectiveSampleCnt, SamplePattern*) override {
+        SkASSERT(0);
+        *effectiveSampleCnt = 0; // ??
+    }
+
     GrGpuRTCommandBuffer* createCommandBuffer(
                                     GrRenderTarget*, GrSurfaceOrigin,
                                     const GrGpuRTCommandBuffer::LoadAndStoreInfo&,
diff --git a/src/gpu/effects/GrBezierEffect.cpp b/src/gpu/effects/GrBezierEffect.cpp
index 4cc8770..25148cd 100644
--- a/src/gpu/effects/GrBezierEffect.cpp
+++ b/src/gpu/effects/GrBezierEffect.cpp
@@ -81,7 +81,7 @@
     varyingHandler->addVarying("ConicCoeffs", &v);
     vertBuilder->codeAppendf("%s = %s;", v.vsOut(), gp.inConicCoeffs()->fName);
 
-    GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+    GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
     this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
 
@@ -333,7 +333,7 @@
     varyingHandler->addVarying("HairQuadEdge", &v);
     vertBuilder->codeAppendf("%s = %s;", v.vsOut(), gp.inHairQuadEdge()->fName);
 
-    GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+    GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
     this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
 
@@ -534,7 +534,7 @@
     // emit attributes
     varyingHandler->emitAttributes(gp);
 
-    GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+    GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
     if (!gp.colorIgnored()) {
         this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
diff --git a/src/gpu/effects/GrBitmapTextGeoProc.cpp b/src/gpu/effects/GrBitmapTextGeoProc.cpp
index 3d822e0..a9ebdca 100644
--- a/src/gpu/effects/GrBitmapTextGeoProc.cpp
+++ b/src/gpu/effects/GrBitmapTextGeoProc.cpp
@@ -43,7 +43,7 @@
         append_index_uv_varyings(args, btgp.inTextureCoords()->fName, atlasSizeInvName,
                                  &uv, &texIdx, nullptr);
 
-        GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+        GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
         // Setup pass through color
         if (btgp.hasVertexColor()) {
             varyingHandler->addPassThroughAttribute(btgp.inColor(), args.fOutputColor);
diff --git a/src/gpu/effects/GrDistanceFieldGeoProc.cpp b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
index 092750e..8a1a4de 100644
--- a/src/gpu/effects/GrDistanceFieldGeoProc.cpp
+++ b/src/gpu/effects/GrDistanceFieldGeoProc.cpp
@@ -27,7 +27,7 @@
     void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override{
         const GrDistanceFieldA8TextGeoProc& dfTexEffect =
                 args.fGP.cast<GrDistanceFieldA8TextGeoProc>();
-        GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+        GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
         GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
@@ -309,7 +309,7 @@
         const GrDistanceFieldPathGeoProc& dfPathEffect =
                 args.fGP.cast<GrDistanceFieldPathGeoProc>();
 
-        GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+        GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
         GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
@@ -592,7 +592,7 @@
                                                           "AtlasSizeInv",
                                                           &atlasSizeInvName);
 
-        GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+        GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
         // setup pass through color
         varyingHandler->addPassThroughAttribute(dfTexEffect.inColor(), args.fOutputColor);
diff --git a/src/gpu/effects/GrShadowGeoProc.cpp b/src/gpu/effects/GrShadowGeoProc.cpp
index 3312610..507101d 100644
--- a/src/gpu/effects/GrShadowGeoProc.cpp
+++ b/src/gpu/effects/GrShadowGeoProc.cpp
@@ -22,7 +22,7 @@
         GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
         GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
         GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
-        GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+        GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
         // emit attributes
         varyingHandler->emitAttributes(rsgp);
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 2c72d18..3b90676 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -117,6 +117,13 @@
         fTextureBarrierSupport = ctxInfo.hasExtension("GL_NV_texture_barrier");
     }
 
+    if (kGL_GrGLStandard == standard) {
+        fSampleLocationsSupport = version >= GR_GL_VER(3,2) ||
+                                  ctxInfo.hasExtension("GL_ARB_texture_multisample");
+    } else {
+        fSampleLocationsSupport = version >= GR_GL_VER(3,1);
+    }
+
     fImagingSupport = kGL_GrGLStandard == standard &&
                       ctxInfo.hasExtension("GL_ARB_imaging");
 
@@ -723,6 +730,35 @@
         }
     }
 
+    if (kGL_GrGLStandard == standard) {
+        shaderCaps->fMultisampleInterpolationSupport =
+                ctxInfo.glslGeneration() >= k400_GrGLSLGeneration;
+    } else {
+        if (ctxInfo.glslGeneration() >= k320es_GrGLSLGeneration) {
+            shaderCaps->fMultisampleInterpolationSupport = true;
+        } else if (ctxInfo.hasExtension("GL_OES_shader_multisample_interpolation")) {
+            shaderCaps->fMultisampleInterpolationSupport = true;
+            shaderCaps->fMultisampleInterpolationExtensionString =
+                "GL_OES_shader_multisample_interpolation";
+        }
+    }
+
+    if (kGL_GrGLStandard == standard) {
+        shaderCaps->fSampleVariablesSupport = ctxInfo.glslGeneration() >= k400_GrGLSLGeneration;
+    } else {
+        if (ctxInfo.glslGeneration() >= k320es_GrGLSLGeneration) {
+            shaderCaps->fSampleVariablesSupport = true;
+        } else if (ctxInfo.hasExtension("GL_OES_sample_variables")) {
+            shaderCaps->fSampleVariablesSupport = true;
+            shaderCaps->fSampleVariablesExtensionString = "GL_OES_sample_variables";
+        }
+    }
+
+    if (shaderCaps->fSampleVariablesSupport &&
+        ctxInfo.hasExtension("GL_NV_sample_mask_override_coverage")) {
+        shaderCaps->fSampleMaskOverrideCoverageSupport = true;
+    }
+
     shaderCaps->fVersionDeclString = get_glsl_version_decl_string(standard,
                                                                   shaderCaps->fGLSLGeneration,
                                                                   fIsCoreProfile);
@@ -2262,6 +2298,12 @@
         shaderCaps->fFBFetchSupport = false;
     }
 
+    // Pre-361 NVIDIA has a bug with NV_sample_mask_override_coverage.
+    if (kNVIDIA_GrGLDriver == ctxInfo.driver() &&
+        ctxInfo.driverVersion() < GR_GL_DRIVER_VER(361,00)) {
+        shaderCaps->fSampleMaskOverrideCoverageSupport = false;
+    }
+
     // Adreno GPUs have a tendency to drop tiles when there is a divide-by-zero in a shader
     shaderCaps->fDropsTileOnZeroDivide = kQualcomm_GrGLVendor == ctxInfo.vendor();
 
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index c666da9..ae31495 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -4343,6 +4343,38 @@
     return true;
 }
 
+void GrGLGpu::onQueryMultisampleSpecs(GrRenderTarget* rt, GrSurfaceOrigin rtOrigin,
+                                      const GrStencilSettings& stencil,
+                                      int* effectiveSampleCnt, SamplePattern* samplePattern) {
+    SkASSERT(GrFSAAType::kMixedSamples != rt->fsaaType() ||
+             rt->renderTargetPriv().getStencilAttachment() || stencil.isDisabled());
+
+    this->flushStencil(stencil);
+    this->flushHWAAState(rt, true, !stencil.isDisabled());
+    this->flushRenderTarget(static_cast<GrGLRenderTarget*>(rt), &SkIRect::EmptyIRect());
+
+    if (0 != this->caps()->maxRasterSamples()) {
+        GR_GL_GetIntegerv(this->glInterface(), GR_GL_EFFECTIVE_RASTER_SAMPLES, effectiveSampleCnt);
+    } else {
+        GR_GL_GetIntegerv(this->glInterface(), GR_GL_SAMPLES, effectiveSampleCnt);
+    }
+
+    SkASSERT(*effectiveSampleCnt >= rt->numStencilSamples());
+
+    if (this->caps()->sampleLocationsSupport()) {
+        samplePattern->reset(*effectiveSampleCnt);
+        for (int i = 0; i < *effectiveSampleCnt; ++i) {
+            GrGLfloat pos[2];
+            GL_CALL(GetMultisamplefv(GR_GL_SAMPLE_POSITION, i, pos));
+            if (kTopLeft_GrSurfaceOrigin == rtOrigin) {
+                (*samplePattern)[i].set(pos[0], pos[1]);
+            } else {
+                (*samplePattern)[i].set(pos[0], 1 - pos[1]);
+            }
+        }
+    }
+}
+
 void GrGLGpu::xferBarrier(GrRenderTarget* rt, GrXferBarrierType type) {
     SkASSERT(type);
     switch (type) {
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 95fdc60..9ac86ba 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -267,6 +267,9 @@
                        GrSurface* src, GrSurfaceOrigin srcOrigin,
                        const SkIRect& srcRect, const SkIPoint& dstPoint) override;
 
+    void onQueryMultisampleSpecs(GrRenderTarget*, GrSurfaceOrigin, const GrStencilSettings&,
+                                 int* effectiveSampleCnt, SamplePattern*) override;
+
     // binds texture unit in GL
     void setTextureUnit(int unitIdx);
 
diff --git a/src/gpu/gl/GrGLGpuProgramCache.cpp b/src/gpu/gl/GrGLGpuProgramCache.cpp
index 0dde6bf..b79c17f 100644
--- a/src/gpu/gl/GrGLGpuProgramCache.cpp
+++ b/src/gpu/gl/GrGLGpuProgramCache.cpp
@@ -83,7 +83,7 @@
     if (!entry) {
         // Didn't find an origin-independent version, check with the specific origin
         GrSurfaceOrigin origin = pipeline.proxy()->origin();
-        desc.setSurfaceOriginKey(origin);
+        desc.setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(origin));
         desc.finalize();
         entry = fMap.find(desc);
     }
diff --git a/src/gpu/gl/builders/GrGLProgramBuilder.cpp b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
index 1597a09..7ae63a2 100644
--- a/src/gpu/gl/builders/GrGLProgramBuilder.cpp
+++ b/src/gpu/gl/builders/GrGLProgramBuilder.cpp
@@ -98,7 +98,8 @@
     *shaderIds->append() = shaderId;
     if (inputs.fFlipY) {
         GrProgramDesc* d = this->desc();
-        d->setSurfaceOriginKey(this->pipeline().proxy()->origin());
+        d->setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(
+                                                     this->pipeline().proxy()->origin()));
         d->finalize();
     }
 
diff --git a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
index db252e3..6de545b 100644
--- a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
@@ -17,6 +17,18 @@
 
 const char* GrGLSLFragmentShaderBuilder::kDstColorName = "_dstColor";
 
+static const char* sample_offset_array_name(GrGLSLFPFragmentBuilder::Coordinates coords) {
+    static const char* kArrayNames[] = {
+        "deviceSpaceSampleOffsets",
+        "windowSpaceSampleOffsets"
+    };
+    return kArrayNames[coords];
+
+    GR_STATIC_ASSERT(0 == GrGLSLFPFragmentBuilder::kSkiaDevice_Coordinates);
+    GR_STATIC_ASSERT(1 == GrGLSLFPFragmentBuilder::kGLSLWindow_Coordinates);
+    GR_STATIC_ASSERT(SK_ARRAY_COUNT(kArrayNames) == GrGLSLFPFragmentBuilder::kLast_Coordinates + 1);
+}
+
 static const char* specific_layout_qualifier_name(GrBlendEquation equation) {
     SkASSERT(GrBlendEquationIsAdvanced(equation));
 
@@ -58,19 +70,47 @@
                      kGrBlendEquationCnt - kFirstAdvancedGrBlendEquation);
 }
 
+uint8_t GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(GrSurfaceOrigin origin) {
+    SkASSERT(kTopLeft_GrSurfaceOrigin == origin || kBottomLeft_GrSurfaceOrigin == origin);
+    return origin + 1;
+
+    GR_STATIC_ASSERT(0 == kTopLeft_GrSurfaceOrigin);
+    GR_STATIC_ASSERT(1 == kBottomLeft_GrSurfaceOrigin);
+}
+
 GrGLSLFragmentShaderBuilder::GrGLSLFragmentShaderBuilder(GrGLSLProgramBuilder* program)
     : GrGLSLFragmentBuilder(program)
     , fSetupFragPosition(false)
     , fHasCustomColorOutput(false)
     , fCustomColorOutputIndex(-1)
     , fHasSecondaryOutput(false)
+    , fUsedSampleOffsetArrays(0)
+    , fHasInitializedSampleMask(false)
     , fForceHighPrecision(false) {
     fSubstageIndices.push_back(0);
 #ifdef SK_DEBUG
+    fUsedProcessorFeatures = GrProcessor::kNone_RequiredFeatures;
     fHasReadDstColor = false;
 #endif
 }
 
+bool GrGLSLFragmentShaderBuilder::enableFeature(GLSLFeature feature) {
+    const GrShaderCaps& shaderCaps = *fProgramBuilder->shaderCaps();
+    switch (feature) {
+        case kMultisampleInterpolation_GLSLFeature:
+            if (!shaderCaps.multisampleInterpolationSupport()) {
+                return false;
+            }
+            if (const char* extension = shaderCaps.multisampleInterpolationExtensionString()) {
+                this->addFeature(1 << kMultisampleInterpolation_GLSLFeature, extension);
+            }
+            return true;
+        default:
+            SK_ABORT("Unexpected GLSLFeature requested.");
+            return false;
+    }
+}
+
 SkString GrGLSLFragmentShaderBuilder::ensureCoords2D(const GrShaderVar& coords) {
     if (kFloat3_GrSLType != coords.getType() && kHalf3_GrSLType != coords.getType()) {
         SkASSERT(kFloat2_GrSLType == coords.getType() || kHalf2_GrSLType == coords.getType());
@@ -84,6 +124,57 @@
     return coords2D;
 }
 
+void GrGLSLFragmentShaderBuilder::appendOffsetToSample(const char* sampleIdx, Coordinates coords) {
+    SkASSERT(fProgramBuilder->header().fSamplePatternKey);
+    SkDEBUGCODE(fUsedProcessorFeatures |= GrProcessor::kSampleLocations_RequiredFeature);
+    if (kTopLeft_GrSurfaceOrigin == this->getSurfaceOrigin()) {
+        // With a top left origin, device and window space are equal, so we only use device coords.
+        coords = kSkiaDevice_Coordinates;
+    }
+    this->codeAppendf("%s[%s]", sample_offset_array_name(coords), sampleIdx);
+    fUsedSampleOffsetArrays |= (1 << coords);
+}
+
+void GrGLSLFragmentShaderBuilder::maskSampleCoverage(const char* mask, bool invert) {
+    const GrShaderCaps& shaderCaps = *fProgramBuilder->shaderCaps();
+    if (!shaderCaps.sampleVariablesSupport()) {
+        SkDEBUGFAIL("Attempted to mask sample coverage without support.");
+        return;
+    }
+    if (const char* extension = shaderCaps.sampleVariablesExtensionString()) {
+        this->addFeature(1 << kSampleVariables_GLSLPrivateFeature, extension);
+    }
+    if (!fHasInitializedSampleMask) {
+        this->codePrependf("gl_SampleMask[0] = -1;");
+        fHasInitializedSampleMask = true;
+    }
+    if (invert) {
+        this->codeAppendf("gl_SampleMask[0] &= ~(%s);", mask);
+    } else {
+        this->codeAppendf("gl_SampleMask[0] &= %s;", mask);
+    }
+}
+
+void GrGLSLFragmentShaderBuilder::overrideSampleCoverage(const char* mask) {
+    const GrShaderCaps& shaderCaps = *fProgramBuilder->shaderCaps();
+    if (!shaderCaps.sampleMaskOverrideCoverageSupport()) {
+        SkDEBUGFAIL("Attempted to override sample coverage without support.");
+        return;
+    }
+    SkASSERT(shaderCaps.sampleVariablesSupport());
+    if (const char* extension = shaderCaps.sampleVariablesExtensionString()) {
+        this->addFeature(1 << kSampleVariables_GLSLPrivateFeature, extension);
+    }
+    if (this->addFeature(1 << kSampleMaskOverrideCoverage_GLSLPrivateFeature,
+                         "GL_NV_sample_mask_override_coverage")) {
+        // Redeclare gl_SampleMask with layout(override_coverage) if we haven't already.
+        fOutputs.push_back().set(kInt_GrSLType, "gl_SampleMask", 1, GrShaderVar::kOut_TypeModifier,
+                                 kHigh_GrSLPrecision, "override_coverage");
+    }
+    this->codeAppendf("gl_SampleMask[0] = %s;", mask);
+    fHasInitializedSampleMask = true;
+}
+
 const char* GrGLSLFragmentShaderBuilder::dstColor() {
     SkDEBUGCODE(fHasReadDstColor = true;)
 
@@ -185,6 +276,33 @@
 
 void GrGLSLFragmentShaderBuilder::onFinalize() {
     fProgramBuilder->varyingHandler()->getFragDecls(&this->inputs(), &this->outputs());
+    if (fUsedSampleOffsetArrays & (1 << kSkiaDevice_Coordinates)) {
+        this->defineSampleOffsetArray(sample_offset_array_name(kSkiaDevice_Coordinates),
+                                      SkMatrix::MakeTrans(-0.5f, -0.5f));
+    }
+    if (fUsedSampleOffsetArrays & (1 << kGLSLWindow_Coordinates)) {
+        // With a top left origin, device and window space are equal, so we only use device coords.
+        SkASSERT(kBottomLeft_GrSurfaceOrigin == this->getSurfaceOrigin());
+        SkMatrix m;
+        m.setScale(1, -1);
+        m.preTranslate(-0.5f, -0.5f);
+        this->defineSampleOffsetArray(sample_offset_array_name(kGLSLWindow_Coordinates), m);
+    }
+}
+
+void GrGLSLFragmentShaderBuilder::defineSampleOffsetArray(const char* name, const SkMatrix& m) {
+    SkASSERT(fProgramBuilder->caps()->sampleLocationsSupport());
+    const GrPipeline& pipeline = fProgramBuilder->pipeline();
+    const GrRenderTargetPriv& rtp = pipeline.renderTarget()->renderTargetPriv();
+    const GrGpu::MultisampleSpecs& specs = rtp.getMultisampleSpecs(pipeline);
+    SkSTArray<16, SkPoint, true> offsets;
+    offsets.push_back_n(specs.fEffectiveSampleCnt);
+    m.mapPoints(offsets.begin(), specs.fSampleLocations, specs.fEffectiveSampleCnt);
+    this->definitions().appendf("const float2 %s[] = float2[](", name);
+    for (int i = 0; i < specs.fEffectiveSampleCnt; ++i) {
+        this->definitions().appendf("float2(%f, %f)", offsets[i].x(), offsets[i].y());
+        this->definitions().append(i + 1 != specs.fEffectiveSampleCnt ? ", " : ");\n");
+    }
 }
 
 void GrGLSLFragmentShaderBuilder::onBeforeChildProcEmitCode() {
diff --git a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h
index f5ed024..f124a44 100644
--- a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h
+++ b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.h
@@ -25,6 +25,20 @@
     virtual ~GrGLSLFragmentBuilder() {}
 
     /**
+     * Use of these features may require a GLSL extension to be enabled. Shaders may not compile
+     * if code is added that uses one of these features without calling enableFeature()
+     */
+    enum GLSLFeature {
+        kMultisampleInterpolation_GLSLFeature
+    };
+
+    /**
+     * If the feature is supported then true is returned and any necessary #extension declarations
+     * are added to the shaders. If the feature is not supported then false will be returned.
+     */
+    virtual bool enableFeature(GLSLFeature) = 0;
+
+    /**
      * This returns a variable name to access the 2D, perspective correct version of the coords in
      * the fragment shader. The passed in coordinates must either be of type kHalf2 or kHalf3. If
      * the coordinates are 3-dimensional, it a perspective divide into is emitted into the
@@ -47,6 +61,34 @@
     /** Appease the compiler; the derived class initializes GrGLSLFragmentBuilder. */
     GrGLSLFPFragmentBuilder() : GrGLSLFragmentBuilder(nullptr) {}
 
+    enum Coordinates {
+        kSkiaDevice_Coordinates,
+        kGLSLWindow_Coordinates,
+
+        kLast_Coordinates = kGLSLWindow_Coordinates
+    };
+
+    /**
+     * Appends the offset from the center of the pixel to a specified sample.
+     *
+     * @param sampleIdx      GLSL expression of the sample index.
+     * @param Coordinates    Coordinate space in which to emit the offset.
+     *
+     * A processor must call setWillUseSampleLocations in its constructor before using this method.
+     */
+    virtual void appendOffsetToSample(const char* sampleIdx, Coordinates) = 0;
+
+    /**
+     * Subtracts sample coverage from the fragment. Any sample whose corresponding bit is not found
+     * in the mask will not be written out to the framebuffer.
+     *
+     * @param mask      int that contains the sample mask. Bit N corresponds to the Nth sample.
+     * @param invert    perform a bit-wise NOT on the provided mask before applying it?
+     *
+     * Requires GLSL support for sample variables.
+     */
+    virtual void maskSampleCoverage(const char* mask, bool invert = false) = 0;
+
     /**
      * Fragment procs with child procs should call these functions before/after calling emitCode
      * on a child proc.
@@ -60,6 +102,29 @@
 };
 
 /*
+ * This class is used by primitive processors to build their fragment code.
+ */
+class GrGLSLPPFragmentBuilder : public GrGLSLFPFragmentBuilder {
+public:
+    /** Appease the compiler; the derived class initializes GrGLSLFragmentBuilder. */
+    GrGLSLPPFragmentBuilder() : GrGLSLFragmentBuilder(nullptr) {}
+
+    /**
+     * Overrides the fragment's sample coverage. The provided mask determines which samples will now
+     * be written out to the framebuffer. Note that this mask can be reduced by a future call to
+     * maskSampleCoverage.
+     *
+     * If a primitive processor uses this method, it must guarantee that every codepath through the
+     * shader overrides the sample mask at some point.
+     *
+     * @param mask    int that contains the new coverage mask. Bit N corresponds to the Nth sample.
+     *
+     * Requires NV_sample_mask_override_coverage.
+     */
+    virtual void overrideSampleCoverage(const char* mask) = 0;
+};
+
+/*
  * This class is used by Xfer processors to build their fragment code.
  */
 class GrGLSLXPFragmentBuilder : virtual public GrGLSLFragmentBuilder {
@@ -83,14 +148,22 @@
 /*
  * This class implements the various fragment builder interfaces.
  */
-class GrGLSLFragmentShaderBuilder : public GrGLSLFPFragmentBuilder, public GrGLSLXPFragmentBuilder {
+class GrGLSLFragmentShaderBuilder : public GrGLSLPPFragmentBuilder, public GrGLSLXPFragmentBuilder {
 public:
+   /** Returns a nonzero key for a surface's origin. This should only be called if a processor will
+       use the fragment position and/or sample locations. */
+    static uint8_t KeyForSurfaceOrigin(GrSurfaceOrigin);
+
     GrGLSLFragmentShaderBuilder(GrGLSLProgramBuilder* program);
 
     // Shared GrGLSLFragmentBuilder interface.
+    bool enableFeature(GLSLFeature) override;
     virtual SkString ensureCoords2D(const GrShaderVar&) override;
 
     // GrGLSLFPFragmentBuilder interface.
+    void appendOffsetToSample(const char* sampleIdx, Coordinates) override;
+    void maskSampleCoverage(const char* mask, bool invert = false) override;
+    void overrideSampleCoverage(const char* mask) override;
     const SkString& getMangleString() const override { return fMangleString; }
     void onBeforeChildProcEmitCode() override;
     void onAfterChildProcEmitCode() override;
@@ -112,8 +185,10 @@
 #ifdef SK_DEBUG
     // As GLSLProcessors emit code, there are some conditions we need to verify.  We use the below
     // state to track this.  The reset call is called per processor emitted.
+    GrProcessor::RequiredFeatures usedProcessorFeatures() const { return fUsedProcessorFeatures; }
     bool hasReadDstColor() const { return fHasReadDstColor; }
     void resetVerification() {
+        fUsedProcessorFeatures = GrProcessor::kNone_RequiredFeatures;
         fHasReadDstColor = false;
     }
 #endif
@@ -124,6 +199,7 @@
     GrSurfaceOrigin getSurfaceOrigin() const;
 
     void onFinalize() override;
+    void defineSampleOffsetArray(const char* name, const SkMatrix&);
 
     static const char* kDstColorName;
 
@@ -151,11 +227,14 @@
     bool          fHasCustomColorOutput;
     int           fCustomColorOutputIndex;
     bool          fHasSecondaryOutput;
+    uint8_t       fUsedSampleOffsetArrays;
+    bool          fHasInitializedSampleMask;
     bool          fForceHighPrecision;
 
 #ifdef SK_DEBUG
     // some state to verify shaders and effects are consistent, this is reset between effects by
     // the program creator
+    GrProcessor::RequiredFeatures fUsedProcessorFeatures;
     bool fHasReadDstColor;
 #endif
 
diff --git a/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp b/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp
index e31f7c2..02fa2e7 100644
--- a/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp
+++ b/src/gpu/glsl/GrGLSLPrimitiveProcessor.cpp
@@ -36,7 +36,7 @@
     return combined;
 }
 
-void GrGLSLPrimitiveProcessor::setupUniformColor(GrGLSLFragmentBuilder* fragBuilder,
+void GrGLSLPrimitiveProcessor::setupUniformColor(GrGLSLPPFragmentBuilder* fragBuilder,
                                                  GrGLSLUniformHandler* uniformHandler,
                                                  const char* outputName,
                                                  UniformHandle* colorUniform) {
diff --git a/src/gpu/glsl/GrGLSLPrimitiveProcessor.h b/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
index dc398a4..30ca143 100644
--- a/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
+++ b/src/gpu/glsl/GrGLSLPrimitiveProcessor.h
@@ -14,7 +14,7 @@
 #include "glsl/GrGLSLUniformHandler.h"
 
 class GrPrimitiveProcessor;
-class GrGLSLFragmentBuilder;
+class GrGLSLPPFragmentBuilder;
 class GrGLSLGeometryBuilder;
 class GrGLSLGPBuilder;
 class GrGLSLVaryingHandler;
@@ -67,7 +67,7 @@
     struct EmitArgs {
         EmitArgs(GrGLSLVertexBuilder* vertBuilder,
                  GrGLSLGeometryBuilder* geomBuilder,
-                 GrGLSLFragmentBuilder* fragBuilder,
+                 GrGLSLPPFragmentBuilder* fragBuilder,
                  GrGLSLVaryingHandler* varyingHandler,
                  GrGLSLUniformHandler* uniformHandler,
                  const GrShaderCaps* caps,
@@ -93,7 +93,7 @@
             , fFPCoordTransformHandler(transformHandler) {}
         GrGLSLVertexBuilder* fVertBuilder;
         GrGLSLGeometryBuilder* fGeomBuilder;
-        GrGLSLFragmentBuilder* fFragBuilder;
+        GrGLSLPPFragmentBuilder* fFragBuilder;
         GrGLSLVaryingHandler* fVaryingHandler;
         GrGLSLUniformHandler* fUniformHandler;
         const GrShaderCaps* fShaderCaps;
@@ -129,7 +129,7 @@
     static SkMatrix GetTransformMatrix(const SkMatrix& localMatrix, const GrCoordTransform&);
 
 protected:
-    void setupUniformColor(GrGLSLFragmentBuilder* fragBuilder,
+    void setupUniformColor(GrGLSLPPFragmentBuilder* fragBuilder,
                            GrGLSLUniformHandler* uniformHandler,
                            const char* outputName,
                            UniformHandle* colorUniform);
diff --git a/src/gpu/glsl/GrGLSLProgramBuilder.cpp b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
index 914a958..04ebc6a 100644
--- a/src/gpu/glsl/GrGLSLProgramBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLProgramBuilder.cpp
@@ -360,13 +360,16 @@
 
 #ifdef SK_DEBUG
 void GrGLSLProgramBuilder::verify(const GrPrimitiveProcessor& gp) {
+    SkASSERT(fFS.usedProcessorFeatures() == gp.requiredFeatures());
 }
 
 void GrGLSLProgramBuilder::verify(const GrXferProcessor& xp) {
+    SkASSERT(fFS.usedProcessorFeatures() == xp.requiredFeatures());
     SkASSERT(fFS.hasReadDstColor() == xp.willReadDstColor());
 }
 
 void GrGLSLProgramBuilder::verify(const GrFragmentProcessor& fp) {
+    SkASSERT(fFS.usedProcessorFeatures() == fp.requiredFeatures());
 }
 #endif
 
diff --git a/src/gpu/glsl/GrGLSLShaderBuilder.h b/src/gpu/glsl/GrGLSLShaderBuilder.h
index 8459218..0708625 100644
--- a/src/gpu/glsl/GrGLSLShaderBuilder.h
+++ b/src/gpu/glsl/GrGLSLShaderBuilder.h
@@ -173,7 +173,9 @@
         kTexelBuffer_GLSLPrivateFeature,
         kFramebufferFetch_GLSLPrivateFeature,
         kNoPerspectiveInterpolation_GLSLPrivateFeature,
-        kLastGLSLPrivateFeature = kNoPerspectiveInterpolation_GLSLPrivateFeature
+        kSampleVariables_GLSLPrivateFeature,
+        kSampleMaskOverrideCoverage_GLSLPrivateFeature,
+        kLastGLSLPrivateFeature = kSampleMaskOverrideCoverage_GLSLPrivateFeature
     };
 
     /*
diff --git a/src/gpu/mock/GrMockGpu.h b/src/gpu/mock/GrMockGpu.h
index 4c3b062..5185ce2 100644
--- a/src/gpu/mock/GrMockGpu.h
+++ b/src/gpu/mock/GrMockGpu.h
@@ -39,6 +39,11 @@
                        GrSurface* src, GrSurfaceOrigin srcOrigin,
                        const SkIRect& srcRect, const SkIPoint& dstPoint) override { return true; }
 
+    void onQueryMultisampleSpecs(GrRenderTarget* rt, GrSurfaceOrigin, const GrStencilSettings&,
+                                 int* effectiveSampleCnt, SamplePattern*) override {
+        *effectiveSampleCnt = rt->numStencilSamples();
+    }
+
     GrGpuRTCommandBuffer* createCommandBuffer(
                                     GrRenderTarget*, GrSurfaceOrigin,
                                     const GrGpuRTCommandBuffer::LoadAndStoreInfo&,
diff --git a/src/gpu/mtl/GrMtlCaps.mm b/src/gpu/mtl/GrMtlCaps.mm
index 22001fd..2bce74a 100644
--- a/src/gpu/mtl/GrMtlCaps.mm
+++ b/src/gpu/mtl/GrMtlCaps.mm
@@ -150,6 +150,7 @@
 
     fTextureBarrierSupport = false; // Need to figure out if we can do this
 
+    fSampleLocationsSupport = false;
     fMultisampleDisableSupport = false;
 
     if (this->isMac() || 3 == fFamilyGroup) {
diff --git a/src/gpu/mtl/GrMtlGpu.h b/src/gpu/mtl/GrMtlGpu.h
index 7372f8e..5a5289b 100644
--- a/src/gpu/mtl/GrMtlGpu.h
+++ b/src/gpu/mtl/GrMtlGpu.h
@@ -46,6 +46,9 @@
                        const SkIRect& srcRect,
                        const SkIPoint& dstPoint) override { return false; }
 
+    void onQueryMultisampleSpecs(GrRenderTarget*, GrSurfaceOrigin, const GrStencilSettings&,
+                                 int* effectiveSampleCnt, SamplePattern*) override {}
+
     GrGpuRTCommandBuffer* createCommandBuffer(
                                     GrRenderTarget*, GrSurfaceOrigin,
                                     const GrGpuRTCommandBuffer::LoadAndStoreInfo&,
diff --git a/src/gpu/ops/GrAAConvexPathRenderer.cpp b/src/gpu/ops/GrAAConvexPathRenderer.cpp
index a5cc8aa..badaadd 100644
--- a/src/gpu/ops/GrAAConvexPathRenderer.cpp
+++ b/src/gpu/ops/GrAAConvexPathRenderer.cpp
@@ -573,7 +573,7 @@
             // Setup pass through color
             varyingHandler->addPassThroughAttribute(qe.fInColor, args.fOutputColor);
 
-            GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+            GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
             // Setup position
             this->writeOutputPosition(vertBuilder, gpArgs, qe.fInPosition->fName);
diff --git a/src/gpu/ops/GrDashOp.cpp b/src/gpu/ops/GrDashOp.cpp
index 0cd087f..9728aa9 100644
--- a/src/gpu/ops/GrDashOp.cpp
+++ b/src/gpu/ops/GrDashOp.cpp
@@ -919,7 +919,7 @@
     varyingHandler->addVarying("CircleParams", &circleParams);
     vertBuilder->codeAppendf("%s = %s;", circleParams.vsOut(), dce.inCircleParams()->fName);
 
-    GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+    GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
     this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
 
@@ -1122,7 +1122,7 @@
     varyingHandler->addVarying("RectParams", &inRectParams);
     vertBuilder->codeAppendf("%s = %s;", inRectParams.vsOut(), de.inRectParams()->fName);
 
-    GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+    GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
     // Setup pass through color
     this->setupUniformColor(fragBuilder, uniformHandler, args.fOutputColor, &fColorUniform);
 
diff --git a/src/gpu/ops/GrMSAAPathRenderer.cpp b/src/gpu/ops/GrMSAAPathRenderer.cpp
index 306feb5..842d710 100644
--- a/src/gpu/ops/GrMSAAPathRenderer.cpp
+++ b/src/gpu/ops/GrMSAAPathRenderer.cpp
@@ -153,7 +153,7 @@
                                  qp.inPosition()->asShaderVar(), SkMatrix::I(),
                                  args.fFPCoordTransformHandler);
 
-            GrGLSLFragmentBuilder* fsBuilder = args.fFragBuilder;
+            GrGLSLPPFragmentBuilder* fsBuilder = args.fFragBuilder;
             fsBuilder->codeAppendf("if (%s.x * %s.x >= %s.y) discard;", uv.fsIn(), uv.fsIn(),
                                                                         uv.fsIn());
             fsBuilder->codeAppendf("%s = half4(1.0);", args.fOutputCoverage);
diff --git a/src/gpu/ops/GrOvalOpFactory.cpp b/src/gpu/ops/GrOvalOpFactory.cpp
index e727047..9194f67 100644
--- a/src/gpu/ops/GrOvalOpFactory.cpp
+++ b/src/gpu/ops/GrOvalOpFactory.cpp
@@ -113,7 +113,7 @@
             GrGLSLVertexBuilder* vertBuilder = args.fVertBuilder;
             GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
             GrGLSLUniformHandler* uniformHandler = args.fUniformHandler;
-            GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+            GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
 
             // emit attributes
             varyingHandler->emitAttributes(cgp);
@@ -281,7 +281,7 @@
             varyingHandler->addVarying("EllipseRadii", &ellipseRadii);
             vertBuilder->codeAppendf("%s = %s;", ellipseRadii.vsOut(), egp.fInEllipseRadii->fName);
 
-            GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+            GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
             // setup pass through color
             varyingHandler->addPassThroughAttribute(egp.fInColor, args.fOutputColor);
 
@@ -420,7 +420,7 @@
             varyingHandler->addVarying("EllipseOffsets1", &offsets1);
             vertBuilder->codeAppendf("%s = %s;", offsets1.vsOut(), diegp.fInEllipseOffsets1->fName);
 
-            GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+            GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
             varyingHandler->addPassThroughAttribute(diegp.fInColor, args.fOutputColor);
 
             // Setup position
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index 9a93dc5..f7e3d47 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -1879,6 +1879,13 @@
     return false;
 }
 
+void GrVkGpu::onQueryMultisampleSpecs(GrRenderTarget* rt, GrSurfaceOrigin, const GrStencilSettings&,
+                                      int* effectiveSampleCnt, SamplePattern*) {
+    // TODO: stub.
+    SkASSERT(!this->caps()->sampleLocationsSupport());
+    *effectiveSampleCnt = rt->numStencilSamples();
+}
+
 bool GrVkGpu::onGetReadPixelsInfo(GrSurface* srcSurface, GrSurfaceOrigin srcOrigin,
                                   int width, int height, size_t rowBytes,
                                   GrPixelConfig readConfig, DrawPreference* drawPreference,
diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h
index 59e7cd0..8cc98d9 100644
--- a/src/gpu/vk/GrVkGpu.h
+++ b/src/gpu/vk/GrVkGpu.h
@@ -78,6 +78,9 @@
                        GrSurface* src, GrSurfaceOrigin srcOrigin,
                        const SkIRect& srcRect, const SkIPoint& dstPoint) override;
 
+    void onQueryMultisampleSpecs(GrRenderTarget*, GrSurfaceOrigin, const GrStencilSettings&,
+                                 int* effectiveSampleCnt, SamplePattern*) override;
+
     void xferBarrier(GrRenderTarget*, GrXferBarrierType) override {}
 
     GrBackendTexture createTestingOnlyBackendTexture(void* pixels, int w, int h,
diff --git a/src/gpu/vk/GrVkPipelineStateBuilder.cpp b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
index 7ba9f32..9b9b070 100644
--- a/src/gpu/vk/GrVkPipelineStateBuilder.cpp
+++ b/src/gpu/vk/GrVkPipelineStateBuilder.cpp
@@ -79,7 +79,8 @@
         this->addRTHeightUniform(SKSL_RTHEIGHT_NAME);
     }
     if (inputs.fFlipY) {
-        desc->setSurfaceOriginKey(this->pipeline().proxy()->origin());
+        desc->setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(
+                                                     this->pipeline().proxy()->origin()));
         desc->finalize();
     }
     return result;
diff --git a/src/gpu/vk/GrVkPipelineStateCache.cpp b/src/gpu/vk/GrVkPipelineStateCache.cpp
index 0aee985..caffe05 100644
--- a/src/gpu/vk/GrVkPipelineStateCache.cpp
+++ b/src/gpu/vk/GrVkPipelineStateCache.cpp
@@ -103,7 +103,7 @@
     if (!entry) {
         // Didn't find an origin-independent version, check with the specific origin
         GrSurfaceOrigin origin = pipeline.proxy()->origin();
-        desc.setSurfaceOriginKey(origin);
+        desc.setSurfaceOriginKey(GrGLSLFragmentShaderBuilder::KeyForSurfaceOrigin(origin));
         desc.finalize();
         entry = fMap.find(desc);
     }
diff --git a/src/sksl/SkSLIRGenerator.cpp b/src/sksl/SkSLIRGenerator.cpp
index 54a62cc..3388a47 100644
--- a/src/sksl/SkSLIRGenerator.cpp
+++ b/src/sksl/SkSLIRGenerator.cpp
@@ -130,6 +130,9 @@
     CAP(dropsTileOnZeroDivide);
     CAP(flatInterpolationSupport);
     CAP(noperspectiveInterpolationSupport);
+    CAP(multisampleInterpolationSupport);
+    CAP(sampleVariablesSupport);
+    CAP(sampleMaskOverrideCoverageSupport);
     CAP(externalTextureSupport);
     CAP(texelFetchSupport);
     CAP(imageLoadStoreSupport);
diff --git a/src/sksl/SkSLUtil.h b/src/sksl/SkSLUtil.h
index 6eb4bba..787698f 100644
--- a/src/sksl/SkSLUtil.h
+++ b/src/sksl/SkSLUtil.h
@@ -99,6 +99,18 @@
         return true;
     }
 
+    bool multisampleInterpolationSupport() const {
+        return true;
+    }
+
+    bool sampleVariablesSupport() const {
+        return true;
+    }
+
+    bool sampleMaskOverrideCoverageSupport() const {
+        return true;
+    }
+
     bool externalTextureSupport() const {
         return true;
     }
diff --git a/tests/GpuSampleLocationsTest.cpp b/tests/GpuSampleLocationsTest.cpp
new file mode 100644
index 0000000..de15e03
--- /dev/null
+++ b/tests/GpuSampleLocationsTest.cpp
@@ -0,0 +1,199 @@
+/*
+ * Copyright 2016 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkTypes.h"
+#include "SkPoint.h"
+#include "Test.h"
+#include <vector>
+
+#if SK_SUPPORT_GPU
+
+#include "GrAppliedClip.h"
+#include "GrRenderTargetContext.h"
+#include "GrRenderTargetPriv.h"
+#include "GrTypesPriv.h"
+#include "gl/GrGLGpu.h"
+#include "gl/debug/DebugGLTestContext.h"
+
+typedef std::vector<SkPoint> SamplePattern;
+
+static const SamplePattern kTestPatterns[] = {
+    SamplePattern{ // Intel on mac, msaa8, offscreen.
+        {0.562500, 0.312500},
+        {0.437500, 0.687500},
+        {0.812500, 0.562500},
+        {0.312500, 0.187500},
+        {0.187500, 0.812500},
+        {0.062500, 0.437500},
+        {0.687500, 0.937500},
+        {0.937500, 0.062500}
+    },
+
+    SamplePattern{ // Intel on mac, msaa8, on-screen.
+        {0.562500, 0.687500},
+        {0.437500, 0.312500},
+        {0.812500, 0.437500},
+        {0.312500, 0.812500},
+        {0.187500, 0.187500},
+        {0.062500, 0.562500},
+        {0.687500, 0.062500},
+        {0.937500, 0.937500}
+    },
+
+    SamplePattern{ // NVIDIA, msaa16.
+        {0.062500, 0.000000},
+        {0.250000, 0.125000},
+        {0.187500, 0.375000},
+        {0.437500, 0.312500},
+        {0.500000, 0.062500},
+        {0.687500, 0.187500},
+        {0.750000, 0.437500},
+        {0.937500, 0.250000},
+        {0.000000, 0.500000},
+        {0.312500, 0.625000},
+        {0.125000, 0.750000},
+        {0.375000, 0.875000},
+        {0.562500, 0.562500},
+        {0.812500, 0.687500},
+        {0.625000, 0.812500},
+        {0.875000, 0.937500}
+    },
+
+    SamplePattern{ // NVIDIA, mixed samples, 16:1.
+        {0.250000, 0.125000},
+        {0.625000, 0.812500},
+        {0.500000, 0.062500},
+        {0.812500, 0.687500},
+        {0.187500, 0.375000},
+        {0.875000, 0.937500},
+        {0.125000, 0.750000},
+        {0.750000, 0.437500},
+        {0.937500, 0.250000},
+        {0.312500, 0.625000},
+        {0.437500, 0.312500},
+        {0.000000, 0.500000},
+        {0.375000, 0.875000},
+        {0.687500, 0.187500},
+        {0.062500, 0.000000},
+        {0.562500, 0.562500}
+    }
+};
+constexpr int numTestPatterns = SK_ARRAY_COUNT(kTestPatterns);
+
+class TestSampleLocationsInterface : public SkNoncopyable {
+public:
+    virtual void overrideSamplePattern(const SamplePattern&) = 0;
+    virtual ~TestSampleLocationsInterface() {}
+};
+
+void assert_equal(skiatest::Reporter* reporter, const SamplePattern& pattern,
+                  const GrGpu::MultisampleSpecs& specs, bool flipY) {
+    GrAlwaysAssert(specs.fSampleLocations);
+    if ((int)pattern.size() != specs.fEffectiveSampleCnt) {
+        REPORT_FAILURE(reporter, "", SkString("Sample pattern has wrong number of samples."));
+        return;
+    }
+    for (int i = 0; i < specs.fEffectiveSampleCnt; ++i) {
+        SkPoint expectedLocation = specs.fSampleLocations[i];
+        if (flipY) {
+            expectedLocation.fY = 1 - expectedLocation.fY;
+        }
+        if (pattern[i] != expectedLocation) {
+            REPORT_FAILURE(reporter, "", SkString("Sample pattern has wrong sample location."));
+            return;
+        }
+    }
+}
+
+void test_sampleLocations(skiatest::Reporter* reporter, TestSampleLocationsInterface* testInterface,
+                          GrContext* ctx) {
+    SkRandom rand;
+    sk_sp<GrRenderTargetContext> bottomUps[numTestPatterns];
+    sk_sp<GrRenderTargetContext> topDowns[numTestPatterns];
+    for (int i = 0; i < numTestPatterns; ++i) {
+        int numSamples = (int)kTestPatterns[i].size();
+        GrAlwaysAssert(numSamples > 1 && SkIsPow2(numSamples));
+        bottomUps[i] = ctx->makeDeferredRenderTargetContext(
+                           SkBackingFit::kExact, 100, 100, kRGBA_8888_GrPixelConfig, nullptr,
+                           rand.nextRangeU(1 + numSamples / 2, numSamples), GrMipMapped::kNo,
+                           kBottomLeft_GrSurfaceOrigin);
+        topDowns[i] = ctx->makeDeferredRenderTargetContext(
+                          SkBackingFit::kExact, 100, 100, kRGBA_8888_GrPixelConfig, nullptr,
+                          rand.nextRangeU(1 + numSamples / 2, numSamples), GrMipMapped::kNo,
+                          kTopLeft_GrSurfaceOrigin);
+    }
+
+    // Ensure all sample locations get queried and/or cached properly.
+    for (int repeat = 0; repeat < 2; ++repeat) {
+        for (int i = 0; i < numTestPatterns; ++i) {
+            testInterface->overrideSamplePattern(kTestPatterns[i]);
+            for (GrRenderTargetContext* rtc : {bottomUps[i].get(), topDowns[i].get()}) {
+                GrPipeline dummyPipeline(rtc->asRenderTargetProxy(),
+                                         GrPipeline::ScissorState::kDisabled,
+                                         SkBlendMode::kSrcOver);
+                GrRenderTarget* rt = rtc->accessRenderTarget();
+                assert_equal(reporter, kTestPatterns[i],
+                             rt->renderTargetPriv().getMultisampleSpecs(dummyPipeline),
+                             kBottomLeft_GrSurfaceOrigin == rtc->asSurfaceProxy()->origin());
+            }
+        }
+    }
+
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+class GLTestSampleLocationsInterface : public TestSampleLocationsInterface, public GrGLInterface {
+public:
+    GLTestSampleLocationsInterface() : fTestContext(sk_gpu_test::CreateDebugGLTestContext()) {
+        fStandard = fTestContext->gl()->fStandard;
+        fExtensions = fTestContext->gl()->fExtensions;
+        fFunctions = fTestContext->gl()->fFunctions;
+
+        fFunctions.fGetIntegerv = [&](GrGLenum pname, GrGLint* params) {
+            GrAlwaysAssert(GR_GL_EFFECTIVE_RASTER_SAMPLES != pname);
+            if (GR_GL_SAMPLES == pname) {
+                GrAlwaysAssert(!fSamplePattern.empty());
+                *params = (int)fSamplePattern.size();
+            } else {
+                fTestContext->gl()->fFunctions.fGetIntegerv(pname, params);
+            }
+        };
+
+        fFunctions.fGetMultisamplefv = [&](GrGLenum pname, GrGLuint index, GrGLfloat* val) {
+            GrAlwaysAssert(GR_GL_SAMPLE_POSITION == pname);
+            val[0] = fSamplePattern[index].fX;
+            val[1] = fSamplePattern[index].fY;
+        };
+    }
+
+    operator GrBackendContext() {
+        return reinterpret_cast<GrBackendContext>(static_cast<GrGLInterface*>(this));
+    }
+
+    void overrideSamplePattern(const SamplePattern& newPattern) override {
+        fSamplePattern = newPattern;
+    }
+
+private:
+    std::unique_ptr<sk_gpu_test::GLTestContext> fTestContext;
+    SamplePattern                               fSamplePattern;
+};
+
+DEF_GPUTEST(GLSampleLocations, reporter, /* options */) {
+    auto testInterface = sk_make_sp<GLTestSampleLocationsInterface>();
+    sk_sp<GrContext> ctx(GrContext::MakeGL(testInterface));
+
+    // This test relies on at least 2 samples.
+    int supportedSample = ctx->caps()->getSampleCount(2, kRGBA_8888_GrPixelConfig);
+    if (supportedSample < 2) {
+        return;
+    }
+    test_sampleLocations(reporter, testInterface.get(), ctx.get());
+}
+
+#endif
diff --git a/tests/GrMeshTest.cpp b/tests/GrMeshTest.cpp
index 86437d5..ccc9a73 100644
--- a/tests/GrMeshTest.cpp
+++ b/tests/GrMeshTest.cpp
@@ -341,7 +341,7 @@
         }
         gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertex");
 
-        GrGLSLFragmentBuilder* f = args.fFragBuilder;
+        GrGLSLPPFragmentBuilder* f = args.fFragBuilder;
         f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
     }
 };
diff --git a/tests/GrPipelineDynamicStateTest.cpp b/tests/GrPipelineDynamicStateTest.cpp
index 00da84e..cd1ce1e 100644
--- a/tests/GrPipelineDynamicStateTest.cpp
+++ b/tests/GrPipelineDynamicStateTest.cpp
@@ -93,7 +93,7 @@
         v->codeAppendf("float2 vertex = %s;", mp.fVertex.fName);
         gpArgs->fPositionVar.set(kFloat2_GrSLType, "vertex");
 
-        GrGLSLFragmentBuilder* f = args.fFragBuilder;
+        GrGLSLPPFragmentBuilder* f = args.fFragBuilder;
         f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
     }
 };
diff --git a/tests/PrimitiveProcessorTest.cpp b/tests/PrimitiveProcessorTest.cpp
index ffe8175..406d5e2 100644
--- a/tests/PrimitiveProcessorTest.cpp
+++ b/tests/PrimitiveProcessorTest.cpp
@@ -73,7 +73,7 @@
                         const GP& gp = args.fGP.cast<GP>();
                         args.fVaryingHandler->emitAttributes(gp);
                         this->writeOutputPosition(args.fVertBuilder, gpArgs, gp.getAttrib(0).fName);
-                        GrGLSLFragmentBuilder* fragBuilder = args.fFragBuilder;
+                        GrGLSLPPFragmentBuilder* fragBuilder = args.fFragBuilder;
                         fragBuilder->codeAppendf("%s = half4(1);", args.fOutputColor);
                         fragBuilder->codeAppendf("%s = half4(1);", args.fOutputCoverage);
                     }