Fix caching of sample locations

The original caching logic for sample locations wishfully assumed that
the GPU would always use the same sample pattern for render targets
that had the same number of samples. It turns out we can't rely on
that. This change improves the caching logic to handle mismatched
simple patterns with the same count, and adds a unit test that
emulates different sample patterns observed on real hardware.

BUG=skia:
GOLD_TRYBOT_URL= https://gold.skia.org/search?issue=2111423002

Committed: https://skia.googlesource.com/skia/+/09d49a3bfe2d1e652a648ce1ea0962b38d10d166
Review-Url: https://codereview.chromium.org/2111423002
diff --git a/include/gpu/GrRenderTarget.h b/include/gpu/GrRenderTarget.h
index ff75af3..77b04d2 100644
--- a/include/gpu/GrRenderTarget.h
+++ b/include/gpu/GrRenderTarget.h
@@ -163,6 +163,7 @@
                    SampleConfig sampleConfig, GrStencilAttachment* stencil = nullptr)
         : INHERITED(gpu, desc)
         , fStencilAttachment(stencil)
+        , fMultisampleSpecsID(0)
         , fSampleConfig(sampleConfig)
         , fLastDrawTarget(nullptr) {
         fResolveRect.setLargestInverted();
@@ -184,6 +185,7 @@
     friend class GrRenderTargetPriv;
 
     GrStencilAttachment*  fStencilAttachment;
+    uint8_t               fMultisampleSpecsID;
     SampleConfig          fSampleConfig;
 
     SkIRect               fResolveRect;
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index 1397845..4cb96ed 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -46,8 +46,8 @@
 GrGpu::GrGpu(GrContext* context)
     : fResetTimestamp(kExpiredTimestamp+1)
     , fResetBits(kAll_GrBackendState)
-    , fMultisampleSpecsAllocator(1)
     , fContext(context) {
+    fMultisampleSpecs.emplace_back(0, 0, nullptr); // Index 0 is an invalid unique id.
 }
 
 GrGpu::~GrGpu() {}
@@ -425,58 +425,63 @@
     }
 }
 
-inline static uint8_t multisample_specs_id(uint8_t numSamples, GrSurfaceOrigin origin,
-                                           const GrCaps& caps) {
-    if (!caps.sampleLocationsSupport()) {
-        return numSamples;
-    }
-
-    SkASSERT(numSamples < 128);
-    SkASSERT(kTopLeft_GrSurfaceOrigin == origin || kBottomLeft_GrSurfaceOrigin == origin);
-    return (numSamples << 1) | (origin - 1);
-
-    GR_STATIC_ASSERT(1 == kTopLeft_GrSurfaceOrigin);
-    GR_STATIC_ASSERT(2 == kBottomLeft_GrSurfaceOrigin);
-}
-
 const GrGpu::MultisampleSpecs& GrGpu::getMultisampleSpecs(GrRenderTarget* rt,
                                                           const GrStencilSettings& stencil) {
-    const GrSurfaceDesc& desc = rt->desc();
-    uint8_t surfDescKey = multisample_specs_id(desc.fSampleCnt, desc.fOrigin, *this->caps());
-    if (fMultisampleSpecsMap.count() > surfDescKey && fMultisampleSpecsMap[surfDescKey]) {
-#if !defined(SK_DEBUG)
-        // In debug mode we query the multisample info every time and verify the caching is correct.
-        return *fMultisampleSpecsMap[surfDescKey];
+    SkASSERT(rt->desc().fSampleCnt > 1);
+
+#ifndef SK_DEBUG
+    // In debug mode we query the multisample info every time to verify the caching is correct.
+    if (uint8_t id = rt->renderTargetPriv().accessMultisampleSpecsID()) {
+        SkASSERT(id > 0 && id < fMultisampleSpecs.count());
+        return fMultisampleSpecs[id];
+    }
 #endif
-    }
+
     int effectiveSampleCnt;
-    SkAutoTDeleteArray<SkPoint> locations(nullptr);
-    this->onGetMultisampleSpecs(rt, stencil, &effectiveSampleCnt, &locations);
-    SkASSERT(effectiveSampleCnt && effectiveSampleCnt >= desc.fSampleCnt);
-    uint8_t effectiveKey = multisample_specs_id(effectiveSampleCnt, desc.fOrigin, *this->caps());
-    if (fMultisampleSpecsMap.count() > effectiveKey && fMultisampleSpecsMap[effectiveKey]) {
-        const MultisampleSpecs& specs = *fMultisampleSpecsMap[effectiveKey];
-        SkASSERT(effectiveKey == specs.fUniqueID);
-        SkASSERT(effectiveSampleCnt == specs.fEffectiveSampleCnt);
-        SkASSERT(!this->caps()->sampleLocationsSupport() ||
-                 !memcmp(locations.get(), specs.fSampleLocations.get(),
-                         effectiveSampleCnt * sizeof(SkPoint)));
-        SkASSERT(surfDescKey <= effectiveKey);
-        SkASSERT(!fMultisampleSpecsMap[surfDescKey] || fMultisampleSpecsMap[surfDescKey] == &specs);
-        fMultisampleSpecsMap[surfDescKey] = &specs;
-        return specs;
+    SkSTArray<16, SkPoint, true> pattern;
+    this->onGetMultisampleSpecs(rt, stencil, &effectiveSampleCnt, &pattern);
+    SkASSERT(effectiveSampleCnt >= rt->desc().fSampleCnt);
+
+    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);
+        }
     }
