Enable MSAA for Metal.

Adds a separate resolve texture to GrMtlRenderTarget, which can be used
to do a resolve for the main multisample color texture. The resolve is
handled by setting a special Store action for the RenderCommandEncoder.

Bug: skia:8243
Change-Id: I1ffd756c01a9b363116ffefee2c4c50ba9a3e637
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/225536
Reviewed-by: Greg Daniel <egdaniel@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
diff --git a/src/gpu/mtl/GrMtlCaps.h b/src/gpu/mtl/GrMtlCaps.h
index 0acd33a..d431022 100644
--- a/src/gpu/mtl/GrMtlCaps.h
+++ b/src/gpu/mtl/GrMtlCaps.h
@@ -59,6 +59,9 @@
                        int srcSampleCount, const SkIRect& srcRect, const SkIPoint& dstPoint,
                        bool areDstSrcSameObj) const;
 
+    bool canCopyAsResolve(GrSurface* dst, int dstSampleCount, GrSurface* src, int srcSampleCount,
+                          const SkIRect& srcRect, const SkIPoint& dstPoint) const;
+
     bool initDescForDstCopy(const GrRenderTargetProxy* src, GrSurfaceDesc* desc,
                             bool* rectsMustMatch, bool* disallowSubrect) const override {
         return false;
@@ -104,9 +107,8 @@
             kMSAA_Flag        = 0x4,
             kResolve_Flag     = 0x8,
         };
-        // TODO: Put kMSAA_Flag back when MSAA is implemented
         static const uint16_t kAllFlags = kTextureable_Flag | kRenderable_Flag |
-                                          /*kMSAA_Flag |*/ kResolve_Flag;
+                                          kMSAA_Flag | kResolve_Flag;
 
         uint16_t fFlags;
     };
diff --git a/src/gpu/mtl/GrMtlCaps.mm b/src/gpu/mtl/GrMtlCaps.mm
index 5edc5b7..e8910c7 100644
--- a/src/gpu/mtl/GrMtlCaps.mm
+++ b/src/gpu/mtl/GrMtlCaps.mm
@@ -37,7 +37,6 @@
     // doesn't support it.
     fFenceSyncSupport = false;           // Fences are not implemented yet
     fSemaphoreSupport = false;           // Semaphores are not implemented yet
-    fMultisampleDisableSupport = true;   // MSAA and resolving not implemented yet
     fCrossContextTextureSupport = false; // GrMtlGpu::prepareTextureForCrossContextUsage() not impl
 }
 
@@ -137,6 +136,30 @@
     return true;
 }
 
