Implement support for mixed sampled render targets

Adds a new FBO type kStencil_MSFBOType that is selected whenever
NV_framebuffer_mixed_samples extension is available.  In this new
FBO type a non-msaa color buffer is created with a multisampled
stencil buffer attachment.

Replaces numSamples() with separate numColorSamples and numStencilSamples
methods in RenderTarget.

In mixed samples mode non-MSAA codepaths are used to draw simple shapes,
while NVPR-rendered paths and text are rendered with a multisampled
stencil.

BUG=skia:3177

Review URL: https://codereview.chromium.org/1001503002
diff --git a/include/gpu/GrRenderTarget.h b/include/gpu/GrRenderTarget.h
index 5984bf7..91a67ae 100644
--- a/include/gpu/GrRenderTarget.h
+++ b/include/gpu/GrRenderTarget.h
@@ -31,14 +31,65 @@
 
     // GrRenderTarget
     /**
-     * @return true if the surface is multisampled, false otherwise
-     */
-    bool isMultisampled() const { return 0 != fDesc.fSampleCnt; }
+     * On some hardware it is possible for a render target to have multisampling
+     * only in certain buffers.
+     * Enforce only two legal sample configs.
+     * kUnified_SampleConfig signifies multisampling in both color and stencil
+     * buffers and is available across all hardware.
+     * kStencil_SampleConfig means multisampling is present in stencil buffer
+     * only; this config requires hardware support of
+     * NV_framebuffer_mixed_samples.
+    */
+    enum SampleConfig {
+        kUnified_SampleConfig = 0,
+        kStencil_SampleConfig = 1
+    };
 
     /**
-     * @return the number of samples-per-pixel or zero if non-MSAA.
+     * @return true if the surface is multisampled in all buffers,
+     *         false otherwise
      */