-    const MultisampleSpecs& specs = *new (&fMultisampleSpecsAllocator)
-        MultisampleSpecs{effectiveKey, effectiveSampleCnt, locations.release()};
-    if (fMultisampleSpecsMap.count() <= effectiveKey) {
-        int n = 1 + effectiveKey - fMultisampleSpecsMap.count();
-        fMultisampleSpecsMap.push_back_n(n, (const MultisampleSpecs*) nullptr);
-    }
-    fMultisampleSpecsMap[effectiveKey] = &specs;
-    if (effectiveSampleCnt != desc.fSampleCnt) {
-        SkASSERT(surfDescKey < effectiveKey);
-        fMultisampleSpecsMap[surfDescKey] = &specs;
-    }
-    return specs;
+    SkASSERT(id > 0);
+    SkASSERT(!rt->renderTargetPriv().accessMultisampleSpecsID() ||
+             rt->renderTargetPriv().accessMultisampleSpecsID() == id);
+
+    rt->renderTargetPriv().accessMultisampleSpecsID() = id;
+    return fMultisampleSpecs[id];
 }
 
+bool GrGpu::SamplePatternComparator::operator()(const SamplePattern& a,
+                                                const SamplePattern& b) const {
+    if (a.count() != b.count()) {
+        return a.count() < b.count();
+    }
+    for (int i = 0; i < a.count(); ++i) {
+        // This doesn't have geometric meaning. We just need to define an ordering for std::map.
+        if (a[i].x() != b[i].x()) {
+            return a[i].x() < b[i].x();
+        }
+        if (a[i].y() != b[i].y()) {
+            return a[i].y() < b[i].y();
+        }
+    }
+    return false; // Equal.
+}
diff --git a/src/gpu/GrGpu.h b/src/gpu/GrGpu.h
index a733f45..e7c2196 100644
--- a/src/gpu/GrGpu.h
+++ b/src/gpu/GrGpu.h
@@ -17,6 +17,7 @@
 #include "GrXferProcessor.h"
 #include "SkPath.h"
 #include "SkTArray.h"
+#include <map>
 
 class GrBatchTracker;
 class GrBuffer;
@@ -332,14 +333,19 @@
                      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;
