Separate out natively-texture image/bmp draws from cached-as-texture image/bmp draws

This makes texture-backed images and bitmaps down a new code path. It adds a pinch point via the texture adjuster that will be used to handle copied necessary for different texture targets. It also fixes bugs in the existing code exhibited by recent updates to the bleed GM. The plan is to move the the sw/generator-backed imgs/bmps on to this code path with future changes.

Review URL: https://codereview.chromium.org/1424313010
diff --git a/gyp/gpu.gypi b/gyp/gpu.gypi
index b3e89ff..47c7a6b 100644
--- a/gyp/gpu.gypi
+++ b/gyp/gpu.gypi
@@ -100,6 +100,8 @@
       '<(skia_src_path)/gpu/GrGpuResource.cpp',
       '<(skia_src_path)/gpu/GrGpuFactory.cpp',
       '<(skia_src_path)/gpu/GrGpuFactory.h',
+      '<(skia_src_path)/gpu/GrImageIDTextureAdjuster.cpp',
+      '<(skia_src_path)/gpu/GrImageIDTextureAdjuster.h',
       '<(skia_src_path)/gpu/GrIndexBuffer.h',
       '<(skia_src_path)/gpu/GrInvariantOutput.cpp',
       '<(skia_src_path)/gpu/GrLayerAtlas.cpp',
@@ -381,6 +383,7 @@
 
       '<(skia_src_path)/gpu/SkGpuDevice.cpp',
       '<(skia_src_path)/gpu/SkGpuDevice.h',
+      '<(skia_src_path)/gpu/SkGpuDevice_drawTexture.cpp',
       '<(skia_src_path)/gpu/SkGr.cpp',
       '<(skia_src_path)/gpu/SkGrPixelRef.cpp',
       '<(skia_src_path)/gpu/SkGrPriv.h',
diff --git a/src/gpu/GrBlurUtils.cpp b/src/gpu/GrBlurUtils.cpp
index 3d4c00d..c6cff60 100644
--- a/src/gpu/GrBlurUtils.cpp
+++ b/src/gpu/GrBlurUtils.cpp
@@ -51,7 +51,7 @@
                                   const GrClip& clipData,
                                   const SkMatrix& viewMatrix,
                                   const SkPath& devPath,
-                                  SkMaskFilter* filter,
+                                  const SkMaskFilter* filter,
                                   const SkIRect& clipBounds,
                                   GrPaint* grp,
                                   SkPaint::Style style) {
@@ -146,6 +146,127 @@
     return mask;
 }
 