-    int numSamples() const { return fDesc.fSampleCnt; }
+    bool isUnifiedMultisampled() const {
+        if (fSampleConfig != kUnified_SampleConfig) {
+            return false;
+        }
+        return 0 != fDesc.fSampleCnt;
+    }
+
+    /**
+     * @return true if the surface is multisampled in the stencil buffer,
+     *         false otherwise
+     */
+    bool isStencilBufferMultisampled() const {
+        return 0 != fDesc.fSampleCnt;
+    }
+
+    /**
+     * @return the number of color samples-per-pixel, or zero if non-MSAA or
+     *         multisampled in the stencil buffer only.
+     */
+    int numColorSamples() const {
+        if (fSampleConfig == kUnified_SampleConfig) {
+            return fDesc.fSampleCnt;
+        }
+        return 0;
+    }
+
+    /**
+     * @return the number of stencil samples-per-pixel, or zero if non-MSAA.
+     */
+    int numStencilSamples() const {
+        return fDesc.fSampleCnt;
+    }
+
+    /**
+     * @return true if the surface is mixed sampled, false otherwise.
+     */
+    bool hasMixedSamples() const {
+        SkASSERT(kStencil_SampleConfig != fSampleConfig ||
+                 this->isStencilBufferMultisampled());
+        return kStencil_SampleConfig == fSampleConfig;
+    }
 
     /**
      * Call to indicate the multisample contents were modified such that the
@@ -94,9 +145,11 @@
     const GrRenderTargetPriv renderTargetPriv() const;
 
 protected:
-    GrRenderTarget(GrGpu* gpu, LifeCycle lifeCycle, const GrSurfaceDesc& desc)
+    GrRenderTarget(GrGpu* gpu, LifeCycle lifeCycle, const GrSurfaceDesc& desc,
+                   SampleConfig sampleConfig)
         : INHERITED(gpu, lifeCycle, desc)
-        , fStencilAttachment(NULL) {
+        , fStencilAttachment(NULL)
+        , fSampleConfig(sampleConfig) {
         fResolveRect.setLargestInverted();
     }
 
@@ -111,10 +164,12 @@
     friend class GrRenderTargetPriv;
 
     GrStencilAttachment*  fStencilAttachment;
+    SampleConfig          fSampleConfig;
 
     SkIRect               fResolveRect;
 
     typedef GrSurface INHERITED;
 };
 
+
 #endif
diff --git a/samplecode/SampleApp.cpp b/samplecode/SampleApp.cpp
index 04b1cb02..ae352aa 100644
--- a/samplecode/SampleApp.cpp
+++ b/samplecode/SampleApp.cpp
@@ -1953,9 +1953,9 @@
     if (IsGpuDeviceType(fDeviceType) &&
         fDevManager &&
         fDevManager->getGrRenderTarget() &&
-        fDevManager->getGrRenderTarget()->numSamples() > 0) {
+        fDevManager->getGrRenderTarget()->numColorSamples() > 0) {
         title.appendf(" [MSAA: %d]",
-                       fDevManager->getGrRenderTarget()->numSamples());
+                       fDevManager->getGrRenderTarget()->numColorSamples());
     }
 #endif
 
diff --git a/src/core/SkMultiPictureDraw.cpp b/src/core/SkMultiPictureDraw.cpp
index 7cdab73..22fd05f 100644
--- a/src/core/SkMultiPictureDraw.cpp
+++ b/src/core/SkMultiPictureDraw.cpp
@@ -141,7 +141,7 @@
             GrLayerHoister::FindLayersToAtlas(context, data.fPicture, initialMatrix,
                                               clipBounds,
                                               &atlasedNeedRendering, &atlasedRecycled,
-                                              rt->numSamples());
+                                              rt->numColorSamples());
         }
     }
 
@@ -174,7 +174,7 @@
             // layers in the 'recycled' list since they have already been drawn.
             GrLayerHoister::FindLayersToHoist(context, picture, initialMatrix,
                                               clipBounds, &needRendering, &recycled,
-                                              rt->numSamples());
+                                              rt->numColorSamples());
 
             GrLayerHoister::DrawLayers(context, needRendering);
 
diff --git a/src/gpu/GrBlurUtils.cpp b/src/gpu/GrBlurUtils.cpp
index 66861ec..caea2b4 100644
--- a/src/gpu/GrBlurUtils.cpp
+++ b/src/gpu/GrBlurUtils.cpp
@@ -259,7 +259,7 @@
                                                          *devPathPtr,
                                                          strokeInfo,
                                                          grPaint.isAntiAlias(),
-                                                         renderTarget->numSamples()));
+                                                         renderTarget->numColorSamples()));
             if (mask) {
                 GrTexture* filtered;
 
diff --git a/src/gpu/GrClipMaskManager.cpp b/src/gpu/GrClipMaskManager.cpp
index 9d79a6d..17a15a3 100644
--- a/src/gpu/GrClipMaskManager.cpp
+++ b/src/gpu/GrClipMaskManager.cpp
@@ -165,7 +165,7 @@
         if (!skip) {
             GrPrimitiveEdgeType edgeType;
             if (iter.get()->isAA()) {
-                if (rt->isMultisampled()) {
+                if (rt->isUnifiedMultisampled()) {
                     // Coverage based AA clips don't place nicely with MSAA.
                     failed = true;
                     break;
@@ -306,7 +306,7 @@
     }
 
     // If MSAA is enabled we can do everything in the stencil buffer.
-    if (0 == rt->numSamples() && requiresAA) {
+    if (0 == rt->numColorSamples() && requiresAA) {
         GrTexture* result = NULL;
 
         // The top-left of the mask corresponds to the top-left corner of the bounds.
@@ -750,7 +750,7 @@
             pipelineBuilder.setDisableColorXPFactory();
 
             // if the target is MSAA then we want MSAA enabled when the clip is soft
-            if (rt->isMultisampled()) {
+            if (rt->isUnifiedMultisampled()) {
                 pipelineBuilder.setState(GrPipelineBuilder::kHWAntialias_Flag, element->isAA());
             }
 
diff --git a/src/gpu/GrContext.cpp b/src/gpu/GrContext.cpp
index ca98ffb..64f4cb3 100755
--- a/src/gpu/GrContext.cpp
+++ b/src/gpu/GrContext.cpp
@@ -250,7 +250,8 @@
                                             const SkDeviceProperties&
                                             leakyProperties,
                                             bool enableDistanceFieldFonts) {
-    if (fGpu->caps()->shaderCaps()->pathRenderingSupport() && renderTarget->isMultisampled()) {
+    if (fGpu->caps()->shaderCaps()->pathRenderingSupport() &&
+        renderTarget->isStencilBufferMultisampled()) {
         GrStencilAttachment* sb = renderTarget->renderTargetPriv().attachStencilAttachment();
         if (sb) {
             return GrStencilAndCoverTextContext::Create(this, leakyProperties);
diff --git a/src/gpu/GrDrawContext.cpp b/src/gpu/GrDrawContext.cpp
index 92a1e36..23c349b 100644
--- a/src/gpu/GrDrawContext.cpp
+++ b/src/gpu/GrDrawContext.cpp
@@ -156,7 +156,7 @@
                              SkScalar strokeWidth,
                              const SkMatrix& combinedMatrix,
                              GrColor color) {
-    if (pipelineBuilder->getRenderTarget()->isMultisampled()) {
+    if (pipelineBuilder->getRenderTarget()->isUnifiedMultisampled()) {
         return false;
     }
 
@@ -422,7 +422,8 @@
 
     GrColor color = paint.getColor();
     SkRect devBoundRect;
-    bool needAA = paint.isAntiAlias() && !pipelineBuilder.getRenderTarget()->isMultisampled();
+    bool needAA = paint.isAntiAlias() &&
+                  !pipelineBuilder.getRenderTarget()->isUnifiedMultisampled();
     bool doAA = needAA && apply_aa_to_rect(fDrawTarget, &pipelineBuilder, &devBoundRect, rect,
                                            width, viewMatrix, color);
 
@@ -455,7 +456,7 @@
         geometry.fStrokeWidth = width;
 
         // Non-AA hairlines are snapped to pixel centers to make which pixels are hit deterministic
-        bool snapToPixelCenters = (0 == width && !rt->isMultisampled());
+        bool snapToPixelCenters = (0 == width && !rt->isUnifiedMultisampled());
         SkAutoTUnref<GrBatch> batch(StrokeRectBatch::Create(geometry, snapToPixelCenters));
 
         // Depending on sub-pixel coordinates and the particular GPU, we may lose a corner of
@@ -1014,7 +1015,7 @@
 
     if (!strokeInfo.isDashed()) {
         bool useCoverageAA = paint.isAntiAlias() &&
-                !pipelineBuilder.getRenderTarget()->isMultisampled();
+                !pipelineBuilder.getRenderTarget()->isUnifiedMultisampled();
 
         if (useCoverageAA && strokeInfo.getWidth() < 0 && !path.isConvex()) {
             // Concave AA paths are expensive - try to avoid them for special cases
@@ -1062,7 +1063,7 @@
     // aa. If we have some future driver-mojo path AA that can do the right
     // thing WRT to the blend then we'll need some query on the PR.
     bool useCoverageAA = useAA &&
-        !pipelineBuilder->getRenderTarget()->isMultisampled();
+        !pipelineBuilder->getRenderTarget()->isUnifiedMultisampled();
 
 
     GrPathRendererChain::DrawType type =
diff --git a/src/gpu/GrGpu.cpp b/src/gpu/GrGpu.cpp
index 4d6f5d6..a6e9654 100644
--- a/src/gpu/GrGpu.cpp
+++ b/src/gpu/GrGpu.cpp
@@ -146,7 +146,8 @@
     }
 #endif
 
-    GrStencilAttachment::ComputeSharedStencilAttachmentKey(width, height, rt->numSamples(), &sbKey);
+    GrStencilAttachment::ComputeSharedStencilAttachmentKey(width, height,
+        rt->numStencilSamples(), &sbKey);
     SkAutoTUnref<GrStencilAttachment> sb(static_cast<GrStencilAttachment*>(
         this->getContext()->getResourceCache()->findAndRefUniqueResource(sbKey)));
     if (sb) {
diff --git a/src/gpu/GrOvalRenderer.cpp b/src/gpu/GrOvalRenderer.cpp
index f98adaa..e5e742c 100644
--- a/src/gpu/GrOvalRenderer.cpp
+++ b/src/gpu/GrOvalRenderer.cpp
@@ -610,7 +610,7 @@
                               const SkRect& oval,
                               const SkStrokeRec& stroke)
 {
-    bool useCoverageAA = useAA && !pipelineBuilder->getRenderTarget()->isMultisampled();
+    bool useCoverageAA = useAA && !pipelineBuilder->getRenderTarget()->isUnifiedMultisampled();
 
     if (!useCoverageAA) {
         return false;
@@ -1404,7 +1404,7 @@
                                 const SkRRect& origOuter,
                                 const SkRRect& origInner) {
     bool applyAA = useAA &&
-                   !pipelineBuilder->getRenderTarget()->isMultisampled();
+                   !pipelineBuilder->getRenderTarget()->isUnifiedMultisampled();
     GrPipelineBuilder::AutoRestoreFragmentProcessors arfp;
     if (!origInner.isEmpty()) {
         SkTCopyOnFirstWrite<SkRRect> inner(origInner);
@@ -1978,7 +1978,7 @@
                         stroke);
     }
 
-    bool useCoverageAA = useAA && !pipelineBuilder->getRenderTarget()->isMultisampled();
+    bool useCoverageAA = useAA && !pipelineBuilder->getRenderTarget()->isUnifiedMultisampled();
 
     // only anti-aliased rrects for now
     if (!useCoverageAA) {
diff --git a/src/gpu/GrPipelineBuilder.cpp b/src/gpu/GrPipelineBuilder.cpp
index 26422fd..b31d105 100644
--- a/src/gpu/GrPipelineBuilder.cpp
+++ b/src/gpu/GrPipelineBuilder.cpp
@@ -75,7 +75,7 @@
 
     this->setState(GrPipelineBuilder::kDither_Flag, paint.isDither());
     this->setState(GrPipelineBuilder::kHWAntialias_Flag,
-                   rt->isMultisampled() && paint.isAntiAlias());
+                   rt->isUnifiedMultisampled() && paint.isAntiAlias());
 
     fColorProcInfoValid = false;
     fCoverageProcInfoValid = false;
diff --git a/src/gpu/GrPipelineBuilder.h b/src/gpu/GrPipelineBuilder.h
index 6a0e137..87c2bc0 100644
--- a/src/gpu/GrPipelineBuilder.h
+++ b/src/gpu/GrPipelineBuilder.h
@@ -210,7 +210,7 @@
      * than the color buffer. In is scenario, the higher sample rate is resolved during blending.
      */
     bool hasMixedSamples() const {
-        return this->isHWAntialias() && !fRenderTarget->isMultisampled();
+        return this->isHWAntialias() && !fRenderTarget->isUnifiedMultisampled();
     }
 
     /// @}