+        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, contains the subpixel locations at which the GPU will
-        // sample. Pixel center is at (.5, .5) and (0, 0) indicates the top left corner.
-        SkAutoTDeleteArray<const SkPoint>  fSampleLocations;
+        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 stencil settings are only needed to flush the
@@ -495,6 +501,8 @@
     // Subclass must initialize this in its constructor.
     SkAutoTUnref<const GrCaps>    fCaps;
 
+    typedef SkTArray<SkPoint, true> SamplePattern;
+
 private:
     // called when the 3D context state is unknown. Subclass should emit any
     // assumed 3D context state and dirty any state cache.
@@ -560,10 +568,8 @@
                                const SkIPoint& dstPoint) = 0;
 
     // overridden by backend specific derived class to perform the multisample queries
-    virtual void onGetMultisampleSpecs(GrRenderTarget*,
-                                       const GrStencilSettings&,
-                                       int* effectiveSampleCnt,
-                                       SkAutoTDeleteArray<SkPoint>* sampleLocations) = 0;
+    virtual void onGetMultisampleSpecs(GrRenderTarget*, const GrStencilSettings&,
+                                       int* effectiveSampleCnt, SamplePattern*) = 0;
 
     void resetContext() {
         this->onResetContext(fResetBits);
@@ -571,12 +577,18 @@
         ++fResetTimestamp;
     }
 
-    ResetTimestamp                                                      fResetTimestamp;
-    uint32_t                                                            fResetBits;
-    SkTArray<const MultisampleSpecs*, true>                             fMultisampleSpecsMap;
-    GrTAllocator<MultisampleSpecs>                                      fMultisampleSpecsAllocator;
+    struct SamplePatternComparator {
+        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;
+    GrContext*                             fContext;
 
     friend class GrPathRendering;
     typedef SkRefCnt INHERITED;
diff --git a/src/gpu/GrRenderTargetPriv.h b/src/gpu/GrRenderTargetPriv.h
index 52eed69..db66bc3 100644
--- a/src/gpu/GrRenderTargetPriv.h
+++ b/src/gpu/GrRenderTargetPriv.h
@@ -33,6 +33,7 @@
     int numStencilBits() const;
 
     const GrGpu::MultisampleSpecs& getMultisampleSpecs(const GrStencilSettings& stencil) const;
+    uint8_t& accessMultisampleSpecsID() { return fRenderTarget->fMultisampleSpecsID; }
 
     GrRenderTarget::SampleConfig sampleConfig() const { return fRenderTarget->fSampleConfig; }
 
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 5b24d8a..a37d72e 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -4425,10 +4425,8 @@
     return true;
 }
 
-void GrGLGpu::onGetMultisampleSpecs(GrRenderTarget* rt,
-                                    const GrStencilSettings& stencil,
-                                    int* effectiveSampleCnt,
-                                    SkAutoTDeleteArray<SkPoint>* sampleLocations) {
+void GrGLGpu::onGetMultisampleSpecs(GrRenderTarget* rt, const GrStencilSettings& stencil,
+                                    int* effectiveSampleCnt, SamplePattern* samplePattern) {
     SkASSERT(!rt->hasMixedSamples() || rt->renderTargetPriv().getStencilAttachment() ||
              stencil.isDisabled());
 
@@ -4445,14 +4443,14 @@
     SkASSERT(*effectiveSampleCnt >= rt->desc().fSampleCnt);
 
     if (this->caps()->sampleLocationsSupport()) {
-        sampleLocations->reset(new SkPoint[*effectiveSampleCnt]);
+        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 == rt->origin()) {
-                (*sampleLocations)[i].set(pos[0], pos[1]);
+                (*samplePattern)[i].set(pos[0], pos[1]);
             } else {
-                (*sampleLocations)[i].set(pos[0], 1 - pos[1]);
+                (*samplePattern)[i].set(pos[0], 1 - pos[1]);
             }
         }
     }