+static void draw_path_with_mask_filter(GrContext* context,
+                                       GrDrawContext* drawContext,
+                                       GrRenderTarget* renderTarget,
+                                       const GrClip& clip,
+                                       GrPaint* paint,
+                                       const SkMatrix& viewMatrix,
+                                       const SkMaskFilter* maskFilter,
+                                       const SkPathEffect* pathEffect,
+                                       const GrStrokeInfo& origStrokeInfo,
+                                       SkPath* pathPtr,
+                                       bool pathIsMutable) {
+    SkASSERT(maskFilter);
+
+    SkIRect clipBounds;
+    clip.getConservativeBounds(renderTarget, &clipBounds);
+    SkTLazy<SkPath> tmpPath;
+    GrStrokeInfo strokeInfo(origStrokeInfo);
+
+    static const SkRect* cullRect = nullptr;  // TODO: what is our bounds?
+
+    if (!strokeInfo.isDashed() && pathEffect && pathEffect->filterPath(tmpPath.init(), *pathPtr,
+                                                                       &strokeInfo, cullRect)) {
+        pathPtr = tmpPath.get();
+        pathPtr->setIsVolatile(true);
+        pathIsMutable = true;
+    }
+    if (!strokeInfo.isHairlineStyle()) {
+        SkPath* strokedPath = pathIsMutable ? pathPtr : tmpPath.init();
+        if (strokeInfo.isDashed()) {
+            if (pathEffect->filterPath(strokedPath, *pathPtr, &strokeInfo, cullRect)) {
+                pathPtr = strokedPath;
+                pathPtr->setIsVolatile(true);
+                pathIsMutable = true;
+            }
+            strokeInfo.removeDash();
+        }
+        if (strokeInfo.applyToPath(strokedPath, *pathPtr)) {
+            pathPtr = strokedPath;
+            pathPtr->setIsVolatile(true);
+            pathIsMutable = true;
+            strokeInfo.setFillStyle();
+        }
+    }
+
+    // avoid possibly allocating a new path in transform if we can
+    SkPath* devPathPtr = pathIsMutable ? pathPtr : tmpPath.init();
+    if (!pathIsMutable) {
+        devPathPtr->setIsVolatile(true);
+    }
+
+    // transform the path into device space
+    pathPtr->transform(viewMatrix, devPathPtr);
+
+    SkRect maskRect;
+    if (maskFilter->canFilterMaskGPU(SkRRect::MakeRect(devPathPtr->getBounds()),
+                                     clipBounds,
+                                     viewMatrix,
+                                     &maskRect)) {
+        SkIRect finalIRect;
+        maskRect.roundOut(&finalIRect);
+        if (clip_bounds_quick_reject(clipBounds, finalIRect)) {
+            // clipped out
+            return;
+        }
+
+        if (maskFilter->directFilterMaskGPU(context->textureProvider(),
+                                            drawContext,
+                                            paint,
+                                            clip,
+                                            viewMatrix,
+                                            strokeInfo,
+                                            *devPathPtr)) {
+            // the mask filter was able to draw itself directly, so there's nothing
+            // left to do.
+            return;
+        }
+
+        SkAutoTUnref<GrTexture> mask(create_mask_GPU(context,
+                                                     &maskRect,
+                                                     *devPathPtr,
+                                                     strokeInfo,
+                                                     paint->isAntiAlias(),
+                                                     renderTarget->numColorSamples()));
+        if (mask) {
+            GrTexture* filtered;
+
+            if (maskFilter->filterMaskGPU(mask, viewMatrix, maskRect, &filtered, true)) {
+                // filterMaskGPU gives us ownership of a ref to the result
+                SkAutoTUnref<GrTexture> atu(filtered);
+                if (draw_mask(drawContext, clip, viewMatrix, maskRect, paint, filtered)) {
+                    // This path is completely drawn
+                    return;
+                }
+            }
+        }
+    }
+
+    // draw the mask on the CPU - this is a fallthrough path in case the
+    // GPU path fails
+    SkPaint::Style style = strokeInfo.isHairlineStyle() ? SkPaint::kStroke_Style :
+                                                          SkPaint::kFill_Style;
+    draw_with_mask_filter(drawContext, context->textureProvider(),
+                          clip, viewMatrix, *devPathPtr,
+                          maskFilter, clipBounds, paint, style);
+}
+
+void GrBlurUtils::drawPathWithMaskFilter(GrContext* context,
+                                         GrDrawContext* drawContext,
+                                         GrRenderTarget* rt,
+                                         const GrClip& clip,
+                                         const SkPath& origPath,
+                                         GrPaint* paint,
+                                         const SkMatrix& viewMatrix,
+                                         const SkMaskFilter* mf,
+                                         const SkPathEffect* pe,
+                                         const GrStrokeInfo& strokeInfo) {
+    SkPath* path = const_cast<SkPath*>(&origPath);
+    draw_path_with_mask_filter(context, drawContext, rt, clip, paint, viewMatrix, mf, pe,
+                               strokeInfo, path, false);
+}
+
 void GrBlurUtils::drawPathWithMaskFilter(GrContext* context, 
                                          GrDrawContext* drawContext,
                                          GrRenderTarget* renderTarget,
@@ -173,7 +294,7 @@
     if (prePathMatrix) {
         // stroking, path effects, and blurs are supposed to be applied *after* the prePathMatrix.
         // The pre-path-matrix also should not affect shading.
-        if (nullptr == paint.getMaskFilter() && nullptr == pathEffect && nullptr == paint.getShader() &&
+        if (!paint.getMaskFilter() && !pathEffect && !paint.getShader() &&
             (strokeInfo.isFillStyle() || strokeInfo.isHairlineStyle())) {
             viewMatrix.preConcat(*prePathMatrix);
         } else {
@@ -198,99 +319,19 @@
         return;
     }
 
-    const SkRect* cullRect = nullptr;  // TODO: what is our bounds?
-    if (!strokeInfo.isDashed() && pathEffect && pathEffect->filterPath(effectPath.init(), *pathPtr,
-                                                                       &strokeInfo, cullRect)) {
-        pathPtr = effectPath.get();
-        pathIsMutable = true;
-    }
-
     if (paint.getMaskFilter()) {
-        if (!strokeInfo.isHairlineStyle()) {
-            SkPath* strokedPath = pathIsMutable ? pathPtr : tmpPath.init();
-            if (strokeInfo.isDashed()) {
-                if (pathEffect->filterPath(strokedPath, *pathPtr, &strokeInfo, cullRect)) {
-                    pathPtr = strokedPath;
-                    pathIsMutable = true;
-                }
-                strokeInfo.removeDash();
-            }
-            if (strokeInfo.applyToPath(strokedPath, *pathPtr)) {
-                pathPtr = strokedPath;
-                pathIsMutable = true;
-                strokeInfo.setFillStyle();
-            }
+        draw_path_with_mask_filter(context, drawContext, renderTarget, clip, &grPaint, viewMatrix,
+                                   paint.getMaskFilter(), paint.getPathEffect(), strokeInfo,
+                                   pathPtr, pathIsMutable);
+    } else {
+        SkTLazy<SkPath> tmpPath2;
+        if (!strokeInfo.isDashed() && pathEffect &&
+            pathEffect->filterPath(tmpPath2.init(), *pathPtr, &strokeInfo, nullptr)) {
+            pathPtr = tmpPath2.get();
+            pathPtr->setIsVolatile(true);
+            pathIsMutable = true;
         }
-
-        // avoid possibly allocating a new path in transform if we can
-        SkPath* devPathPtr = pathIsMutable ? pathPtr : tmpPath.init();
-        if (!pathIsMutable) {
-            devPathPtr->setIsVolatile(true);
-        }
-
-        // transform the path into device space
-        pathPtr->transform(viewMatrix, devPathPtr);
-
-        SkRect maskRect;
-        if (paint.getMaskFilter()->canFilterMaskGPU(SkRRect::MakeRect(devPathPtr->getBounds()),
-                                                    clipBounds,
-                                                    viewMatrix,
-                                                    &maskRect)) {
-            SkIRect finalIRect;
-            maskRect.roundOut(&finalIRect);
-            if (clip_bounds_quick_reject(clipBounds, finalIRect)) {
-                // clipped out
-                return;
-            }
-
-            if (paint.getMaskFilter()->directFilterMaskGPU(context->textureProvider(),
-                                                           drawContext,
-                                                           &grPaint,
-                                                           clip,
-                                                           viewMatrix,
-                                                           strokeInfo,
-                                                           *devPathPtr)) {
-                // the mask filter was able to draw itself directly, so there's nothing
-                // left to do.
-                return;
-            }
-
-            SkAutoTUnref<GrTexture> mask(create_mask_GPU(context,
-                                                         &maskRect,
-                                                         *devPathPtr,
-                                                         strokeInfo,
-                                                         grPaint.isAntiAlias(),
-                                                         renderTarget->numColorSamples()));
-            if (mask) {
-                GrTexture* filtered;
-
-                if (paint.getMaskFilter()->filterMaskGPU(mask, viewMatrix, maskRect,
-                                                         &filtered, true)) {
-                    // filterMaskGPU gives us ownership of a ref to the result
-                    SkAutoTUnref<GrTexture> atu(filtered);
-                    if (draw_mask(drawContext,
-                                  clip,
-                                  viewMatrix,
-                                  maskRect,
-                                  &grPaint,
-                                  filtered)) {
-                        // This path is completely drawn
-                        return;
-                    }
-                }
-            }
-        }
-
-        // draw the mask on the CPU - this is a fallthrough path in case the
-        // GPU path fails
-        SkPaint::Style style = strokeInfo.isHairlineStyle() ? SkPaint::kStroke_Style :
-                                                              SkPaint::kFill_Style;
-        draw_with_mask_filter(drawContext, context->textureProvider(),
-                              clip, viewMatrix, *devPathPtr,
-                              paint.getMaskFilter(), clipBounds, &grPaint, style);
-        return;
+        drawContext->drawPath(clip, grPaint, viewMatrix, *pathPtr, strokeInfo);
     }
-
-    drawContext->drawPath(clip, grPaint, viewMatrix, *pathPtr, strokeInfo);
 }
 
diff --git a/src/gpu/GrBlurUtils.h b/src/gpu/GrBlurUtils.h
index 0fc7726..d64f492 100644
--- a/src/gpu/GrBlurUtils.h
+++ b/src/gpu/GrBlurUtils.h
@@ -11,11 +11,15 @@
 class GrClip;
 class GrContext;
 class GrDrawContext;
+class GrPaint;
 class GrRenderTarget;
+class GrStrokeInfo;
 struct SkIRect;
+class SkMaskFilter;
 class SkMatrix;
 class SkPaint;
 class SkPath;
+class SkPathEffect;
 
 
 /**
@@ -35,6 +39,22 @@
                                 const SkMatrix* prePathMatrix,
                                 const SkIRect& clipBounds,
                                 bool pathIsMutable);
+
+    /**
+     * Draw a path handling the mask filter. The mask filter is not optional. The path effect is
+     * optional. The GrPaint will be modified after return.
+     */
+    void drawPathWithMaskFilter(GrContext*,
+                                GrDrawContext*,
+                                GrRenderTarget*,
+                                const GrClip&,
+                                const SkPath& path,
+                                GrPaint*,
+                                const SkMatrix& viewMatrix,
+                                const SkMaskFilter*,
+                                const SkPathEffect*,
+                                const GrStrokeInfo&);
+
 };
 
 #endif
diff --git a/src/gpu/GrDrawContext.cpp b/src/gpu/GrDrawContext.cpp
index ec26672..9c07211 100644
--- a/src/gpu/GrDrawContext.cpp
+++ b/src/gpu/GrDrawContext.cpp
@@ -225,6 +225,14 @@
            point.fY >= rect.fTop && point.fY <= rect.fBottom;
 }
 
+static bool view_matrix_ok_for_aa_fill_rect(const SkMatrix& viewMatrix) {
+    return viewMatrix.preservesRightAngles();
+}
+
+static bool should_apply_coverage_aa(const GrPaint& paint, GrRenderTarget* rt) {
+    return paint.isAntiAlias() && !rt->isUnifiedMultisampled();
+}
+
 void GrDrawContext::drawRect(const GrClip& clip,
                              const GrPaint& paint,
                              const SkMatrix& viewMatrix,
@@ -282,13 +290,13 @@
     }
 
     GrColor color = paint.getColor();
-    bool needAA = paint.isAntiAlias() &&
-                  !pipelineBuilder.getRenderTarget()->isUnifiedMultisampled();
+    bool needAA = should_apply_coverage_aa(paint, pipelineBuilder.getRenderTarget());
 
     // The fill path can handle rotation but not skew
     // The stroke path needs the rect to remain axis aligned (no rotation or skew)
     // None of our AA draw rect calls can handle perspective yet
-    bool canApplyAA = width >=0 ? viewMatrix.rectStaysRect() : viewMatrix.preservesRightAngles();
+    bool canApplyAA = width >=0 ? viewMatrix.rectStaysRect() :
+                                  view_matrix_ok_for_aa_fill_rect(viewMatrix);
 
     if (needAA && canApplyAA) {
         SkASSERT(!viewMatrix.hasPerspective());
@@ -333,11 +341,20 @@
     AutoCheckFlush acf(fDrawingManager);
 
     GrPipelineBuilder pipelineBuilder(paint, fRenderTarget, clip);
-    this->getDrawTarget()->drawNonAARect(pipelineBuilder,
-                                         paint.getColor(),
-                                         viewMatrix,
-                                         rectToDraw,
-                                         localRect);
+    if (should_apply_coverage_aa(paint, fRenderTarget) &&
+        view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
+        SkAutoTUnref<GrDrawBatch> batch(GrAAFillRectBatch::CreateWithLocalRect(
+            paint.getColor(), viewMatrix, rectToDraw, localRect));
+        if (batch) {
+            this->drawBatch(&pipelineBuilder, batch);
+        }
+    } else {
+        this->getDrawTarget()->drawNonAARect(pipelineBuilder,
+                                             paint.getColor(),
+                                             viewMatrix,
+                                             rectToDraw,
+                                             localRect);
+    }
 }
 
 void GrDrawContext::fillRectWithLocalMatrix(const GrClip& clip,
@@ -351,11 +368,18 @@
     AutoCheckFlush acf(fDrawingManager);
 
     GrPipelineBuilder pipelineBuilder(paint, fRenderTarget, clip);
-    this->getDrawTarget()->drawNonAARect(pipelineBuilder,
-                                         paint.getColor(),
-                                         viewMatrix,
-                                         rectToDraw,
-                                         localMatrix);
+    if (should_apply_coverage_aa(paint, pipelineBuilder.getRenderTarget()) &&
+        view_matrix_ok_for_aa_fill_rect(viewMatrix)) {
+        SkAutoTUnref<GrDrawBatch> batch(GrAAFillRectBatch::Create(
+            paint.getColor(), viewMatrix, localMatrix, rectToDraw));
+        this->drawBatch(&pipelineBuilder, batch);
+    } else {
+        this->getDrawTarget()->drawNonAARect(pipelineBuilder,
+                                             paint.getColor(),
+                                             viewMatrix,
+                                             rectToDraw,
+                                             localMatrix);
+    }
 }
 
 void GrDrawContext::drawVertices(const GrClip& clip,
@@ -632,8 +656,7 @@
 
     GrPipelineBuilder pipelineBuilder(paint, fRenderTarget, clip);
     if (!strokeInfo.isDashed()) {
-        bool useCoverageAA = paint.isAntiAlias() &&
-                !pipelineBuilder.getRenderTarget()->isUnifiedMultisampled();
+        bool useCoverageAA = should_apply_coverage_aa(paint, pipelineBuilder.getRenderTarget());
 
         if (useCoverageAA && strokeInfo.getWidth() < 0 && !path.isConvex()) {
             // Concave AA paths are expensive - try to avoid them for special cases
diff --git a/src/gpu/GrImageIDTextureAdjuster.cpp b/src/gpu/GrImageIDTextureAdjuster.cpp
new file mode 100644
index 0000000..525223c
--- /dev/null
+++ b/src/gpu/GrImageIDTextureAdjuster.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrImageIDTextureAdjuster.h"
+
+#include "SkBitmap.h"
+#include "SkGrPriv.h"
+#include "SkImage_Base.h"
+
+
+GrBitmapTextureAdjuster::GrBitmapTextureAdjuster(const SkBitmap* bmp)
+    : INHERITED(bmp->getTexture(), SkIRect::MakeWH(bmp->width(), bmp->height()))
+    , fBmp(bmp) {}
+
+void GrBitmapTextureAdjuster::makeCopyKey(const CopyParams& params, GrUniqueKey* copyKey) {
+    if (fBmp->isVolatile()) {
+        return;
+    }
+    // The content area must represent the whole bitmap. Texture-backed bitmaps don't support
+    // extractSubset(). Therefore, either the bitmap and the texture are the same size or the
+    // content's dimensions are the bitmap's dimensions which is pinned to the upper left
+    // of the texture.
+    GrUniqueKey baseKey;
+    GrMakeKeyFromImageID(&baseKey, fBmp->getGenerationID(),
+                         SkIRect::MakeWH(fBmp->width(), fBmp->height()));
+    MakeCopyKeyFromOrigKey(baseKey, params, copyKey);
+}
+
+void GrBitmapTextureAdjuster::didCacheCopy(const GrUniqueKey& copyKey) {
+    GrInstallBitmapUniqueKeyInvalidator(copyKey, fBmp->pixelRef());
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+GrImageTextureAdjuster::GrImageTextureAdjuster(const SkImage_Base* img)
+    : INHERITED(img->peekTexture(), SkIRect::MakeWH(img->width(), img->height()))
+    , fImageBase(img) {}
+
+void GrImageTextureAdjuster::makeCopyKey(const CopyParams& params, GrUniqueKey* copyKey) {
+    // By construction this texture adjuster always represents an entire SkImage, so use the
+    // image's width and height for the key's rectangle.
+    GrUniqueKey baseKey;
+    GrMakeKeyFromImageID(&baseKey, fImageBase->uniqueID(),
+                         SkIRect::MakeWH(fImageBase->width(), fImageBase->height()));
+    MakeCopyKeyFromOrigKey(baseKey, params, copyKey);
+}
+
+void GrImageTextureAdjuster::didCacheCopy(const GrUniqueKey& copyKey) {
+    // We don't currently have a mechanism for notifications on Images!
+}
diff --git a/src/gpu/GrImageIDTextureAdjuster.h b/src/gpu/GrImageIDTextureAdjuster.h
new file mode 100644
index 0000000..6c0747a
--- /dev/null
+++ b/src/gpu/GrImageIDTextureAdjuster.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef GrImageIDTextureAdjuster_DEFINED
+#define GrImageIDTextureAdjuster_DEFINED
+
+#include "GrTextureParamsAdjuster.h"
+
+class SkBitmap;
+class SkImage_Base;
+
+/** Implementation for texture-backed SkBitmaps. The bitmap must stay in scope and unmodified 
+    while this object exists. */
+class GrBitmapTextureAdjuster : public GrTextureAdjuster {
+public:
+    explicit GrBitmapTextureAdjuster(const SkBitmap* bmp);
+
+private:
+    void makeCopyKey(const CopyParams& params, GrUniqueKey* copyKey) override;
+
+    void didCacheCopy(const GrUniqueKey& copyKey) override;
+
+    const SkBitmap* fBmp;
+
+    typedef GrTextureAdjuster INHERITED;
+};
+
+/** Implementation for texture-backed SkImages. The image must stay in scope and unmodified while
+    this object exists. */
+class GrImageTextureAdjuster : public GrTextureAdjuster {
+public:
+    explicit GrImageTextureAdjuster(const SkImage_Base* img);
+
+private:
+    void makeCopyKey(const CopyParams& params, GrUniqueKey* copyKey) override;
+
+    void didCacheCopy(const GrUniqueKey& copyKey) override;
+
+    const SkImage_Base* fImageBase;
+
+    typedef GrTextureAdjuster INHERITED;
+};
+
+#endif
diff --git a/src/gpu/GrStrokeInfo.h b/src/gpu/GrStrokeInfo.h
index a820a02..9cf7d83 100644
--- a/src/gpu/GrStrokeInfo.h
+++ b/src/gpu/GrStrokeInfo.h
@@ -21,6 +21,11 @@
  */
 class GrStrokeInfo : public SkStrokeRec {
 public:
+    static const GrStrokeInfo& FillInfo() {
+        static const GrStrokeInfo gFill(kFill_InitStyle);
+        return gFill;
+    }
+
     GrStrokeInfo(SkStrokeRec::InitStyle style)
         : INHERITED(style)
         , fDashType(SkPathEffect::kNone_DashType) {
diff --git a/src/gpu/GrTextureParamsAdjuster.cpp b/src/gpu/GrTextureParamsAdjuster.cpp
index 579cf96..b98a126 100644
--- a/src/gpu/GrTextureParamsAdjuster.cpp
+++ b/src/gpu/GrTextureParamsAdjuster.cpp
@@ -19,6 +19,7 @@
 #include "SkCanvas.h"
 #include "SkGr.h"
 #include "SkGrPriv.h"
+#include "effects/GrBicubicEffect.h"
 #include "effects/GrTextureDomain.h"
 
 typedef GrTextureProducer::CopyParams CopyParams;
@@ -131,7 +132,7 @@
     GrTexture* texture = this->originalTexture();
     GrContext* context = texture->getContext();
     CopyParams copyParams;
-    const SkIRect* contentArea = this->contentArea();
+    const SkIRect* contentArea = this->contentAreaOrNull();
 
     if (contentArea && GrTextureParams::kMipMap_FilterMode == params.filterMode()) {
         // If we generate a MIP chain for texture it will read pixel values from outside the content
@@ -171,6 +172,211 @@
     return result;
 }
 
+enum DomainMode {
+    kNoDomain_DomainMode,
+    kDomain_DomainMode,
+    kTightCopy_DomainMode
+};
+
+/** Determines whether a texture domain is necessary and if so what domain to use. There are two
+ *  rectangles to consider:
+ *  - The first is the content area specified by the texture adjuster. We can *never* allow
+ *    filtering to cause bleed of pixels outside this rectangle.
+ *  - The second rectangle is the constraint rectangle, which is known to be contained by the
+ *    content area. The filterConstraint specifies whether we are allowed to bleed across this
+ *    rect.
+ *
+ *  We want to avoid using a domain if possible. We consider the above rectangles, the filter type,
+ *  and whether the coords generated by the draw would all fall within the constraint rect. If the
+ *  latter is true we only need to consider whether the filter would extend beyond the rects.
+ */
+static DomainMode determine_domain_mode(
+                                    const SkRect& constraintRect,
+                                    GrTextureAdjuster::FilterConstraint filterConstraint,
+                                    bool coordsLimitedToConstraintRect,
+                                    int texW, int texH,
+                                    const SkIRect* textureContentArea,
+                                    const GrTextureParams::FilterMode* filterModeOrNullForBicubic,
+                                    SkRect* domainRect) {
+
+    SkASSERT(SkRect::MakeIWH(texW, texH).contains(constraintRect));
+    // We only expect a content area rect if there is some non-content area.
+    SkASSERT(!textureContentArea ||
+             (!textureContentArea->contains(SkIRect::MakeWH(texW, texH)) &&
+              SkRect::Make(*textureContentArea).contains(constraintRect)));
+
+    SkRect textureBounds = SkRect::MakeIWH(texW, texH);
+    // If the src rectangle contains the whole texture then no need for a domain.
+    if (constraintRect.contains(textureBounds)) {
+        return kNoDomain_DomainMode;
+    }
+
+    bool restrictFilterToRect = (filterConstraint == GrTextureAdjuster::kYes_FilterConstraint);
+
+    // If we can filter outside the constraint rect, and there is no non-content area of the
+    // texture, and we aren't going to generate sample coords outside the constraint rect then we
+    // don't need a domain.
+    if (!restrictFilterToRect && !textureContentArea && coordsLimitedToConstraintRect) {
+        return kNoDomain_DomainMode;
+    }
+
+    // Get the domain inset based on sampling mode (or bail if mipped)
+    SkScalar filterHalfWidth = 0.f;
+    if (filterModeOrNullForBicubic) {
+        switch (*filterModeOrNullForBicubic) {
+            case GrTextureParams::kNone_FilterMode:
+                if (coordsLimitedToConstraintRect) {
+                    return kNoDomain_DomainMode;
+                } else {
+                    filterHalfWidth = 0.f;
+                }
+                break;
+            case GrTextureParams::kBilerp_FilterMode:
+                filterHalfWidth = .5f;
+                break;
+            case GrTextureParams::kMipMap_FilterMode:
+                // No domain can save use here.
+                return kTightCopy_DomainMode;
+        }
+    } else {
+        // bicubic does nearest filtering internally.
+        filterHalfWidth = 1.5f;
+    }
+
+    // Both bilerp and bicubic use bilinear filtering and so need to be clamped to the center
+    // of the edge texel. Pinning to the texel center has no impact on nearest mode and MIP-maps
+
+    static const SkScalar kDomainInset = 0.5f;
+    // Figure out the limits of pixels we're allowed to sample from.
+    // Unless we know the amount of outset and the texture matrix we have to conservatively enforce
+    // the domain.
+    if (restrictFilterToRect) {
+        domainRect->fLeft = constraintRect.fLeft + kDomainInset;
+        domainRect->fTop = constraintRect.fTop + kDomainInset;
+        domainRect->fRight = constraintRect.fRight - kDomainInset;
+        domainRect->fBottom = constraintRect.fBottom - kDomainInset;
+    } else if (textureContentArea) {
+        // If we got here then: there is a textureContentArea, the coords are limited to the
+        // constraint rect, and we're allowed to filter across the constraint rect boundary. So
+        // we check whether the filter would reach across the edge of the content area.
+        // We will only set the sides that are required.
+
+        domainRect->setLargest();
+        if (coordsLimitedToConstraintRect) {
+            // We may be able to use the fact that the texture coords are limited to the constraint
+            // rect in order to avoid having to add a domain.
+            bool needContentAreaConstraint = false;
+            if (textureContentArea->fLeft > 0 &&
+                textureContentArea->fLeft + filterHalfWidth > constraintRect.fLeft) {
+                domainRect->fLeft = textureContentArea->fLeft + kDomainInset;
+                needContentAreaConstraint = true;
+            }
+            if (textureContentArea->fTop > 0 &&
+                textureContentArea->fTop + filterHalfWidth > constraintRect.fTop) {
+                domainRect->fTop = textureContentArea->fTop + kDomainInset;
+                needContentAreaConstraint = true;
+            }
+            if (textureContentArea->fRight < texW &&
+                textureContentArea->fRight - filterHalfWidth < constraintRect.fRight) {
+                domainRect->fRight = textureContentArea->fRight - kDomainInset;
+                needContentAreaConstraint = true;
+            }
+            if (textureContentArea->fBottom < texH &&
+                textureContentArea->fBottom - filterHalfWidth < constraintRect.fBottom) {
+                domainRect->fBottom = textureContentArea->fBottom - kDomainInset;
+                needContentAreaConstraint = true;
+            }
+            if (!needContentAreaConstraint) {
+                return kNoDomain_DomainMode;
+            }
+        } else {
+            // Our sample coords for the texture are allowed to be outside the constraintRect so we
+            // don't consider it when computing the domain.
+            if (textureContentArea->fLeft != 0) {
+                domainRect->fLeft = textureContentArea->fLeft + kDomainInset;
+            }
+            if (textureContentArea->fTop != 0) {
+                domainRect->fTop = textureContentArea->fTop + kDomainInset;
+            }
+            if (textureContentArea->fRight != texW) {
+                domainRect->fRight = textureContentArea->fRight - kDomainInset;
+            }
+            if (textureContentArea->fBottom != texH) {
+                domainRect->fBottom = textureContentArea->fBottom - kDomainInset;
+            }
+        }
+    } else {
+        return kNoDomain_DomainMode;
+    }
+
+    if (domainRect->fLeft > domainRect->fRight) {
+        domainRect->fLeft = domainRect->fRight = SkScalarAve(domainRect->fLeft, domainRect->fRight);
+    }
+    if (domainRect->fTop > domainRect->fBottom) {
+        domainRect->fTop = domainRect->fBottom = SkScalarAve(domainRect->fTop, domainRect->fBottom);
+    }
+    domainRect->fLeft /= texW;
+    domainRect->fTop /= texH;
+    domainRect->fRight /= texW;
+    domainRect->fBottom /= texH;
+    return kDomain_DomainMode;
+}
+
+const GrFragmentProcessor* GrTextureAdjuster::createFragmentProcessor(
+                                        const SkMatrix& textureMatrix,
+                                        const SkRect& constraintRect,
+                                        FilterConstraint filterConstraint,
+                                        bool coordsLimitedToConstraintRect,
+                                        const GrTextureParams::FilterMode* filterOrNullForBicubic) {
+
+    const SkIRect* contentArea = this->contentAreaOrNull();
+
+    SkRect domain;
+    GrTexture* texture = this->originalTexture();
+    DomainMode domainMode =
+        determine_domain_mode(constraintRect, filterConstraint, coordsLimitedToConstraintRect,
+                              texture->width(), texture->height(),
+                              contentArea, filterOrNullForBicubic,
+                              &domain);
+    if (kTightCopy_DomainMode == domainMode) {
+        // TODO: Copy the texture and adjust the texture matrix (both parts need to consider
+        // non-int constraint rect)
+        // For now: treat as bilerp and ignore what goes on above level 0.
+
+        // We only expect MIP maps to require a tight copy.
+        SkASSERT(filterOrNullForBicubic &&
+                 GrTextureParams::kMipMap_FilterMode == *filterOrNullForBicubic);
+        static const GrTextureParams::FilterMode kBilerp = GrTextureParams::kBilerp_FilterMode;
+        domainMode =
+            determine_domain_mode(constraintRect, filterConstraint, coordsLimitedToConstraintRect,
+                                  texture->width(), texture->height(),
+                                  contentArea, &kBilerp, &domain);
+        SkASSERT(kTightCopy_DomainMode != domainMode);
+        filterOrNullForBicubic = &kBilerp;
+    }
+    SkASSERT(kNoDomain_DomainMode == domainMode ||
+             (domain.fLeft <= domain.fRight && domain.fTop <= domain.fBottom));
+    if (filterOrNullForBicubic) {
+        if (kDomain_DomainMode == domainMode) {
+            SkASSERT(*filterOrNullForBicubic != GrTextureParams::kMipMap_FilterMode);
+            return GrTextureDomainEffect::Create(texture, textureMatrix, domain,
+                                                 GrTextureDomain::kClamp_Mode,
+                                                 *filterOrNullForBicubic);
+        } else {
+            GrTextureParams params(SkShader::kClamp_TileMode, *filterOrNullForBicubic);
+            return GrSimpleTextureEffect::Create(texture, textureMatrix, params);
+        }
+    } else {
+        if (kDomain_DomainMode == domainMode) {
+            return GrBicubicEffect::Create(texture, textureMatrix, domain);
+        } else {
+            static const SkShader::TileMode kClampClamp[] =
+            { SkShader::kClamp_TileMode, SkShader::kClamp_TileMode };
+            return GrBicubicEffect::Create(texture, textureMatrix, kClampClamp);
+        }
+    }
+}
+
 //////////////////////////////////////////////////////////////////////////////
 
 GrTexture* GrTextureMaker::refTextureForParams(GrContext* ctx, const GrTextureParams& params) {
diff --git a/src/gpu/GrTextureParamsAdjuster.h b/src/gpu/GrTextureParamsAdjuster.h
index f8fdc2d..4e1f5a7 100644
--- a/src/gpu/GrTextureParamsAdjuster.h
+++ b/src/gpu/GrTextureParamsAdjuster.h
@@ -10,10 +10,10 @@
 
 #include "GrTextureParams.h"
 #include "GrResourceKey.h"
+#include "GrTexture.h"
 #include "SkTLazy.h"
 
 class GrContext;
-class GrTexture;
 class GrTextureParams;
 class GrUniqueKey;
 class SkBitmap;
@@ -83,16 +83,54 @@
         does not match subset's dimensions then the contents are scaled to fit the copy.*/
     GrTexture* refTextureSafeForParams(const GrTextureParams&, SkIPoint* outOffset);
 
+    enum FilterConstraint {
+        kYes_FilterConstraint,
+        kNo_FilterConstraint,
+    };
+
+    /**
+     * Helper for creating a fragment processor to sample the texture with a given filtering mode.
+     * It attempts to avoids making a copy of the texture and avoid using a texture domain unless
+     * necessary.
+     *
+     * @param textureMatrix                    Matrix to transform local coords by to compute
+     *                                         texture coords.
+     * @param constraintRect                   Subrect of content area to be rendered. Must be
+     *                                         clipped to the content area already.
+     * @param filterConstriant                 Indicates whether filtering is limited to
+     *                                         constraintRect.
+     * @param coordsLimitedToConstraintRect    Is it known that textureMatrix*localCoords is bound
+     *                                         by the portion of the texture indicated by
+     *                                         constraintRect (without consideration of filter
+     *                                         width, just the raw coords).
+     * @param filterOrNullForBicubic           If non-null indicates the filter mode. If null means
+     *                                         use bicubic filtering.
+     **/
+    const GrFragmentProcessor* createFragmentProcessor(
+        const SkMatrix& textureMatrix,
+        const SkRect& constraintRect,
+        FilterConstraint filterConstraint,
+        bool coordsLimitedToConstraintRect,
+        const GrTextureParams::FilterMode* filterOrNullForBicubic);
+
+    GrTexture* originalTexture() const { return fOriginal; }
+
+    void getContentArea(SkIRect* contentArea) const {
+        if (fContentArea.isValid()) {
+            *contentArea = *fContentArea.get();
+        } else {
+            *contentArea = SkIRect::MakeWH(fOriginal->width(), fOriginal->height());
+        }
+    }
+
 protected:
     /** The whole texture is content. */
     explicit GrTextureAdjuster(GrTexture* original): fOriginal(original) {}
 
     GrTextureAdjuster(GrTexture* original, const SkIRect& contentArea);
 
-    GrTexture* originalTexture() { return fOriginal; }
-
     /** Returns the content area or null for the whole original texture */
-    const SkIRect* contentArea() { return fContentArea.getMaybeNull(); }
+    const SkIRect* contentAreaOrNull() { return fContentArea.getMaybeNull(); }
 
 private:
     SkTLazy<SkIRect>    fContentArea;
diff --git a/src/gpu/SkGpuDevice.cpp b/src/gpu/SkGpuDevice.cpp
index 6a317d5..c97d248 100644
--- a/src/gpu/SkGpuDevice.cpp
+++ b/src/gpu/SkGpuDevice.cpp
@@ -13,6 +13,7 @@
 #include "GrFontScaler.h"
 #include "GrGpu.h"
 #include "GrGpuResourcePriv.h"
+#include "GrImageIDTextureAdjuster.h"
 #include "GrLayerHoister.h"
 #include "GrRecordReplaceDraw.h"
 #include "GrStrokeInfo.h"
@@ -841,13 +842,26 @@
                              const SkBitmap& bitmap,
                              const SkMatrix& m,
                              const SkPaint& paint) {
+
+    GrTexture* texture = bitmap.getTexture();
+    if (texture) {
+        CHECK_SHOULD_DRAW(origDraw);
+        bool alphaOnly = kAlpha_8_SkColorType == bitmap.colorType();
+        GrBitmapTextureAdjuster adjuster(&bitmap);
+        SkMatrix viewMatrix;
+        viewMatrix.setConcat(*origDraw.fMatrix, m);
+        this->drawTextureAdjuster(&adjuster, alphaOnly, nullptr, nullptr,
+                                  SkCanvas::kFast_SrcRectConstraint, viewMatrix, fClip, paint);
+        return;
+    }
     SkMatrix concat;
     SkTCopyOnFirstWrite<SkDraw> draw(origDraw);
     if (!m.isIdentity()) {
         concat.setConcat(*draw->fMatrix, m);
         draw.writable()->fMatrix = &concat;
     }
-    this->drawBitmapCommon(*draw, bitmap, nullptr, nullptr, paint, SkCanvas::kStrict_SrcRectConstraint);
+    this->drawBitmapCommon(*draw, bitmap, nullptr, nullptr, paint,
+                           SkCanvas::kStrict_SrcRectConstraint);
 }
 
 // This method outsets 'iRect' by 'outset' all around and then clamps its extents to
@@ -1184,7 +1198,7 @@
                                   const SkRect& srcRect,
                                   const SkIRect& clippedSrcIRect,
                                   const GrTextureParams& params,
-                                  const SkPaint& paint,
+                                  const SkPaint& origPaint,
                                   SkCanvas::SrcRectConstraint constraint,
                                   int tileSize,
                                   bool bicubic) {
@@ -1193,6 +1207,15 @@
     // at each tile in cases where 'bitmap' holds an SkDiscardablePixelRef that
     // is larger than the limit of the discardable memory pool.
     SkAutoLockPixels alp(bitmap);
+
+    const SkPaint* paint = &origPaint;
+    SkPaint tempPaint;
+    if (origPaint.isAntiAlias() && !fRenderTarget->isUnifiedMultisampled()) {
+        // Drop antialiasing to avoid seams at tile boundaries.
+        tempPaint = origPaint;
+        tempPaint.setAntiAlias(false);
+        paint = &tempPaint;
+    }
     SkRect clippedSrcRect = SkRect::Make(clippedSrcIRect);
 
     int nx = bitmap.width() / tileSize;
@@ -1254,7 +1277,7 @@
                                          viewM,
                                          tileR,
                                          paramsTemp,
-                                         paint,
+                                         *paint,
                                          constraint,
                                          bicubic,
                                          needsTextureDomain);
@@ -1482,6 +1505,15 @@
 void SkGpuDevice::drawBitmapRect(const SkDraw& origDraw, const SkBitmap& bitmap,
                                  const SkRect* src, const SkRect& dst,
                                  const SkPaint& paint, SkCanvas::SrcRectConstraint constraint) {
+    if (GrTexture* tex = bitmap.getTexture()) {
+        CHECK_SHOULD_DRAW(origDraw);
+        bool alphaOnly = GrPixelConfigIsAlphaOnly(tex->config());
+        GrBitmapTextureAdjuster adjuster(&bitmap);
+        this->drawTextureAdjuster(&adjuster, alphaOnly, src, &dst, constraint, *origDraw.fMatrix,
+                                  fClip, paint);
+        return;
+    }
+
     SkMatrix    matrix;
     SkRect      bitmapBounds, tmpSrc;
 
@@ -1643,7 +1675,14 @@
                             const SkPaint& paint) {
     SkBitmap bm;
     if (GrTexture* tex = as_IB(image)->peekTexture()) {
-        GrWrapTextureInBitmap(tex, image->width(), image->height(), image->isOpaque(), &bm);
+        CHECK_SHOULD_DRAW(draw);
+        SkMatrix viewMatrix = *draw.fMatrix;
+        viewMatrix.preTranslate(x, y);
+        bool alphaOnly = GrPixelConfigIsAlphaOnly(tex->config());
+        GrImageTextureAdjuster adjuster(as_IB(image));
+        this->drawTextureAdjuster(&adjuster, alphaOnly, nullptr, nullptr,
+                                  SkCanvas::kFast_SrcRectConstraint, viewMatrix, fClip, paint);
+        return;
     } else {
         if (this->shouldTileImage(image, nullptr, SkCanvas::kFast_SrcRectConstraint,
                                   paint.getFilterQuality(), *draw.fMatrix)) {
@@ -1663,23 +1702,26 @@
 void SkGpuDevice::drawImageRect(const SkDraw& draw, const SkImage* image, const SkRect* src,
                                 const SkRect& dst, const SkPaint& paint,
                                 SkCanvas::SrcRectConstraint constraint) {
-    SkBitmap bm;
     if (GrTexture* tex = as_IB(image)->peekTexture()) {
-        GrWrapTextureInBitmap(tex, image->width(), image->height(), image->isOpaque(), &bm);
+        CHECK_SHOULD_DRAW(draw);
+        GrImageTextureAdjuster adjuster(as_IB(image));
+        bool alphaOnly = GrPixelConfigIsAlphaOnly(tex->config());
+        this->drawTextureAdjuster(&adjuster, alphaOnly, src, &dst, constraint, *draw.fMatrix,
+                                  fClip, paint);
+        return;
+    }
+    SkBitmap bm;
+    SkMatrix viewMatrix = *draw.fMatrix;
+    viewMatrix.preScale(dst.width() / (src ? src->width() : image->width()),
+                        dst.height() / (src ? src->height() : image->height()));
+    if (this->shouldTileImage(image, src, constraint, paint.getFilterQuality(), viewMatrix)) {
+        // only support tiling as bitmap at the moment, so force raster-version
+        if (!as_IB(image)->getROPixels(&bm)) {
+            return;
+        }
     } else {
-        SkMatrix viewMatrix = *draw.fMatrix;
-        viewMatrix.preScale(dst.width() / (src ? src->width() : image->width()),
-                            dst.height() / (src ? src->height() : image->height()));
-
-        if (this->shouldTileImage(image, src, constraint, paint.getFilterQuality(), viewMatrix)) {
-            // only support tiling as bitmap at the moment, so force raster-version
-            if (!as_IB(image)->getROPixels(&bm)) {
-                return;
-            }
-        } else {
-            if (!wrap_as_bm(this->context(), image, &bm)) {
-                return;
-            }
+        if (!wrap_as_bm(this->context(), image, &bm)) {
+            return;
         }
     }
     this->drawBitmapRect(draw, bm, src, dst, paint, constraint);
diff --git a/src/gpu/SkGpuDevice.h b/src/gpu/SkGpuDevice.h
index b2031e8..8816589 100644
--- a/src/gpu/SkGpuDevice.h
+++ b/src/gpu/SkGpuDevice.h
@@ -22,6 +22,7 @@
 struct GrSkDrawProcs;
 
 class GrAccelData;
+class GrTextureAdjuster;
 struct GrCachedLayer;
 
 /**
@@ -224,6 +225,7 @@
                             SkCanvas::SrcRectConstraint,
                             bool bicubic,
                             bool needsTextureDomain);
+
     void drawTiledBitmap(const SkBitmap& bitmap,
                          const SkMatrix& viewMatrix,
                          const SkRect& srcRect,
@@ -234,6 +236,25 @@
                          int tileSize,
                          bool bicubic);
 
+    void drawTextureAdjuster(GrTextureAdjuster* adjuster,
+                             bool alphaOnly,
+                             const SkRect* srcRect,
+                             const SkRect* dstRect,
+                             SkCanvas::SrcRectConstraint constraint,
+                             const SkMatrix& viewMatrix,
+                             const GrClip& clip,
+                             const SkPaint& paint);
+
+    void drawTextureAdjusterImpl(GrTextureAdjuster*,
+                                 bool alphaOnly,
+                                 const SkRect& clippedSrcRect,
+                                 const SkRect& clippedDstRect,
+                                 SkCanvas::SrcRectConstraint constraint,
+                                 const SkMatrix& viewMatrix,
+                                 const SkMatrix& srcToDstMatrix,
+                                 const GrClip& clip,
+                                 const SkPaint& paint);
+
     bool drawDashLine(const SkPoint pts[2], const SkPaint& paint);
 
     static GrRenderTarget* CreateRenderTarget(GrContext*, SkSurface::Budgeted, const SkImageInfo&,
diff --git a/src/gpu/SkGpuDevice_drawTexture.cpp b/src/gpu/SkGpuDevice_drawTexture.cpp
new file mode 100644
index 0000000..902aae0
--- /dev/null
+++ b/src/gpu/SkGpuDevice_drawTexture.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkGpuDevice.h"
+
+#include "GrBlurUtils.h"
+#include "GrCaps.h"
+#include "GrDrawContext.h"
+#include "GrStrokeInfo.h"
+#include "GrTextureParamsAdjuster.h"
+#include "SkDraw.h"
+#include "SkGrPriv.h"
+#include "SkMaskFilter.h"
+#include "effects/GrBicubicEffect.h"
+#include "effects/GrSimpleTextureEffect.h"
+#include "effects/GrTextureDomain.h"
+
+static inline bool use_shader(bool textureIsAlphaOnly, const SkPaint& paint) {
+    return textureIsAlphaOnly && paint.getShader();
+}
+
+/** Determines how to combine the texture FP with the paint's color and SkShader, if any. */
+static const GrFragmentProcessor* mix_texture_fp_with_paint_color_and_shader(
+                                                            const GrFragmentProcessor* textureFP,
+                                                            bool textureIsAlphaOnly,
+                                                            GrContext* context,
+                                                            const SkMatrix& viewMatrix,
+                                                            const SkPaint& paint) {
+    // According to the SkCanvas API, we only consider the shader if the bitmap or image being
+    // rendered is alpha-only.
+    if (textureIsAlphaOnly) {
+        if (const SkShader* shader = paint.getShader()) {
+            SkAutoTUnref<const GrFragmentProcessor> shaderFP(
+                shader->asFragmentProcessor(context,
+                                            viewMatrix,
+                                            nullptr,
+                                            paint.getFilterQuality()));
+            if (!shaderFP) {
+                return nullptr;
+            }
+            const GrFragmentProcessor* fpSeries[] = { shaderFP, textureFP };
+            return GrFragmentProcessor::RunInSeries(fpSeries, 2);
+        } else {
+            return GrFragmentProcessor::MulOutputByInputUnpremulColor(textureFP);
+        }
+    } else {
+        return GrFragmentProcessor::MulOutputByInputAlpha(textureFP);
+    }
+}
+
+void SkGpuDevice::drawTextureAdjuster(GrTextureAdjuster* adjuster,
+                                      bool alphaOnly,
+                                      const SkRect* srcRect,
+                                      const SkRect* dstRect,
+                                      SkCanvas::SrcRectConstraint constraint,
+                                      const SkMatrix& viewMatrix,
+                                      const GrClip& clip,
+                                      const SkPaint& paint) {
+    // Figure out the actual dst and src rect by clipping the src rect to the bounds of the
+    // adjuster. If the src rect is clipped then the dst rect must be recomputed. Also determine
+    // the matrix that maps the src rect to the dst rect.
+    SkRect clippedSrcRect;
+    SkRect clippedDstRect;
+    SkIRect contentIBounds;
+    adjuster->getContentArea(&contentIBounds);
+    const SkRect contentBounds = SkRect::Make(contentIBounds);
+    SkMatrix srcToDstMatrix;
+    if (srcRect) {
+        if (!dstRect) {
+            dstRect = &contentBounds;
+        }
+        if (!contentBounds.contains(*srcRect)) {
+            clippedSrcRect = *srcRect;
+            if (!clippedSrcRect.intersect(contentBounds)) {
+                return;
+            }
+            if (!srcToDstMatrix.setRectToRect(*srcRect, *dstRect, SkMatrix::kFill_ScaleToFit)) {
+                return;
+            }
+            srcToDstMatrix.mapRect(&clippedDstRect, clippedSrcRect);
+        } else {
+            clippedSrcRect = *srcRect;
+            clippedDstRect = *dstRect;
+            if (!srcToDstMatrix.setRectToRect(*srcRect, *dstRect, SkMatrix::kFill_ScaleToFit)) {
+                return;
+            }
+        }
+    } else {
+        clippedSrcRect = contentBounds;
+        if (dstRect) {
+            clippedDstRect = *dstRect;
+            if (!srcToDstMatrix.setRectToRect(contentBounds, *dstRect,
+                                              SkMatrix::kFill_ScaleToFit)) {
+                return;
+            }
+        } else {
+            clippedDstRect = contentBounds;
+            srcToDstMatrix.reset();
+        }
+    }
+
+    this->drawTextureAdjusterImpl(adjuster, alphaOnly, clippedSrcRect, clippedDstRect, constraint,
+                                  viewMatrix, srcToDstMatrix, clip, paint);
+}
+
+void SkGpuDevice::drawTextureAdjusterImpl(GrTextureAdjuster* adjuster,
+                                          bool alphaTexture,
+                                          const SkRect& clippedSrcRect,
+                                          const SkRect& clippedDstRect,
+                                          SkCanvas::SrcRectConstraint constraint,
+                                          const SkMatrix& viewMatrix,
+                                          const SkMatrix& srcToDstMatrix,
+                                          const GrClip& clip,
+                                          const SkPaint& paint) {
+    // Specifying the texture coords as local coordinates is an attempt to enable more batching
+    // by not baking anything about the srcRect, dstRect, or viewMatrix, into the texture FP. In
+    // the future this should be an opaque optimization enabled by the combination of batch/GP and
+    // FP.
+    const SkMatrix* textureFPMatrix;
+    SkMatrix tempMatrix;
+    const SkMaskFilter* mf = paint.getMaskFilter();
+    GrTexture* texture = adjuster->originalTexture();
+    // The shader expects proper local coords, so we can't replace local coords with texture coords
+    // if the shader will be used. If we have a mask filter we will change the underlying geometry
+    // that is rendered.
+    bool canUseTextureCoordsAsLocalCoords = !use_shader(alphaTexture, paint) && !mf;
+    if (canUseTextureCoordsAsLocalCoords) {
+        textureFPMatrix = &SkMatrix::I();
+    } else {
+        if (!srcToDstMatrix.invert(&tempMatrix)) {
+            return;
+        }
+        tempMatrix.postIDiv(texture->width(), texture->height());
+        textureFPMatrix = &tempMatrix;
+    }
+
+    bool doBicubic;
+    GrTextureParams::FilterMode fm =
+        GrSkFilterQualityToGrFilterMode(paint.getFilterQuality(), viewMatrix, srcToDstMatrix,
+                                        &doBicubic);
+    const GrTextureParams::FilterMode* filterMode = doBicubic ? nullptr : &fm;
+
+    GrTextureAdjuster::FilterConstraint constraintMode;
+    if (SkCanvas::kFast_SrcRectConstraint == constraint) {
+        constraintMode = GrTextureAdjuster::kNo_FilterConstraint;
+    } else {
+        constraintMode = GrTextureAdjuster::kYes_FilterConstraint;
+    }
+    
+    // If we have to outset for AA then we will generate texture coords outside the src rect. The
+    // same happens for any mask filter that extends the bounds rendered in the dst.
+    // This is conservative as a mask filter does not have to expand the bounds rendered.
+    bool coordsAllInsideSrcRect = !paint.isAntiAlias() && !mf;
+
+    SkAutoTUnref<const GrFragmentProcessor> fp(adjuster->createFragmentProcessor(
+        *textureFPMatrix, clippedSrcRect, constraintMode, coordsAllInsideSrcRect, filterMode));
+    if (!fp) {
+        return;
+    }
+    fp.reset(mix_texture_fp_with_paint_color_and_shader(fp, alphaTexture, this->context(),
+                                                        viewMatrix, paint));
+    GrPaint grPaint;
+    if (!SkPaintToGrPaintReplaceShader(fContext, paint, fp, &grPaint)) {
+        return;
+    }
+
+    if (canUseTextureCoordsAsLocalCoords) {
+        SkRect localRect;
+        localRect.fLeft = clippedSrcRect.fLeft / texture->width();
+        localRect.fBottom = clippedSrcRect.fBottom / texture->height();
+        localRect.fRight = clippedSrcRect.fRight / texture->width();
+        localRect.fTop = clippedSrcRect.fTop / texture->height();
+        fDrawContext->fillRectToRect(clip, grPaint, viewMatrix, clippedDstRect, localRect);
+        return;
+    }
+
+    if (!mf) {
+        fDrawContext->drawRect(clip, grPaint, viewMatrix, clippedDstRect);
+        return;
+    }
+
+    // First see if we can do the draw + mask filter direct to the dst.
+    SkStrokeRec rec(SkStrokeRec::kFill_InitStyle);
+    SkRRect rrect;
+    rrect.setRect(clippedDstRect);
+    if (mf->directFilterRRectMaskGPU(fContext->textureProvider(),
+                                      fDrawContext,
+                                      &grPaint,
+                                      clip,
+                                      viewMatrix,
+                                      rec,
+                                      rrect)) {
+        return;
+    }
+    SkPath rectPath;
+    rectPath.addRect(clippedDstRect);
+    GrBlurUtils::drawPathWithMaskFilter(this->context(), fDrawContext, fRenderTarget, fClip,
+                                        rectPath, &grPaint, viewMatrix, mf, paint.getPathEffect(),
+                                        GrStrokeInfo::FillInfo());
+}
diff --git a/src/gpu/SkGr.cpp b/src/gpu/SkGr.cpp
index 469ef5b..540edb6 100644
--- a/src/gpu/SkGr.cpp
+++ b/src/gpu/SkGr.cpp
@@ -12,6 +12,7 @@
 #include "GrContext.h"
 #include "GrTextureParamsAdjuster.h"
 #include "GrGpuResourcePriv.h"
+#include "GrImageIDTextureAdjuster.h"
 #include "GrXferProcessor.h"
 #include "GrYUVProvider.h"
 
@@ -271,7 +272,7 @@
 
 ////////////////////////////////////////////////////////////////////////////////
 
-static void install_bmp_key_invalidator(const GrUniqueKey& key, SkPixelRef* pixelRef) {
+void GrInstallBitmapUniqueKeyInvalidator(const GrUniqueKey& key, SkPixelRef* pixelRef) {
     class Invalidator : public SkPixelRef::GenIDChangeListener {
     public:
         explicit Invalidator(const GrUniqueKey& key) : fMsg(key) {}
@@ -313,7 +314,7 @@
         tex = GrUploadBitmapToTexture(ctx, fBitmap);
         if (tex && fOriginalKey.isValid()) {
             tex->resourcePriv().setUniqueKey(fOriginalKey);
-            install_bmp_key_invalidator(fOriginalKey, fBitmap.pixelRef());
+            GrInstallBitmapUniqueKeyInvalidator(fOriginalKey, fBitmap.pixelRef());
         }
         return tex;
     }
@@ -325,7 +326,7 @@
     }
 
     void didCacheCopy(const GrUniqueKey& copyKey) override {
-        install_bmp_key_invalidator(copyKey, fBitmap.pixelRef());
+        GrInstallBitmapUniqueKeyInvalidator(copyKey, fBitmap.pixelRef());
     }
 
 private:
@@ -335,40 +336,10 @@
     typedef GrTextureMaker INHERITED;
 };
 
-class TextureBitmap_GrTextureAdjuster : public GrTextureAdjuster {
-public:
-    explicit TextureBitmap_GrTextureAdjuster(const SkBitmap* bmp)
-        : INHERITED(bmp->getTexture(), SkIRect::MakeWH(bmp->width(), bmp->height()))
-        , fBmp(bmp) {}
-
-private:
-    void makeCopyKey(const CopyParams& params, GrUniqueKey* copyKey) override {
-        if (fBmp->isVolatile()) {
-            return;
-        }
-        // The content area must represent the whole bitmap. Texture-backed bitmaps don't support
-        // extractSubset(). Therefore, either the bitmap and the texture are the same size or the
-        // content's dimensions are the bitmap's dimensions which is pinned to the upper left
-        // of the texture.
-        GrUniqueKey baseKey;
-        GrMakeKeyFromImageID(&baseKey, fBmp->getGenerationID(),
-                             SkIRect::MakeWH(fBmp->width(), fBmp->height()));
-        MakeCopyKeyFromOrigKey(baseKey, params, copyKey);
-    }
-
-    void didCacheCopy(const GrUniqueKey& copyKey) override {
-        install_bmp_key_invalidator(copyKey, fBmp->pixelRef());
-    }
-
-    const SkBitmap* fBmp;
-
-    typedef GrTextureAdjuster INHERITED;
-};
-
 GrTexture* GrRefCachedBitmapTexture(GrContext* ctx, const SkBitmap& bitmap,
                                     const GrTextureParams& params) {
     if (bitmap.getTexture()) {
-        return TextureBitmap_GrTextureAdjuster(&bitmap).refTextureSafeForParams(params, nullptr);
+        return GrBitmapTextureAdjuster(&bitmap).refTextureSafeForParams(params, nullptr);
     }
     return RasterBitmap_GrTextureMaker(bitmap).refTextureForParams(ctx, params);
 }
diff --git a/src/gpu/SkGrPriv.h b/src/gpu/SkGrPriv.h
index 5935309..5924ea6 100644
--- a/src/gpu/SkGrPriv.h
+++ b/src/gpu/SkGrPriv.h
@@ -39,6 +39,10 @@
  */
 void GrMakeKeyFromImageID(GrUniqueKey* key, uint32_t imageID, const SkIRect& imageBounds);
 
+/** Call this after installing a GrUniqueKey on texture. It will cause the texture's key to be
+    removed should the bitmap's contents change or be destroyed. */
+void GrInstallBitmapUniqueKeyInvalidator(const GrUniqueKey& key, SkPixelRef* pixelRef);
+
 /** Converts an SkPaint to a GrPaint for a given GrContext. The matrix is required in order
     to convert the SkShader (if any) on the SkPaint. The primitive itself has no color. */
 bool SkPaintToGrPaint(GrContext*,
diff --git a/src/gpu/batches/GrAAFillRectBatch.cpp b/src/gpu/batches/GrAAFillRectBatch.cpp
index 5b22422..9646107 100644
--- a/src/gpu/batches/GrAAFillRectBatch.cpp
+++ b/src/gpu/batches/GrAAFillRectBatch.cpp
@@ -332,6 +332,28 @@
     return batch;
 }
 
+GrDrawBatch* Create(GrColor color,
+                    const SkMatrix& viewMatrix,
+                    const SkMatrix& localMatrix,
+                    const SkRect& rect) {
+    SkRect devRect;
+    viewMatrix.mapRect(&devRect, rect);
+    return Create(color, viewMatrix, localMatrix, rect, devRect);
+}
+
+GrDrawBatch* CreateWithLocalRect(GrColor color,
+                                 const SkMatrix& viewMatrix,
+                                 const SkRect& rect,
+                                 const SkRect& localRect) {
+    SkRect devRect;
+    viewMatrix.mapRect(&devRect, rect);
+    SkMatrix localMatrix;
+    if (!localMatrix.setRectToRect(rect, localRect, SkMatrix::kFill_ScaleToFit)) {
+        return nullptr;
+    }
+    return Create(color, viewMatrix, localMatrix, rect, devRect);
+}
+
 void Append(GrBatch* origBatch,
             GrColor color,
             const SkMatrix& viewMatrix,
diff --git a/src/gpu/batches/GrAAFillRectBatch.h b/src/gpu/batches/GrAAFillRectBatch.h
index fcbae59..6f6f0d0 100644
--- a/src/gpu/batches/GrAAFillRectBatch.h
+++ b/src/gpu/batches/GrAAFillRectBatch.h
@@ -24,9 +24,19 @@
 GrDrawBatch* Create(GrColor color,
                     const SkMatrix& viewMatrix,
                     const SkMatrix& localMatrix,
+                    const SkRect& rect);
+
+GrDrawBatch* Create(GrColor color,
+                    const SkMatrix& viewMatrix,
+                    const SkMatrix& localMatrix,
                     const SkRect& rect,
                     const SkRect& devRect);
 
+GrDrawBatch* CreateWithLocalRect(GrColor color,
+                                 const SkMatrix& viewMatrix,
+                                 const SkRect& rect,
+                                 const SkRect& localRect);
+
 void Append(GrBatch*,
             GrColor,
             const SkMatrix& viewMatrix,
diff --git a/src/gpu/effects/GrBicubicEffect.h b/src/gpu/effects/GrBicubicEffect.h
index 9f9e20d..5a54cc1 100644
--- a/src/gpu/effects/GrBicubicEffect.h
+++ b/src/gpu/effects/GrBicubicEffect.h
@@ -49,7 +49,7 @@
      * Create a Mitchell filter effect with specified texture matrix and x/y tile modes.
      */
     static const GrFragmentProcessor* Create(GrTexture* tex, const SkMatrix& matrix,
-                                             SkShader::TileMode tileModes[2]) {
+                                             const SkShader::TileMode tileModes[2]) {
         return Create(tex, gMitchellCoefficients, matrix, tileModes);
     }