diff --git a/src/gpu/GrStencilAndCoverPathRenderer.cpp b/src/gpu/GrStencilAndCoverPathRenderer.cpp
index a082325..248730b 100644
--- a/src/gpu/GrStencilAndCoverPathRenderer.cpp
+++ b/src/gpu/GrStencilAndCoverPathRenderer.cpp
@@ -51,9 +51,17 @@
                                                 const SkPath& path,
                                                 const GrStrokeInfo& stroke,
                                                 bool antiAlias) const {
-    return !stroke.isHairlineStyle() &&
-        !antiAlias && // doesn't do per-path AA, relies on the target having MSAA
-        pipelineBuilder->getStencil().isDisabled();
+    if (stroke.isHairlineStyle()) {
+        return false;
+    }
+    if (!pipelineBuilder->getStencil().isDisabled()) {
+        return false;
+    }
+    if (antiAlias) {
+        return pipelineBuilder->getRenderTarget()->isStencilBufferMultisampled();
+    } else {
+        return true; // doesn't do per-path AA, relies on the target having MSAA
+    }
 }
 
 GrPathRenderer::StencilSupport
@@ -100,10 +108,14 @@
                                                const SkPath& path,
                                                const GrStrokeInfo& stroke,
                                                bool antiAlias) {
-    SkASSERT(!antiAlias);
     SkASSERT(!stroke.isHairlineStyle());
     SkASSERT(pipelineBuilder->getStencil().isDisabled());
 
+    if (antiAlias) {
+        SkASSERT(pipelineBuilder->getRenderTarget()->isStencilBufferMultisampled());
+        pipelineBuilder->enableState(GrPipelineBuilder::kHWAntialias_Flag);
+    }
+
     SkAutoTUnref<GrPath> p(get_gr_path(fResourceProvider, path, stroke));
 
     if (path.isInverseFillType()) {
diff --git a/src/gpu/GrStencilAndCoverTextContext.cpp b/src/gpu/GrStencilAndCoverTextContext.cpp
index 4d34cf4..763863fe 100644
--- a/src/gpu/GrStencilAndCoverTextContext.cpp
+++ b/src/gpu/GrStencilAndCoverTextContext.cpp
@@ -373,6 +373,8 @@
     fStateRestore.set(&fPipelineBuilder);
 
     fPipelineBuilder.setFromPaint(fPaint, fRenderTarget, fClip);
+    SkASSERT(fRenderTarget->isStencilBufferMultisampled() || !fPaint.isAntiAlias());
+    fPipelineBuilder.setState(GrPipelineBuilder::kHWAntialias_Flag, fPaint.isAntiAlias());
 
     GR_STATIC_CONST_SAME_STENCIL(kStencilPass,
                                  kZero_StencilOp,
diff --git a/src/gpu/GrTexture.cpp b/src/gpu/GrTexture.cpp
index 33b8687..e954c5e 100644
--- a/src/gpu/GrTexture.cpp
+++ b/src/gpu/GrTexture.cpp
@@ -51,7 +51,7 @@
     if (this->asRenderTarget()) {
         // This texture has a render target
         SkASSERT(0 != (fDesc.fFlags & kRenderTarget_GrSurfaceFlag));
-        SkASSERT(fDesc.fSampleCnt == this->asRenderTarget()->numSamples());
+        SkASSERT(fDesc.fSampleCnt == this->asRenderTarget()->numColorSamples());
     } else {
         SkASSERT(0 == (fDesc.fFlags & kRenderTarget_GrSurfaceFlag));
         SkASSERT(0 == fDesc.fSampleCnt);
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 6db5c1d..18b2643 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -333,7 +333,7 @@
                                                        : SkSurface::kNo_Budgeted;
 
     SkAutoTUnref<GrRenderTarget> newRT(CreateRenderTarget(
-        fRenderTarget->getContext(), budgeted, this->imageInfo(), fRenderTarget->numSamples()));
+        fRenderTarget->getContext(), budgeted, this->imageInfo(), fRenderTarget->desc().fSampleCnt));
 
     if (NULL == newRT) {
         return;
@@ -919,7 +919,7 @@
     // anti-aliased edges, we work around that for now by drawing directly
     // if the image size exceeds maximum texture size.
     int maxTextureSize = fContext->caps()->maxTextureSize();
-    bool directDraw = fRenderTarget->isMultisampled() ||
+    bool directDraw = fRenderTarget->isUnifiedMultisampled() ||
                       !paint.isAntiAlias() ||
                       bitmap.width() > maxTextureSize ||
                       bitmap.height() > maxTextureSize;
@@ -1690,7 +1690,7 @@
     desc.fFlags = kRenderTarget_GrSurfaceFlag;
     desc.fWidth = cinfo.fInfo.width();
     desc.fHeight = cinfo.fInfo.height();
-    desc.fSampleCnt = fRenderTarget->numSamples();
+    desc.fSampleCnt = fRenderTarget->desc().fSampleCnt;
 
     SkAutoTUnref<GrTexture> texture;
     // Skia's convention is to only clear a device if it is non-opaque.
@@ -1718,7 +1718,7 @@
 SkSurface* SkGpuDevice::newSurface(const SkImageInfo& info, const SkSurfaceProps& props) {
     // TODO: Change the signature of newSurface to take a budgeted parameter.
     static const SkSurface::Budgeted kBudgeted = SkSurface::kNo_Budgeted;
-    return SkSurface::NewRenderTarget(fContext, kBudgeted, info, fRenderTarget->numSamples(),
+    return SkSurface::NewRenderTarget(fContext, kBudgeted, info, fRenderTarget->desc().fSampleCnt,
                                       &props);
 }
 
@@ -1758,7 +1758,7 @@
                                       initialMatrix,
                                       clipBounds,
                                       &atlasedNeedRendering, &atlasedRecycled,
-                                      fRenderTarget->numSamples());
+                                      fRenderTarget->numColorSamples());
 
     GrLayerHoister::DrawLayersToAtlas(fContext, atlasedNeedRendering);
 
@@ -1770,7 +1770,7 @@
                                       initialMatrix,
                                       clipBounds,
                                       &needRendering, &recycled,
-                                      fRenderTarget->numSamples());
+                                      fRenderTarget->numColorSamples());
 
     GrLayerHoister::DrawLayers(fContext, needRendering);
 