diff --git a/src/gpu/gl/GrGLGpu.h b/src/gpu/gl/GrGLGpu.h
index 013613a..c8edbb9 100644
--- a/src/gpu/gl/GrGLGpu.h
+++ b/src/gpu/gl/GrGLGpu.h
@@ -216,10 +216,8 @@
                        const SkIRect& srcRect,
                        const SkIPoint& dstPoint) override;
 
-    void onGetMultisampleSpecs(GrRenderTarget*,
-                               const GrStencilSettings&,
-                               int* effectiveSampleCnt,
-                               SkAutoTDeleteArray<SkPoint>* sampleLocations) override;
+    void onGetMultisampleSpecs(GrRenderTarget*, const GrStencilSettings&,
+                               int* effectiveSampleCnt, SamplePattern*) override;
 
     // binds texture unit in GL
     void setTextureUnit(int unitIdx);
diff --git a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
index a52b1a6..5d1ba51 100644
--- a/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
+++ b/src/gpu/glsl/GrGLSLFragmentShaderBuilder.cpp
@@ -365,7 +365,7 @@
     const GrGpu::MultisampleSpecs& specs = rtp.getMultisampleSpecs(pipeline.getStencil());
     SkSTArray<16, SkPoint, true> offsets;
     offsets.push_back_n(specs.fEffectiveSampleCnt);
-    m.mapPoints(offsets.begin(), specs.fSampleLocations.get(), specs.fEffectiveSampleCnt);
+    m.mapPoints(offsets.begin(), specs.fSampleLocations, specs.fEffectiveSampleCnt);
     this->definitions().append("const ");
     if (fProgramBuilder->glslCaps()->usesPrecisionModifiers()) {
         this->definitions().append("highp ");
diff --git a/src/gpu/vk/GrVkGpu.cpp b/src/gpu/vk/GrVkGpu.cpp
index 6875824..fdec861 100644
--- a/src/gpu/vk/GrVkGpu.cpp
+++ b/src/gpu/vk/GrVkGpu.cpp
@@ -1367,7 +1367,7 @@
 }
 
 void GrVkGpu::onGetMultisampleSpecs(GrRenderTarget* rt, const GrStencilSettings&,
-                                    int* effectiveSampleCnt, SkAutoTDeleteArray<SkPoint>*) {
+                                    int* effectiveSampleCnt, SamplePattern*) {
     // TODO: stub.
     SkASSERT(!this->caps()->sampleLocationsSupport());
     *effectiveSampleCnt = rt->desc().fSampleCnt;
diff --git a/src/gpu/vk/GrVkGpu.h b/src/gpu/vk/GrVkGpu.h
index df1aae1..29a3aca 100644
--- a/src/gpu/vk/GrVkGpu.h
+++ b/src/gpu/vk/GrVkGpu.h
@@ -79,10 +79,8 @@
                        const SkIRect& srcRect,
                        const SkIPoint& dstPoint) override;
 
-    void onGetMultisampleSpecs(GrRenderTarget* rt,
-                               const GrStencilSettings&,
-                               int* effectiveSampleCnt,
-                               SkAutoTDeleteArray<SkPoint>*) override;
+    void onGetMultisampleSpecs(GrRenderTarget* rt, const GrStencilSettings&,
+                               int* effectiveSampleCnt, SamplePattern*) override;
 
     bool initCopySurfaceDstDesc(const GrSurface* src, GrSurfaceDesc* desc) const override;
 