+bool GrMtlCaps::canCopyAsResolve(GrSurface* dst, int dstSampleCount,
+                                 GrSurface* src, int srcSampleCount,
+                                 const SkIRect& srcRect, const SkIPoint& dstPoint) const {
+    if (dst == src) {
+        return false;
+    }
+    if (dst->backendFormat() != src->backendFormat()) {
+        return false;
+    }
+    if (dstSampleCount > 1 || srcSampleCount == 1 || !src->asRenderTarget()) {
+        return false;
+    }
+
+    // TODO: Support copying subrectangles
+    if (dstPoint != SkIPoint::Make(0, 0)) {
+        return false;
+    }
+    if (srcRect != SkIRect::MakeXYWH(0, 0, src->width(), src->height())) {
+        return false;
+    }
+
+    return true;
+}
+
 bool GrMtlCaps::onCanCopySurface(const GrSurfaceProxy* dst, const GrSurfaceProxy* src,
                                  const SkIRect& srcRect, const SkIPoint& dstPoint) const {
     int dstSampleCnt = 0;
diff --git a/src/gpu/mtl/GrMtlCommandBuffer.mm b/src/gpu/mtl/GrMtlCommandBuffer.mm
index 7b4cd54..c6b3480 100644
--- a/src/gpu/mtl/GrMtlCommandBuffer.mm
+++ b/src/gpu/mtl/GrMtlCommandBuffer.mm
@@ -59,8 +59,9 @@
                              first.storeAction == MTLStoreActionDontCare;
     bool loadActionsValid = second.loadAction == MTLLoadActionLoad ||
                             second.loadAction == MTLLoadActionDontCare;
-    bool secondDoesntSampleFirst = !pipelineState ||
-                                   pipelineState->doesntSampleAttachment(first);
+    bool secondDoesntSampleFirst = (!pipelineState ||
+                                    pipelineState->doesntSampleAttachment(first)) &&
+                                   second.storeAction != MTLStoreActionMultisampleResolve;
 
     return renderTargetsMatch &&
            (nil == first.texture ||
@@ -81,7 +82,9 @@
 
     this->endAllEncoding();
     fActiveRenderCommandEncoder = [fCmdBuffer renderCommandEncoderWithDescriptor:descriptor];
-    gpuCommandBuffer->initRenderState(fActiveRenderCommandEncoder);
+    if (gpuCommandBuffer) {
+        gpuCommandBuffer->initRenderState(fActiveRenderCommandEncoder);
+    }
     fPreviousRenderPassDescriptor = descriptor;
 
     return fActiveRenderCommandEncoder;
diff --git a/src/gpu/mtl/GrMtlGpu.h b/src/gpu/mtl/GrMtlGpu.h
index cd26919..209f73c 100644
--- a/src/gpu/mtl/GrMtlGpu.h
+++ b/src/gpu/mtl/GrMtlGpu.h
@@ -72,7 +72,9 @@
     void testingOnly_flushGpuAndSync() override;
 #endif
 
-    bool copySurfaceAsBlit(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
+    void copySurfaceAsResolve(GrSurface* dst, GrSurface* src);
+
+    void copySurfaceAsBlit(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
                            const SkIPoint& dstPoint);
 
     bool onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
@@ -114,6 +116,10 @@
         this->didWriteToSurface(surface, origin, bounds);
     }
 
+    void resolveRenderTargetNoFlush(GrRenderTarget* target) {
+        this->internalResolveRenderTarget(target, false);
+    }
+
 private:
     GrMtlGpu(GrContext* context, const GrContextOptions& options,
              id<MTLDevice> device, id<MTLCommandQueue> queue, MTLFeatureSet featureSet);
@@ -171,7 +177,15 @@
 
     bool onRegenerateMipMapLevels(GrTexture*) override;
 
-    void onResolveRenderTarget(GrRenderTarget* target) override { return; }
+    void onResolveRenderTarget(GrRenderTarget* target) override {
+        // This resolve is called when we are preparing an msaa surface for external I/O. It is
+        // called after flushing, so we need to make sure we submit the command buffer after doing
+        // the resolve so that the resolve actually happens.
+        this->internalResolveRenderTarget(target, true);
+    }
+
+    void internalResolveRenderTarget(GrRenderTarget* target, bool requiresSubmit);
+    void resolveTexture(id<MTLTexture> colorTexture, id<MTLTexture> resolveTexture);
 
     void onFinishFlush(GrSurfaceProxy*[], int n, SkSurface::BackendSurfaceAccess access,
                        const GrFlushInfo& info, const GrPrepareForExternalIORequests&) override {
diff --git a/src/gpu/mtl/GrMtlGpu.mm b/src/gpu/mtl/GrMtlGpu.mm
index 8761b95..1e869ce 100644
--- a/src/gpu/mtl/GrMtlGpu.mm
+++ b/src/gpu/mtl/GrMtlGpu.mm
@@ -436,7 +436,6 @@
     texDesc.mipmapLevelCount = mipLevels;
     texDesc.sampleCount = 1;
     texDesc.arrayLength = 1;
-    texDesc.cpuCacheMode = MTLCPUCacheModeWriteCombined;
     // Make all textures have private gpu only access. We can use transfer buffers or textures
     // to copy to them.
     texDesc.storageMode = MTLStorageModePrivate;
@@ -455,10 +454,10 @@
     }
 
     if (renderTarget) {
-        tex = GrMtlTextureRenderTarget::CreateNewTextureRenderTarget(this, budgeted,
-                                                                     desc, texDesc, mipMapsStatus);
+        tex = GrMtlTextureRenderTarget::MakeNewTextureRenderTarget(this, budgeted,
+                                                                   desc, texDesc, mipMapsStatus);
     } else {
-        tex = GrMtlTexture::CreateNewTexture(this, budgeted, desc, texDesc, mipMapsStatus);
+        tex = GrMtlTexture::MakeNewTexture(this, budgeted, desc, texDesc, mipMapsStatus);
     }
 
     if (!tex) {
@@ -863,7 +862,23 @@
     return 0;
 }
 
-bool GrMtlGpu::copySurfaceAsBlit(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
+void GrMtlGpu::copySurfaceAsResolve(GrSurface* dst, GrSurface* src) {
+    // TODO: Add support for subrectangles
+    GrMtlRenderTarget* srcRT = static_cast<GrMtlRenderTarget*>(src->asRenderTarget());
+    GrRenderTarget* dstRT = dst->asRenderTarget();
+    id<MTLTexture> dstTexture;
+    if (dstRT) {
+        GrMtlRenderTarget* mtlRT = static_cast<GrMtlRenderTarget*>(dstRT);
+        dstTexture = mtlRT->mtlColorTexture();
+    } else {
+        SkASSERT(dst->asTexture());
+        dstTexture = static_cast<GrMtlTexture*>(dst->asTexture())->mtlTexture();
+    }
+
+    this->resolveTexture(dstTexture, srcRT->mtlColorTexture());
+}
+
+void GrMtlGpu::copySurfaceAsBlit(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
                                  const SkIPoint& dstPoint) {
 #ifdef SK_DEBUG
     int dstSampleCnt = get_surface_sample_cnt(dst);
@@ -871,8 +886,8 @@
     SkASSERT(this->mtlCaps().canCopyAsBlit(dst->config(), dstSampleCnt, src->config(), srcSampleCnt,
                                            srcRect, dstPoint, dst == src));
 #endif
-    id<MTLTexture> dstTex = GrGetMTLTextureFromSurface(dst, false);
-    id<MTLTexture> srcTex = GrGetMTLTextureFromSurface(src, false);
+    id<MTLTexture> dstTex = GrGetMTLTextureFromSurface(dst);
+    id<MTLTexture> srcTex = GrGetMTLTextureFromSurface(src);
 
     id<MTLBlitCommandEncoder> blitCmdEncoder = this->commandBuffer()->getBlitCommandEncoder();
     [blitCmdEncoder copyFromTexture: srcTex
@@ -884,12 +899,11 @@
                    destinationSlice: 0
                    destinationLevel: 0
                   destinationOrigin: MTLOriginMake(dstPoint.fX, dstPoint.fY, 0)];
-
-    return true;
 }
 
 bool GrMtlGpu::onCopySurface(GrSurface* dst, GrSurface* src, const SkIRect& srcRect,
                              const SkIPoint& dstPoint, bool canDiscardOutsideDstRect) {
+    SkASSERT(!src->isProtected() && !dst->isProtected());
 
     GrPixelConfig dstConfig = dst->config();
     GrPixelConfig srcConfig = src->config();
@@ -897,15 +911,14 @@
     int dstSampleCnt = get_surface_sample_cnt(dst);
     int srcSampleCnt = get_surface_sample_cnt(src);
 
-    if (dstSampleCnt > 1 || srcSampleCnt > 1) {
-        SkASSERT(false); // Currently dont support MSAA. TODO: add copySurfaceAsResolve().
-        return false;
-    }
-
     bool success = false;
-    if (this->mtlCaps().canCopyAsBlit(dstConfig, dstSampleCnt, srcConfig, srcSampleCnt, srcRect,
-                                      dstPoint, dst == src)) {
-        success = this->copySurfaceAsBlit(dst, src, srcRect, dstPoint);
+    if (this->mtlCaps().canCopyAsResolve(dst, dstSampleCnt, src, srcSampleCnt, srcRect, dstPoint)) {
+        this->copySurfaceAsResolve(dst, src);
+        success = true;
+    } else if (this->mtlCaps().canCopyAsBlit(dstConfig, dstSampleCnt, srcConfig, srcSampleCnt,
+                                             srcRect, dstPoint, dst == src)) {
+        this->copySurfaceAsBlit(dst, src, srcRect, dstPoint);
+        success = true;
     }
     if (success) {
         SkIRect dstRect = SkIRect::MakeXYWH(dstPoint.x(), dstPoint.y(),
@@ -949,9 +962,32 @@
 
     int bpp = GrColorTypeBytesPerPixel(dstColorType);
     size_t transBufferRowBytes = bpp * width;
-    bool doResolve = get_surface_sample_cnt(surface) > 1;
-    id<MTLTexture> mtlTexture = GrGetMTLTextureFromSurface(surface, doResolve);
-    if (!mtlTexture || [mtlTexture isFramebufferOnly]) {
+
+    id<MTLTexture> mtlTexture;
+    GrMtlRenderTarget* rt = static_cast<GrMtlRenderTarget*>(surface->asRenderTarget());
+    if (rt) {
+        // resolve the render target if necessary
+        switch (rt->getResolveType()) {
+            case GrMtlRenderTarget::kCantResolve_ResolveType:
+                return false;
+            case GrMtlRenderTarget::kAutoResolves_ResolveType:
+                mtlTexture = rt->mtlColorTexture();
+                break;
+            case GrMtlRenderTarget::kCanResolve_ResolveType:
+                this->resolveRenderTargetNoFlush(rt);
+                mtlTexture = rt->mtlResolveTexture();
+                break;
+            default:
+                SK_ABORT("Unknown resolve type");
+        }
+    } else {
+        GrMtlTexture* texture = static_cast<GrMtlTexture*>(surface->asTexture());
+        if (texture) {
+            mtlTexture = texture->mtlTexture();
+        }
+    }
+
+    if (!mtlTexture) {
         return false;
     }
 
@@ -988,6 +1024,33 @@
     SkRectMemcpy(buffer, rowBytes, mappedMemory, transBufferRowBytes, transBufferRowBytes, height);
 
     return true;
-
 }
 
+void GrMtlGpu::internalResolveRenderTarget(GrRenderTarget* target, bool requiresSubmit) {
+    if (target->needsResolve()) {
+        this->resolveTexture(static_cast<GrMtlRenderTarget*>(target)->mtlResolveTexture(),
+                             static_cast<GrMtlRenderTarget*>(target)->mtlColorTexture());
+        target->flagAsResolved();
+
+        if (requiresSubmit) {
+            this->submitCommandBuffer(kSkip_SyncQueue);
+        }
+    }
+}
+
+void GrMtlGpu::resolveTexture(id<MTLTexture> resolveTexture, id<MTLTexture> colorTexture) {
+    auto renderPassDesc = [MTLRenderPassDescriptor renderPassDescriptor];
+    renderPassDesc.colorAttachments[0].texture = colorTexture;
+    renderPassDesc.colorAttachments[0].slice = 0;
+    renderPassDesc.colorAttachments[0].level = 0;
+    renderPassDesc.colorAttachments[0].resolveTexture = resolveTexture;
+    renderPassDesc.colorAttachments[0].slice = 0;
+    renderPassDesc.colorAttachments[0].level = 0;
+    renderPassDesc.colorAttachments[0].loadAction = MTLLoadActionLoad;
+    renderPassDesc.colorAttachments[0].storeAction = MTLStoreActionMultisampleResolve;
+
+    id<MTLRenderCommandEncoder> cmdEncoder =
+            this->commandBuffer()->getRenderCommandEncoder(renderPassDesc, nullptr, nullptr);
+    SkASSERT(nil != cmdEncoder);
+    cmdEncoder.label = @"resolveTexture";
+}
diff --git a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
index 5acd422..e03efec 100644
--- a/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
+++ b/src/gpu/mtl/GrMtlGpuCommandBuffer.mm
@@ -15,6 +15,7 @@
 #include "src/gpu/mtl/GrMtlPipelineState.h"
 #include "src/gpu/mtl/GrMtlPipelineStateBuilder.h"
 #include "src/gpu/mtl/GrMtlRenderTarget.h"
+#include "src/gpu/mtl/GrMtlTexture.h"
 
 #if !__has_feature(objc_arc)
 #error This file must be compiled with Arc. Use -fobjc-arc flag
@@ -113,6 +114,13 @@
     }
 
     auto prepareSampledImage = [&](GrTexture* texture, GrSamplerState::Filter filter) {
+        GrMtlTexture* mtlTexture = static_cast<GrMtlTexture*>(texture);
+        // We may need to resolve the texture first if it is also a render target
+        GrMtlRenderTarget* texRT = static_cast<GrMtlRenderTarget*>(mtlTexture->asRenderTarget());
+        if (texRT) {
+            fGpu->resolveRenderTargetNoFlush(texRT);
+        }
+
         // Check if we need to regenerate any mip maps
         if (GrSamplerState::Filter::kMipMap == filter &&
             (texture->width() != 1 || texture->height() != 1)) {
@@ -238,6 +246,7 @@
 }
 
 void GrMtlGpuRTCommandBuffer::initRenderState(id<MTLRenderCommandEncoder> encoder) {
+    [encoder pushDebugGroup:@"initRenderState"];
     [encoder setFrontFacingWinding:MTLWindingCounterClockwise];
     // Strictly speaking we shouldn't have to set this, as the default viewport is the size of
     // the drawable used to generate the renderCommandEncoder -- but just in case.
@@ -246,6 +255,7 @@
                              0.0, 1.0 };
     [encoder setViewport:viewport];
     this->resetBufferBindings();
+    [encoder popDebugGroup];
 }
 
 void GrMtlGpuRTCommandBuffer::setupRenderPass(
@@ -273,7 +283,7 @@
 
     auto renderPassDesc = [MTLRenderPassDescriptor renderPassDescriptor];
     renderPassDesc.colorAttachments[0].texture =
-            static_cast<GrMtlRenderTarget*>(fRenderTarget)->mtlRenderTexture();
+            static_cast<GrMtlRenderTarget*>(fRenderTarget)->mtlColorTexture();
     renderPassDesc.colorAttachments[0].slice = 0;
     renderPassDesc.colorAttachments[0].level = 0;
     const SkPMColor4f& clearColor = colorInfo.fClearColor;
diff --git a/src/gpu/mtl/GrMtlPipelineState.mm b/src/gpu/mtl/GrMtlPipelineState.mm
index 9e3ac53..f7a9910 100644
--- a/src/gpu/mtl/GrMtlPipelineState.mm
+++ b/src/gpu/mtl/GrMtlPipelineState.mm
@@ -116,9 +116,11 @@
 void GrMtlPipelineState::setDrawState(id<MTLRenderCommandEncoder> renderCmdEncoder,
                                       const GrSwizzle& outputSwizzle,
                                       const GrXferProcessor& xferProcessor) {
+    [renderCmdEncoder pushDebugGroup:@"setDrawState"];
     this->bind(renderCmdEncoder);
     this->setBlendConstants(renderCmdEncoder, outputSwizzle, xferProcessor);
     this->setDepthStencilState(renderCmdEncoder);
+    [renderCmdEncoder popDebugGroup];
 }
 
 void GrMtlPipelineState::bind(id<MTLRenderCommandEncoder> renderCmdEncoder) {
diff --git a/src/gpu/mtl/GrMtlPipelineStateBuilder.mm b/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
index 6f636a9..f580161 100644
--- a/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
+++ b/src/gpu/mtl/GrMtlPipelineStateBuilder.mm
@@ -370,6 +370,7 @@
     pipelineDescriptor.fragmentFunction = fragmentFunction;
     pipelineDescriptor.vertexDescriptor = create_vertex_descriptor(primProc);
     pipelineDescriptor.colorAttachments[0] = create_color_attachment(this->config(), pipeline);
+    pipelineDescriptor.sampleCount = renderTarget->numSamples();
     bool hasStencilAttachment = SkToBool(renderTarget->renderTargetPriv().getStencilAttachment());
     GrMtlCaps* mtlCaps = (GrMtlCaps*)this->caps();
     pipelineDescriptor.stencilAttachmentPixelFormat =
diff --git a/src/gpu/mtl/GrMtlRenderTarget.h b/src/gpu/mtl/GrMtlRenderTarget.h
index 7b2c31b..5b1377c 100644
--- a/src/gpu/mtl/GrMtlRenderTarget.h
+++ b/src/gpu/mtl/GrMtlRenderTarget.h
@@ -26,20 +26,18 @@
 
     // override of GrRenderTarget
     ResolveType getResolveType() const override {
-        return kCantResolve_ResolveType;
-#if 0 // TODO figure this once we support msaa
         if (this->numSamples() > 1) {
             return kCanResolve_ResolveType;
         }
         return kAutoResolves_ResolveType;
-#endif
     }
 
     bool canAttemptStencilAttachment() const override {
         return true;
     }
 
-    id<MTLTexture> mtlRenderTexture() const { return fRenderTexture; }
+    id<MTLTexture> mtlColorTexture() const { return fColorTexture; }
+    id<MTLTexture> mtlResolveTexture() const { return fResolveTexture; }
 
     GrBackendRenderTarget getBackendRenderTarget() const override;
 
@@ -48,7 +46,12 @@
 protected:
     GrMtlRenderTarget(GrMtlGpu* gpu,
                       const GrSurfaceDesc& desc,
-                      id<MTLTexture> renderTexture);
+                      id<MTLTexture> colorTexture,
+                      id<MTLTexture> resolveTexture);
+
+    GrMtlRenderTarget(GrMtlGpu* gpu,
+                      const GrSurfaceDesc& desc,
+                      id<MTLTexture> colorTexture);
 
     GrMtlGpu* getMtlGpu() const;
 
@@ -68,7 +71,7 @@
                                       numColorSamples, GrMipMapped::kNo);
     }
 
-    id<MTLTexture> fRenderTexture;
+    id<MTLTexture> fColorTexture;
     id<MTLTexture> fResolveTexture;
 
 private:
@@ -76,7 +79,12 @@
     enum Wrapped { kWrapped };
     GrMtlRenderTarget(GrMtlGpu* gpu,
                       const GrSurfaceDesc& desc,
-                      id<MTLTexture> renderTexture,
+                      id<MTLTexture> colorTexture,
+                      id<MTLTexture> resolveTexture,
+                      Wrapped);
+    GrMtlRenderTarget(GrMtlGpu* gpu,
+                      const GrSurfaceDesc& desc,
+                      id<MTLTexture> colorTexture,
                       Wrapped);
 
     bool completeStencilAttachment() override;
diff --git a/src/gpu/mtl/GrMtlRenderTarget.mm b/src/gpu/mtl/GrMtlRenderTarget.mm
index d549b6c..7d4d989 100644
--- a/src/gpu/mtl/GrMtlRenderTarget.mm
+++ b/src/gpu/mtl/GrMtlRenderTarget.mm
@@ -17,11 +17,24 @@
 // Called for wrapped non-texture render targets.
 GrMtlRenderTarget::GrMtlRenderTarget(GrMtlGpu* gpu,
                                      const GrSurfaceDesc& desc,
-                                     id<MTLTexture> renderTexture,
+                                     id<MTLTexture> colorTexture,
+                                     id<MTLTexture> resolveTexture,
                                      Wrapped)
         : GrSurface(gpu, desc)
         , GrRenderTarget(gpu, desc)
-        , fRenderTexture(renderTexture)
+        , fColorTexture(colorTexture)
+        , fResolveTexture(resolveTexture) {
+    SkASSERT(desc.fSampleCnt > 1);
+    this->registerWithCacheWrapped(GrWrapCacheable::kNo);
+}
+
+GrMtlRenderTarget::GrMtlRenderTarget(GrMtlGpu* gpu,
+                                     const GrSurfaceDesc& desc,
+                                     id<MTLTexture> colorTexture,
+                                     Wrapped)
+        : GrSurface(gpu, desc)
+        , GrRenderTarget(gpu, desc)
+        , fColorTexture(colorTexture)
         , fResolveTexture(nil) {
     SkASSERT(1 == desc.fSampleCnt);
     this->registerWithCacheWrapped(GrWrapCacheable::kNo);
@@ -30,36 +43,76 @@
 // Called by subclass constructors.
 GrMtlRenderTarget::GrMtlRenderTarget(GrMtlGpu* gpu,
                                      const GrSurfaceDesc& desc,
-                                     id<MTLTexture> renderTexture)
+                                     id<MTLTexture> colorTexture,
+                                     id<MTLTexture> resolveTexture)
         : GrSurface(gpu, desc)
         , GrRenderTarget(gpu, desc)
-        , fRenderTexture(renderTexture)
+        , fColorTexture(colorTexture)
+        , fResolveTexture(resolveTexture) {
+    SkASSERT(desc.fSampleCnt > 1);
+}
+
+GrMtlRenderTarget::GrMtlRenderTarget(GrMtlGpu* gpu,
+                                     const GrSurfaceDesc& desc,
+                                     id<MTLTexture> colorTexture)
+        : GrSurface(gpu, desc)
+        , GrRenderTarget(gpu, desc)
+        , fColorTexture(colorTexture)
         , fResolveTexture(nil) {
     SkASSERT(1 == desc.fSampleCnt);
 }
 
 sk_sp<GrMtlRenderTarget>
 GrMtlRenderTarget::MakeWrappedRenderTarget(GrMtlGpu* gpu, const GrSurfaceDesc& desc,
-                                           id<MTLTexture> renderTexture) {
-    SkASSERT(nil != renderTexture);
-    SkASSERT(1 == renderTexture.mipmapLevelCount);
-    SkASSERT(MTLTextureUsageRenderTarget & renderTexture.usage);
-    return sk_sp<GrMtlRenderTarget>(new GrMtlRenderTarget(gpu, desc, renderTexture, kWrapped));
+                                           id<MTLTexture> texture) {
+    SkASSERT(nil != texture);
+    SkASSERT(1 == texture.mipmapLevelCount);
+    SkASSERT(MTLTextureUsageRenderTarget & texture.usage);
+
+    GrMtlRenderTarget* mtlRT;
+    if (desc.fSampleCnt > 1) {
+        MTLPixelFormat format;
+        if (!GrPixelConfigToMTLFormat(desc.fConfig, &format)) {
+            return nullptr;
+        }
+        MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
+        texDesc.textureType = MTLTextureType2DMultisample;
+        texDesc.pixelFormat = format;
+        texDesc.width = desc.fWidth;
+        texDesc.height = desc.fHeight;
+        texDesc.depth = 1;
+        texDesc.mipmapLevelCount = 1;
+        texDesc.sampleCount = desc.fSampleCnt;
+        texDesc.arrayLength = 1;
+        texDesc.storageMode = MTLStorageModePrivate;
+        texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
+
+        id<MTLTexture> colorTexture = [gpu->device() newTextureWithDescriptor:texDesc];
+        if (!colorTexture) {
+            return nullptr;
+        }
+        SkASSERT((MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget) & colorTexture.usage);
+        mtlRT = new GrMtlRenderTarget(gpu, desc, colorTexture, texture, kWrapped);
+    } else {
+        mtlRT = new GrMtlRenderTarget(gpu, desc, texture, kWrapped);
+    }
+
+    return sk_sp<GrMtlRenderTarget>(mtlRT);
 }
 
 GrMtlRenderTarget::~GrMtlRenderTarget() {
-    SkASSERT(nil == fRenderTexture);
+    SkASSERT(nil == fColorTexture);
     SkASSERT(nil == fResolveTexture);
 }
 
 GrBackendRenderTarget GrMtlRenderTarget::getBackendRenderTarget() const {
     GrMtlTextureInfo info;
-    info.fTexture.reset(GrRetainPtrFromId(fRenderTexture));
-    return GrBackendRenderTarget(this->width(), this->height(), fRenderTexture.sampleCount, info);
+    info.fTexture.reset(GrRetainPtrFromId(fColorTexture));
+    return GrBackendRenderTarget(this->width(), this->height(), fColorTexture.sampleCount, info);
 }
 
 GrBackendFormat GrMtlRenderTarget::backendFormat() const {
-    return GrBackendFormat::MakeMtl(fRenderTexture.pixelFormat);
+    return GrBackendFormat::MakeMtl(fColorTexture.pixelFormat);
 }
 
 GrMtlGpu* GrMtlRenderTarget::getMtlGpu() const {
@@ -68,13 +121,13 @@
 }
 
 void GrMtlRenderTarget::onAbandon() {
-    fRenderTexture = nil;
+    fColorTexture = nil;
     fResolveTexture = nil;
     INHERITED::onAbandon();
 }
 
 void GrMtlRenderTarget::onRelease() {
-    fRenderTexture = nil;
+    fColorTexture = nil;
     fResolveTexture = nil;
     INHERITED::onRelease();
 }
diff --git a/src/gpu/mtl/GrMtlStencilAttachment.mm b/src/gpu/mtl/GrMtlStencilAttachment.mm
index fdafdb4..27be2d8 100644
--- a/src/gpu/mtl/GrMtlStencilAttachment.mm
+++ b/src/gpu/mtl/GrMtlStencilAttachment.mm
@@ -34,6 +34,10 @@
                                                        mipmapped:NO];
     desc.resourceOptions = MTLResourceStorageModePrivate;
     desc.usage = MTLTextureUsageRenderTarget;
+    desc.sampleCount = sampleCnt;
+    if (sampleCnt > 1) {
+        desc.textureType = MTLTextureType2DMultisample;
+    }
     return new GrMtlStencilAttachment(gpu, format, [gpu->device() newTextureWithDescriptor:desc]);
 }
 
diff --git a/src/gpu/mtl/GrMtlTexture.h b/src/gpu/mtl/GrMtlTexture.h
index 4eb241d..c81aef0 100644
--- a/src/gpu/mtl/GrMtlTexture.h
+++ b/src/gpu/mtl/GrMtlTexture.h
@@ -16,10 +16,10 @@
 
 class GrMtlTexture : public GrTexture {
 public:
-    static sk_sp<GrMtlTexture> CreateNewTexture(GrMtlGpu*, SkBudgeted budgeted,
-                                                const GrSurfaceDesc&,
-                                                MTLTextureDescriptor*,
-                                                GrMipMapsStatus);
+    static sk_sp<GrMtlTexture> MakeNewTexture(GrMtlGpu*, SkBudgeted budgeted,
+                                              const GrSurfaceDesc&,
+                                              MTLTextureDescriptor*,
+                                              GrMipMapsStatus);
 
     static sk_sp<GrMtlTexture> MakeWrappedTexture(GrMtlGpu*, const GrSurfaceDesc&, id<MTLTexture>,
                                                   GrWrapCacheable, GrIOType);
diff --git a/src/gpu/mtl/GrMtlTexture.mm b/src/gpu/mtl/GrMtlTexture.mm
index 9c8fd55..36cc835 100644
--- a/src/gpu/mtl/GrMtlTexture.mm
+++ b/src/gpu/mtl/GrMtlTexture.mm
@@ -54,16 +54,14 @@
     SkASSERT((GrMipMapsStatus::kNotAllocated == mipMapsStatus) == (1 == texture.mipmapLevelCount));
 }
 
-sk_sp<GrMtlTexture> GrMtlTexture::CreateNewTexture(GrMtlGpu* gpu, SkBudgeted budgeted,
+sk_sp<GrMtlTexture> GrMtlTexture::MakeNewTexture(GrMtlGpu* gpu, SkBudgeted budgeted,
                                                    const GrSurfaceDesc& desc,
                                                    MTLTextureDescriptor* texDesc,
                                                    GrMipMapsStatus mipMapsStatus) {
-    if (desc.fSampleCnt > 1) {
-        SkASSERT(false); // Currently we don't support msaa
+    id<MTLTexture> texture = [gpu->device() newTextureWithDescriptor:texDesc];
+    if (!texture) {
         return nullptr;
     }
-    id<MTLTexture> texture = [gpu->device() newTextureWithDescriptor:texDesc];
-    SkASSERT(nil != texture);
     SkASSERT(MTLTextureUsageShaderRead & texture.usage);
     return sk_sp<GrMtlTexture>(new GrMtlTexture(gpu, budgeted, desc, texture, mipMapsStatus));
 }
@@ -73,10 +71,6 @@
                                                      id<MTLTexture> texture,
                                                      GrWrapCacheable cacheable,
                                                      GrIOType ioType) {
-    if (desc.fSampleCnt > 1) {
-        SkASSERT(false); // Currently we don't support msaa
-        return nullptr;
-    }
     SkASSERT(nil != texture);
     SkASSERT(MTLTextureUsageShaderRead & texture.usage);
     GrMipMapsStatus mipMapsStatus = texture.mipmapLevelCount > 1 ? GrMipMapsStatus::kValid
diff --git a/src/gpu/mtl/GrMtlTextureRenderTarget.h b/src/gpu/mtl/GrMtlTextureRenderTarget.h
index 68f716f..da73135 100644
--- a/src/gpu/mtl/GrMtlTextureRenderTarget.h
+++ b/src/gpu/mtl/GrMtlTextureRenderTarget.h
@@ -13,11 +13,11 @@
 
 class GrMtlTextureRenderTarget: public GrMtlTexture, public GrMtlRenderTarget {
 public:
-    static sk_sp<GrMtlTextureRenderTarget> CreateNewTextureRenderTarget(GrMtlGpu*,
-                                                                        SkBudgeted,
-                                                                        const GrSurfaceDesc&,
-                                                                        MTLTextureDescriptor*,
-                                                                        GrMipMapsStatus);
+    static sk_sp<GrMtlTextureRenderTarget> MakeNewTextureRenderTarget(GrMtlGpu*,
+                                                                      SkBudgeted,
+                                                                      const GrSurfaceDesc&,
+                                                                      MTLTextureDescriptor*,
+                                                                      GrMipMapsStatus);
 
     static sk_sp<GrMtlTextureRenderTarget> MakeWrappedTextureRenderTarget(GrMtlGpu*,
                                                                           const GrSurfaceDesc&,
@@ -42,25 +42,26 @@
     GrMtlTextureRenderTarget(GrMtlGpu* gpu,
                              SkBudgeted budgeted,
                              const GrSurfaceDesc& desc,
-                             id<MTLTexture> renderTexture,
+                             id<MTLTexture> colorTexture,
                              id<MTLTexture> resolveTexture,
                              GrMipMapsStatus);
 
     GrMtlTextureRenderTarget(GrMtlGpu* gpu,
                              SkBudgeted budgeted,
                              const GrSurfaceDesc& desc,
-                             id<MTLTexture> renderTexture,
+                             id<MTLTexture> colorTexture,
                              GrMipMapsStatus);
 
     GrMtlTextureRenderTarget(GrMtlGpu* gpu,
                              const GrSurfaceDesc& desc,
-                             id<MTLTexture> renderTexture,
+                             id<MTLTexture> colorTexture,
                              id<MTLTexture> resolveTexture,
-                             GrMipMapsStatus);
+                             GrMipMapsStatus,
+                             GrWrapCacheable cacheable);
 
     GrMtlTextureRenderTarget(GrMtlGpu* gpu,
                              const GrSurfaceDesc& desc,
-                             id<MTLTexture> renderTexture,
+                             id<MTLTexture> colorTexture,
                              GrMipMapsStatus,
                              GrWrapCacheable cacheable);
 
diff --git a/src/gpu/mtl/GrMtlTextureRenderTarget.mm b/src/gpu/mtl/GrMtlTextureRenderTarget.mm
index 16202c9..828b04a 100644
--- a/src/gpu/mtl/GrMtlTextureRenderTarget.mm
+++ b/src/gpu/mtl/GrMtlTextureRenderTarget.mm
@@ -16,54 +16,117 @@
 GrMtlTextureRenderTarget::GrMtlTextureRenderTarget(GrMtlGpu* gpu,
                                                    SkBudgeted budgeted,
                                                    const GrSurfaceDesc& desc,
-                                                   id<MTLTexture> renderTexture,
+                                                   id<MTLTexture> colorTexture,
+                                                   id<MTLTexture> resolveTexture,
                                                    GrMipMapsStatus mipMapsStatus)
         : GrSurface(gpu, desc)
-        , GrMtlTexture(gpu, desc, renderTexture, mipMapsStatus)
-        , GrMtlRenderTarget(gpu, desc, renderTexture) {
+        , GrMtlTexture(gpu, desc, resolveTexture, mipMapsStatus)
+        , GrMtlRenderTarget(gpu, desc, colorTexture, resolveTexture) {
+    this->registerWithCache(budgeted);
+}
+
+GrMtlTextureRenderTarget::GrMtlTextureRenderTarget(GrMtlGpu* gpu,
+                                                   SkBudgeted budgeted,
+                                                   const GrSurfaceDesc& desc,
+                                                   id<MTLTexture> colorTexture,
+                                                   GrMipMapsStatus mipMapsStatus)
+        : GrSurface(gpu, desc)
+        , GrMtlTexture(gpu, desc, colorTexture, mipMapsStatus)
+        , GrMtlRenderTarget(gpu, desc, colorTexture) {
     this->registerWithCache(budgeted);
 }
 
 GrMtlTextureRenderTarget::GrMtlTextureRenderTarget(GrMtlGpu* gpu,
                                                    const GrSurfaceDesc& desc,
-                                                   id<MTLTexture> renderTexture,
+                                                   id<MTLTexture> colorTexture,
+                                                   id<MTLTexture> resolveTexture,
                                                    GrMipMapsStatus mipMapsStatus,
                                                    GrWrapCacheable cacheable)
         : GrSurface(gpu, desc)
-        , GrMtlTexture(gpu, desc, renderTexture, mipMapsStatus)
-        , GrMtlRenderTarget(gpu, desc, renderTexture) {
+        , GrMtlTexture(gpu, desc, resolveTexture, mipMapsStatus)
+        , GrMtlRenderTarget(gpu, desc, colorTexture, resolveTexture) {
     this->registerWithCacheWrapped(cacheable);
 }
 
-sk_sp<GrMtlTextureRenderTarget>
-GrMtlTextureRenderTarget::CreateNewTextureRenderTarget(GrMtlGpu* gpu,
-                                                       SkBudgeted budgeted,
-                                                       const GrSurfaceDesc& desc,
-                                                       MTLTextureDescriptor* texDesc,
-                                                       GrMipMapsStatus mipMapsStatus) {
-    id<MTLTexture> renderTexture = [gpu->device() newTextureWithDescriptor:texDesc];
-    SkASSERT(nil != renderTexture);
-    if (desc.fSampleCnt > 1) {
+GrMtlTextureRenderTarget::GrMtlTextureRenderTarget(GrMtlGpu* gpu,
+                                                   const GrSurfaceDesc& desc,
+                                                   id<MTLTexture> colorTexture,
+                                                   GrMipMapsStatus mipMapsStatus,
+                                                   GrWrapCacheable cacheable)
+        : GrSurface(gpu, desc)
+        , GrMtlTexture(gpu, desc, colorTexture, mipMapsStatus)
+        , GrMtlRenderTarget(gpu, desc, colorTexture) {
+    this->registerWithCacheWrapped(cacheable);
+}
+
+id<MTLTexture> create_msaa_texture(GrMtlGpu* gpu, const GrSurfaceDesc& desc) {
+    MTLPixelFormat format;
+    if (!GrPixelConfigToMTLFormat(desc.fConfig, &format)) {
         return nullptr;
     }
-    SkASSERT((MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget) & renderTexture.usage);
-    return sk_sp<GrMtlTextureRenderTarget>(
-            new GrMtlTextureRenderTarget(gpu, budgeted, desc, renderTexture, mipMapsStatus));
+    MTLTextureDescriptor* texDesc = [[MTLTextureDescriptor alloc] init];
+    texDesc.textureType = MTLTextureType2DMultisample;
+    texDesc.pixelFormat = format;
+    texDesc.width = desc.fWidth;
+    texDesc.height = desc.fHeight;
+    texDesc.depth = 1;
+    texDesc.mipmapLevelCount = 1;
+    texDesc.sampleCount = desc.fSampleCnt;
+    texDesc.arrayLength = 1;
+    texDesc.storageMode = MTLStorageModePrivate;
+    texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
+
+    return [gpu->device() newTextureWithDescriptor:texDesc];
+}
+
+sk_sp<GrMtlTextureRenderTarget>
+GrMtlTextureRenderTarget::MakeNewTextureRenderTarget(GrMtlGpu* gpu,
+                                                     SkBudgeted budgeted,
+                                                     const GrSurfaceDesc& desc,
+                                                     MTLTextureDescriptor* texDesc,
+                                                     GrMipMapsStatus mipMapsStatus) {
+    id<MTLTexture> texture = [gpu->device() newTextureWithDescriptor:texDesc];
+    if (!texture) {
+        return nullptr;
+    }
+    SkASSERT((MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget) & texture.usage);
+
+    if (desc.fSampleCnt > 1) {
+        id<MTLTexture> colorTexture = create_msaa_texture(gpu, desc);
+        if (!colorTexture) {
+            return nullptr;
+        }
+        SkASSERT((MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget) & colorTexture.usage);
+        return sk_sp<GrMtlTextureRenderTarget>(
+                new GrMtlTextureRenderTarget(gpu, budgeted, desc, colorTexture, texture,
+                                             mipMapsStatus));
+    } else {
+        return sk_sp<GrMtlTextureRenderTarget>(
+                new GrMtlTextureRenderTarget(gpu, budgeted, desc, texture, mipMapsStatus));
+    }
 }
 
 sk_sp<GrMtlTextureRenderTarget> GrMtlTextureRenderTarget::MakeWrappedTextureRenderTarget(
         GrMtlGpu* gpu,
         const GrSurfaceDesc& desc,
-        id<MTLTexture> renderTexture,
+        id<MTLTexture> texture,
         GrWrapCacheable cacheable) {
-    SkASSERT(nil != renderTexture);
-    GrMipMapsStatus mipMapsStatus = renderTexture.mipmapLevelCount > 1
+    SkASSERT(nil != texture);
+    SkASSERT((MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget) & texture.usage);
+    GrMipMapsStatus mipMapsStatus = texture.mipmapLevelCount > 1
                                             ? GrMipMapsStatus::kDirty
                                             : GrMipMapsStatus::kNotAllocated;
     if (desc.fSampleCnt > 1) {
-        return nullptr;
+        id<MTLTexture> colorTexture = create_msaa_texture(gpu, desc);
+        if (!colorTexture) {
+            return nullptr;
+        }
+        SkASSERT((MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget) & colorTexture.usage);
+        return sk_sp<GrMtlTextureRenderTarget>(
+                new GrMtlTextureRenderTarget(gpu, desc, colorTexture, texture, mipMapsStatus,
+                                             cacheable));
+    } else {
+        return sk_sp<GrMtlTextureRenderTarget>(
+                new GrMtlTextureRenderTarget(gpu, desc, texture, mipMapsStatus, cacheable));
     }
-    SkASSERT((MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget) & renderTexture.usage);
-    return sk_sp<GrMtlTextureRenderTarget>(
-            new GrMtlTextureRenderTarget(gpu, desc, renderTexture, mipMapsStatus, cacheable));
 }
diff --git a/src/gpu/mtl/GrMtlUtil.h b/src/gpu/mtl/GrMtlUtil.h
index 43a0b14..a8e2cd6 100644
--- a/src/gpu/mtl/GrMtlUtil.h
+++ b/src/gpu/mtl/GrMtlUtil.h
@@ -90,8 +90,8 @@
         id<MTLDevice>, MTLRenderPipelineDescriptor*, bool* timedout);
 
 /**
- * Returns a MTLTexture corresponding to the GrSurface. Optionally can do a resolve.
+ * Returns a MTLTexture corresponding to the GrSurface.
  */
-id<MTLTexture> GrGetMTLTextureFromSurface(GrSurface* surface, bool doResolve);
+id<MTLTexture> GrGetMTLTextureFromSurface(GrSurface* surface);
 
 #endif
diff --git a/src/gpu/mtl/GrMtlUtil.mm b/src/gpu/mtl/GrMtlUtil.mm
index ed26f28..eeef445 100644
--- a/src/gpu/mtl/GrMtlUtil.mm
+++ b/src/gpu/mtl/GrMtlUtil.mm
@@ -257,19 +257,18 @@
     return pipelineState;
 }
 
-id<MTLTexture> GrGetMTLTextureFromSurface(GrSurface* surface, bool doResolve) {
+id<MTLTexture> GrGetMTLTextureFromSurface(GrSurface* surface) {
     id<MTLTexture> mtlTexture = nil;
 
     GrMtlRenderTarget* renderTarget = static_cast<GrMtlRenderTarget*>(surface->asRenderTarget());
     GrMtlTexture* texture;
     if (renderTarget) {
-        if (doResolve) {
-            // TODO: do resolve and set mtlTexture to resolved texture. As of now, we shouldn't
-            // have any multisampled render targets.
+        // We should not be using this for multisampled rendertargets
+        if (renderTarget->numSamples() > 1) {
             SkASSERT(false);
-        } else {
-            mtlTexture = renderTarget->mtlRenderTexture();
+            return nil;
         }
+        mtlTexture = renderTarget->mtlColorTexture();
     } else {
         texture = static_cast<GrMtlTexture*>(surface->asTexture());
         if (texture) {
diff --git a/tools/sk_app/MetalWindowContext.mm b/tools/sk_app/MetalWindowContext.mm
index e4947cd..78fbf61 100644
--- a/tools/sk_app/MetalWindowContext.mm
+++ b/tools/sk_app/MetalWindowContext.mm
@@ -39,8 +39,7 @@
             return;
         }
     }
-    // TODO: Multisampling not supported
-    fSampleCount = 1; //fDisplayParams.fMSAASampleCount;
+    fSampleCount = fDisplayParams.fMSAASampleCount;
     fStencilBits = 8;
 
     fMetalLayer = [CAMetalLayer layer];
@@ -86,16 +85,28 @@
         GrMtlTextureInfo fbInfo;
         fbInfo.fTexture.retain((__bridge const void*)(fCurrentDrawable.texture));
 
-        GrBackendRenderTarget backendRT(fWidth,
-                                        fHeight,
-                                        fSampleCount,
-                                        fbInfo);
+        if (fSampleCount == 1) {
+            GrBackendRenderTarget backendRT(fWidth,
+                                            fHeight,
+                                            fSampleCount,
+                                            fbInfo);
 
-        surface = SkSurface::MakeFromBackendRenderTarget(fContext.get(), backendRT,
-                                                         kTopLeft_GrSurfaceOrigin,
-                                                         kBGRA_8888_SkColorType,
-                                                         fDisplayParams.fColorSpace,
-                                                         &fDisplayParams.fSurfaceProps);
+            surface = SkSurface::MakeFromBackendRenderTarget(fContext.get(), backendRT,
+                                                             kTopLeft_GrSurfaceOrigin,
+                                                             kBGRA_8888_SkColorType,
+                                                             fDisplayParams.fColorSpace,
+                                                             &fDisplayParams.fSurfaceProps);
+        } else {
+            GrBackendTexture backendTexture(fWidth,
+                                            fHeight,
+                                            GrMipMapped::kNo,
+                                            fbInfo);
+
+            surface = SkSurface::MakeFromBackendTexture(
+                    fContext.get(), backendTexture, kTopLeft_GrSurfaceOrigin, fSampleCount,
+                    kBGRA_8888_SkColorType, fDisplayParams.fColorSpace,
+                    &fDisplayParams.fSurfaceProps);
+        }
     }
 
     return surface;
diff --git a/tools/sk_app/mac/Window_mac.h b/tools/sk_app/mac/Window_mac.h
index 2eb37ad..761cd40 100644
--- a/tools/sk_app/mac/Window_mac.h
+++ b/tools/sk_app/mac/Window_mac.h
@@ -19,8 +19,7 @@
 public:
     Window_mac()
             : INHERITED()
-            , fWindow(nil)
-            , fMSAASampleCount(1) {}
+            , fWindow(nil) {}
     ~Window_mac() override {
         this->closeWindow();
     }
@@ -50,7 +49,6 @@
 private:
     NSWindow*    fWindow;
     NSInteger    fWindowNumber;
-    int          fMSAASampleCount;
 
     static SkTDynamicHash<Window_mac, NSInteger> gWindowMap;
 
diff --git a/tools/sk_app/mac/Window_mac.mm b/tools/sk_app/mac/Window_mac.mm
index 65069d8..7fe48ea 100644
--- a/tools/sk_app/mac/Window_mac.mm
+++ b/tools/sk_app/mac/Window_mac.mm
@@ -41,10 +41,6 @@
 }
 
 bool Window_mac::initWindow() {
-    if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) {
-        this->closeWindow();
-    }
-
     // we already have a window
     if (fWindow) {
         return true;