diff --git a/src/gpu/effects/GrDashingEffect.cpp b/src/gpu/effects/GrDashingEffect.cpp
index ad6f70d..186cc41 100644
--- a/src/gpu/effects/GrDashingEffect.cpp
+++ b/src/gpu/effects/GrDashingEffect.cpp
@@ -734,7 +734,7 @@
                                    const SkMatrix& viewMatrix, const SkPoint pts[2],
                                    bool useAA, const GrStrokeInfo& strokeInfo) {
     SkAutoTUnref<GrBatch> batch(create_batch(color, viewMatrix, pts, useAA, strokeInfo,
-                                             pipelineBuilder->getRenderTarget()->isMultisampled()));
+                                    pipelineBuilder->getRenderTarget()->isUnifiedMultisampled()));
     if (!batch) {
         return false;
     }
diff --git a/src/gpu/gl/GrGLCaps.cpp b/src/gpu/gl/GrGLCaps.cpp
index 40405fd..e8c3b55 100644
--- a/src/gpu/gl/GrGLCaps.cpp
+++ b/src/gpu/gl/GrGLCaps.cpp
@@ -292,6 +292,11 @@
             ctxInfo.hasExtension("GL_NV_sample_mask_override_coverage") &&
             ctxInfo.hasExtension("GL_EXT_raster_multisample");
     }