diff --git a/tests/GpuSampleLocationsTest.cpp b/tests/GpuSampleLocationsTest.cpp
new file mode 100644
index 0000000..5207c6b
--- /dev/null
+++ b/tests/GpuSampleLocationsTest.cpp
@@ -0,0 +1,193 @@
+/*
+ * 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 "GrRenderTargetPriv.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() {}
+};
+
+GrRenderTarget* SK_WARN_UNUSED_RESULT create_render_target(GrContext* ctx, GrSurfaceOrigin origin,
+                                                           int numSamples) {
+    GrSurfaceDesc desc;
+    desc.fFlags = kRenderTarget_GrSurfaceFlag;
+    desc.fOrigin = origin;
+    desc.fWidth = 100;
+    desc.fHeight = 100;
+    desc.fConfig = kBGRA_8888_GrPixelConfig;
+    desc.fSampleCnt = numSamples;
+    return ctx->textureProvider()->createTexture(desc, SkBudgeted::kNo, 0, 0)->asRenderTarget();
+}
+
+void assert_equal(skiatest::Reporter* reporter, const SamplePattern& pattern,
+                  const GrGpu::MultisampleSpecs& specs, bool flipY) {
+    GrAlwaysAssert(specs.fSampleLocations);
+    if ((int)pattern.size() != specs.fEffectiveSampleCnt) {
+        REPORTER_ASSERT_MESSAGE(reporter, false, "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) {
+            REPORTER_ASSERT_MESSAGE(reporter, false, "Sample pattern has wrong sample location.");
+            return;
+        }
+    }
+}
+
+void test_sampleLocations(skiatest::Reporter* reporter, TestSampleLocationsInterface* testInterface,
+                          GrContext* ctx) {
+    SkRandom rand;
+    SkAutoTUnref<GrRenderTarget> bottomUps[numTestPatterns];
+    SkAutoTUnref<GrRenderTarget> topDowns[numTestPatterns];
+    for (int i = 0; i < numTestPatterns; ++i) {
+        int numSamples = (int)kTestPatterns[i].size();
+        GrAlwaysAssert(numSamples > 1 && SkIsPow2(numSamples));
+        bottomUps[i].reset(create_render_target(ctx, kBottomLeft_GrSurfaceOrigin,
+                                                rand.nextRangeU(1 + numSamples / 2, numSamples)));
+        topDowns[i].reset(create_render_target(ctx, kTopLeft_GrSurfaceOrigin,
+                                               rand.nextRangeU(1 + numSamples / 2, numSamples)));
+    }
+
+    // Ensure all sample locations get queried and/or cached properly.
+    GrStencilSettings dummyStencil;
+    for (int repeat = 0; repeat < 2; ++repeat) {
+        for (int i = 0; i < numTestPatterns; ++i) {
+            testInterface->overrideSamplePattern(kTestPatterns[i]);
+            assert_equal(reporter, kTestPatterns[i],
+                         topDowns[i]->renderTargetPriv().getMultisampleSpecs(dummyStencil), false);
+            assert_equal(reporter, kTestPatterns[i],
+                         bottomUps[i]->renderTargetPriv().getMultisampleSpecs(dummyStencil), true);
+        }
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+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:
+    SkAutoTDelete<sk_gpu_test::GLTestContext>   fTestContext;
+    SamplePattern                               fSamplePattern;
+};
+
+DEF_GPUTEST(GLSampleLocations, reporter, /*factory*/) {
+    GLTestSampleLocationsInterface testInterface;
+    SkAutoTUnref<GrContext> ctx(GrContext::Create(kOpenGL_GrBackend, testInterface));
+    test_sampleLocations(reporter, &testInterface, ctx);
+}
+
+#endif
diff --git a/tools/gpu/GrTest.cpp b/tools/gpu/GrTest.cpp
index 97a9c49..83ab4ea 100644
--- a/tools/gpu/GrTest.cpp
+++ b/tools/gpu/GrTest.cpp
@@ -294,10 +294,8 @@
                        const SkIRect& srcRect,
                        const SkIPoint& dstPoint) override { return false; };
 
-    void onGetMultisampleSpecs(GrRenderTarget* rt,
-                               const GrStencilSettings&,
-                               int* effectiveSampleCnt,
-                               SkAutoTDeleteArray<SkPoint>*) override {
+    void onGetMultisampleSpecs(GrRenderTarget* rt, const GrStencilSettings&,
+                               int* effectiveSampleCnt, SamplePattern*) override {
         *effectiveSampleCnt = rt->desc().fSampleCnt;
     }