+    // Workaround NVIDIA bug related to glInvalidateFramebuffer and mixed samples.
+    if (kNVIDIA_GrGLDriver == ctxInfo.driver() && fShaderCaps->mixedSamplesSupport()) {
+        fDiscardRenderTargetSupport = false;
+        fInvalidateFBType = kNone_InvalidateFBType;
+    }
 
     /**************************************************************************
      * GrCaps fields
@@ -789,6 +794,8 @@
             fMSFBOType = kES_EXT_MsToTexture_MSFBOType;
         } else if (ctxInfo.hasExtension("GL_IMG_multisampled_render_to_texture")) {
             fMSFBOType = kES_IMG_MsToTexture_MSFBOType;
+        } else if (fShaderCaps->mixedSamplesSupport() && fShaderCaps->pathRenderingSupport()) {
+            fMSFBOType = kMixedSamples_MSFBOType;
         } else if (ctxInfo.version() >= GR_GL_VER(3,0)) {
             fMSFBOType = GrGLCaps::kES_3_0_MSFBOType;
         } else if (ctxInfo.hasExtension("GL_CHROMIUM_framebuffer_multisample")) {
@@ -799,7 +806,9 @@
             fMSFBOType = kES_Apple_MSFBOType;
         }
     } else {
-        if ((ctxInfo.version() >= GR_GL_VER(3,0)) ||
+        if (fShaderCaps->mixedSamplesSupport() && fShaderCaps->pathRenderingSupport()) {
+            fMSFBOType = kMixedSamples_MSFBOType;
+        } else if ((ctxInfo.version() >= GR_GL_VER(3,0)) ||
             ctxInfo.hasExtension("GL_ARB_framebuffer_object")) {
             fMSFBOType = GrGLCaps::kDesktop_ARB_MSFBOType;
         } else if (ctxInfo.hasExtension("GL_EXT_framebuffer_multisample") &&
@@ -932,6 +941,7 @@
         "Apple",
         "IMG MS To Texture",
         "EXT MS To Texture",
+        "MixedSamples",
     };
     GR_STATIC_ASSERT(0 == kNone_MSFBOType);
     GR_STATIC_ASSERT(1 == kDesktop_ARB_MSFBOType);
@@ -940,6 +950,7 @@
     GR_STATIC_ASSERT(4 == kES_Apple_MSFBOType);
     GR_STATIC_ASSERT(5 == kES_IMG_MsToTexture_MSFBOType);
     GR_STATIC_ASSERT(6 == kES_EXT_MsToTexture_MSFBOType);
+    GR_STATIC_ASSERT(7 == kMixedSamples_MSFBOType);
     GR_STATIC_ASSERT(SK_ARRAY_COUNT(kMSFBOExtStr) == kLast_MSFBOType + 1);
 
     static const char* kInvalidateFBTypeStr[] = {
diff --git a/src/gpu/gl/GrGLCaps.h b/src/gpu/gl/GrGLCaps.h
index e924c60..b04bf34 100644
--- a/src/gpu/gl/GrGLCaps.h
+++ b/src/gpu/gl/GrGLCaps.h
@@ -67,8 +67,12 @@
          * GL_MAX_SAMPLES value.
          */
         kES_EXT_MsToTexture_MSFBOType,
+        /**
+         * GL_NV_framebuffer_mixed_samples.
+         */
+        kMixedSamples_MSFBOType,
 
-        kLast_MSFBOType = kES_EXT_MsToTexture_MSFBOType
+        kLast_MSFBOType = kMixedSamples_MSFBOType
     };
 
     enum InvalidateFBType {
@@ -141,7 +145,8 @@
     bool usesMSAARenderBuffers() const {
         return kNone_MSFBOType != fMSFBOType &&
                kES_IMG_MsToTexture_MSFBOType != fMSFBOType &&
-               kES_EXT_MsToTexture_MSFBOType != fMSFBOType;
+               kES_EXT_MsToTexture_MSFBOType != fMSFBOType &&
+               kMixedSamples_MSFBOType != fMSFBOType;
     }
 
     /**
@@ -153,8 +158,6 @@
                kES_EXT_MsToTexture_MSFBOType == fMSFBOType;
     }
 
-    bool fbMixedSamplesSupport() const { return fFBMixedSamplesSupport; }
-
     InvalidateFBType invalidateFBType() const { return fInvalidateFBType; }
 
     /// What type of buffer mapping is supported?
@@ -225,7 +228,9 @@
     bool ES2CompatibilitySupport() const { return fES2CompatibilitySupport; }
 
     /// Can we call glDisable(GL_MULTISAMPLE)?
-    bool multisampleDisableSupport() const { return fMultisampleDisableSupport; }
+    bool multisampleDisableSupport() const {
+        return fMultisampleDisableSupport;
+    }
 
     /// Use indices or vertices in CPU arrays rather than VBOs for dynamic content.
     bool useNonVBOVertexAndIndexDynamicData() const {
@@ -349,7 +354,6 @@
     bool fUseNonVBOVertexAndIndexDynamicData : 1;
     bool fIsCoreProfile : 1;
     bool fFullClearIsFree : 1;
-    bool fFBMixedSamplesSupport : 1;
 
     struct ReadPixelsSupportedFormat {
         GrGLenum fFormat;
diff --git a/src/gpu/gl/GrGLGpu.cpp b/src/gpu/gl/GrGLGpu.cpp
index 08e83ec..625299d 100644
--- a/src/gpu/gl/GrGLGpu.cpp
+++ b/src/gpu/gl/GrGLGpu.cpp
@@ -373,6 +373,14 @@
 
     if (resetBits & kMSAAEnable_GrGLBackendState) {
         fMSAAEnabled = kUnknown_TriState;
+
+        // In mixed samples mode coverage modulation allows the coverage to be converted to
+        // "opacity", which can then be blended into the color buffer to accomplish antialiasing.
+        // Enable coverage modulation suitable for premultiplied alpha colors.
+        // This state has no effect when not rendering to a mixed sampled target.
+        if (this->glCaps().shaderCaps()->mixedSamplesSupport()) {
+            GL_CALL(CoverageModulation(GR_GL_RGBA));
+        }
     }
 
     fHWActiveTextureUnitIdx = -1; // invalid
@@ -851,6 +859,7 @@
     switch (ctx.caps()->msFBOType()) {
         case GrGLCaps::kDesktop_ARB_MSFBOType:
         case GrGLCaps::kDesktop_EXT_MSFBOType:
+        case GrGLCaps::kMixedSamples_MSFBOType:
         case GrGLCaps::kES_3_0_MSFBOType:
             GL_ALLOC_CALL(ctx.interface(),
                             RenderbufferStorageMultisample(GR_GL_RENDERBUFFER,
@@ -888,6 +897,9 @@
     idDesc->fRTFBOID = 0;
     idDesc->fTexFBOID = 0;
     idDesc->fLifeCycle = lifeCycle;
+    idDesc->fSampleConfig = (GrGLCaps::kMixedSamples_MSFBOType == this->glCaps().msFBOType() &&
+                            desc.fSampleCnt > 0) ? GrRenderTarget::kStencil_SampleConfig :
+                                                   GrRenderTarget::kUnified_SampleConfig;
 
     GrGLenum status;
 
@@ -1170,7 +1182,7 @@
     SkASSERT(width >= rt->width());
     SkASSERT(height >= rt->height());
 
-    int samples = rt->numSamples();
+    int samples = rt->numStencilSamples();
     GrGLStencilAttachment::IDDesc sbDesc;
 
     int stencilFmtCnt = this->glCaps().stencilFormats().count();
@@ -2090,7 +2102,7 @@
 }
 
 void GrGLGpu::flushHWAAState(GrRenderTarget* rt, bool useHWAA) {
-    SkASSERT(!useHWAA || rt->isMultisampled());
+    SkASSERT(!useHWAA || rt->isStencilBufferMultisampled());
 
     if (this->glCaps().multisampleDisableSupport()) {
         if (useHWAA) {
diff --git a/src/gpu/gl/GrGLRenderTarget.cpp b/src/gpu/gl/GrGLRenderTarget.cpp
index c9e65d4..a89022e 100644
--- a/src/gpu/gl/GrGLRenderTarget.cpp
+++ b/src/gpu/gl/GrGLRenderTarget.cpp
@@ -15,7 +15,7 @@
 // Because this class is virtually derived from GrSurface we must explicitly call its constructor.
 GrGLRenderTarget::GrGLRenderTarget(GrGLGpu* gpu, const GrSurfaceDesc& desc, const IDDesc& idDesc)
     : GrSurface(gpu, idDesc.fLifeCycle, desc)
-    , INHERITED(gpu, idDesc.fLifeCycle, desc) {
+    , INHERITED(gpu, idDesc.fLifeCycle, desc, idDesc.fSampleConfig) {
     this->init(desc, idDesc);
     this->registerWithCache();
 }
@@ -23,7 +23,7 @@
 GrGLRenderTarget::GrGLRenderTarget(GrGLGpu* gpu, const GrSurfaceDesc& desc, const IDDesc& idDesc,
                                    Derived)
     : GrSurface(gpu, idDesc.fLifeCycle, desc)
-    , INHERITED(gpu, idDesc.fLifeCycle, desc) {
+    , INHERITED(gpu, idDesc.fLifeCycle, desc, idDesc.fSampleConfig) {
     this->init(desc, idDesc);
 }
 
diff --git a/src/gpu/gl/GrGLRenderTarget.h b/src/gpu/gl/GrGLRenderTarget.h
index 7c0c08c..32c7d4f 100644
--- a/src/gpu/gl/GrGLRenderTarget.h
+++ b/src/gpu/gl/GrGLRenderTarget.h
@@ -22,10 +22,11 @@
     enum { kUnresolvableFBOID = 0 };
 
     struct IDDesc {
-        GrGLuint                    fRTFBOID;
-        GrGLuint                    fTexFBOID;
-        GrGLuint                    fMSColorRenderbufferID;
-        GrGpuResource::LifeCycle    fLifeCycle;
+        GrGLuint                     fRTFBOID;
+        GrGLuint                     fTexFBOID;
+        GrGLuint                     fMSColorRenderbufferID;
+        GrGpuResource::LifeCycle     fLifeCycle;
+        GrRenderTarget::SampleConfig fSampleConfig;
     };
 
     GrGLRenderTarget(GrGLGpu*, const GrSurfaceDesc&, const IDDesc&);
@@ -43,7 +44,7 @@
 
     // override of GrRenderTarget
     ResolveType getResolveType() const override {
-        if (!this->isMultisampled() ||
+        if (!this->isUnifiedMultisampled() ||
             fRTFBOID == fTexFBOID) {
             // catches FBO 0 and non MSAA case
             return kAutoResolves_ResolveType;
diff --git a/src/image/SkSurface_Gpu.cpp b/src/image/SkSurface_Gpu.cpp
index f44bbfe..ff18a04 100644
--- a/src/image/SkSurface_Gpu.cpp
+++ b/src/image/SkSurface_Gpu.cpp
@@ -37,7 +37,7 @@
 
 SkSurface* SkSurface_Gpu::onNewSurface(const SkImageInfo& info) {
     GrRenderTarget* rt = fDevice->accessRenderTarget();
-    int sampleCount = rt->numSamples();
+    int sampleCount = rt->numColorSamples();
     // TODO: Make caller specify this (change virtual signature of onNewSurface).
     static const Budgeted kBudgeted = kNo_Budgeted;
     return SkSurface::NewRenderTarget(fDevice->context(), kBudgeted, info, sampleCount,
@@ -46,7 +46,7 @@
 
 SkImage* SkSurface_Gpu::onNewImageSnapshot(Budgeted budgeted) {
     const SkImageInfo info = fDevice->imageInfo();
-    const int sampleCount = fDevice->accessRenderTarget()->numSamples();
+    const int sampleCount = fDevice->accessRenderTarget()->numColorSamples();
     SkImage* image = NULL;
     GrTexture* tex = fDevice->accessRenderTarget()->asTexture();
     if (tex) {
diff --git a/tests/GLProgramsTest.cpp b/tests/GLProgramsTest.cpp
index d329124..1bf10ba 100644
--- a/tests/GLProgramsTest.cpp
+++ b/tests/GLProgramsTest.cpp
@@ -179,7 +179,7 @@
 
     // If we don't have an MSAA rendertarget then we have to disable useHWAA
     if ((state | GrPipelineBuilder::kHWAntialias_Flag) &&
-        !pipelineBuilder->getRenderTarget()->isMultisampled()) {
+        !pipelineBuilder->getRenderTarget()->isUnifiedMultisampled()) {
         state &= ~GrPipelineBuilder::kHWAntialias_Flag;
     }
     pipelineBuilder->enableState(state);
diff --git a/tests/ResourceCacheTest.cpp b/tests/ResourceCacheTest.cpp
index b9632ef..18ec6e7 100644
--- a/tests/ResourceCacheTest.cpp
+++ b/tests/ResourceCacheTest.cpp
@@ -156,7 +156,7 @@
         // samples didn't get rounded up to >= 8 or else they could share.).
         if (context->caps()->maxSampleCount() >= 8 &&
             smallMSAART0 && smallMSAART0->asRenderTarget() &&
-            smallMSAART0->asRenderTarget()->numSamples() < 8) {
+            smallMSAART0->asRenderTarget()->numColorSamples() < 8) {
             smallMSAADesc.fSampleCnt = 8;
             smallMSAART1.reset(cache->createTexture(smallMSAADesc, false));
             SkAutoTUnref<GrTexture> smallMSAART1(cache->createTexture(smallMSAADesc, false));
diff --git a/tests/SurfaceTest.cpp b/tests/SurfaceTest.cpp
index 65c0c3b..c38a3d7 100644
--- a/tests/SurfaceTest.cpp
+++ b/tests/SurfaceTest.cpp
@@ -121,7 +121,7 @@
     wrappedDesc.fWidth = dummySurface->width();
     wrappedDesc.fHeight = dummySurface->height();
     wrappedDesc.fOrigin = dummySurface->origin();
-    wrappedDesc.fSampleCnt = dummySurface->asRenderTarget()->numSamples();
+    wrappedDesc.fSampleCnt = dummySurface->asRenderTarget()->numColorSamples();
     wrappedDesc.fFlags = kRenderTarget_GrBackendTextureFlag;
     wrappedDesc.fTextureHandle = textureHandle;