Change SkTileGride geometry calculations to match the Chromium compositor.

This patch changes the semantics of tileWidth/Height to include the border region, and
uses an offset to take into account the fact that there is no outer border for outer
tiles. This patch also fixes a previous bug where the right column and bottom row were
considered to be included in bounds that are expressed as an SkIRect.

Companion Chromium CL required for roll: https://codereview.chromium.org/12221077/

TEST=TileGrid unit test
Review URL: https://codereview.appspot.com/7350050

git-svn-id: http://skia.googlecode.com/svn/trunk@7885 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/gmmain.cpp b/gm/gmmain.cpp
index 611b581..16fb7c7 100644
--- a/gm/gmmain.cpp
+++ b/gm/gmmain.cpp
@@ -806,7 +806,11 @@
         int height = SkScalarCeilToInt(SkScalarMul(SkIntToScalar(gm->getISize().height()), scale));
 
         if (kTileGrid_BbhType == bbhType) {
-            pict = new SkTileGridPicture(16, 16, width, height);
+            SkTileGridPicture::TileGridInfo info;
+            info.fMargin.setEmpty();
+            info.fOffset.setZero();
+            info.fTileInterval.set(16, 16);
+            pict = new SkTileGridPicture(width, height, info);
         } else {
             pict = new SkPicture;
         }
diff --git a/include/core/SkTileGridPicture.h b/include/core/SkTileGridPicture.h
index 28d545a..b28f3d3 100644
--- a/include/core/SkTileGridPicture.h
+++ b/include/core/SkTileGridPicture.h
@@ -9,6 +9,8 @@
 #define SkTileGridPicture_DEFINED
 
 #include "SkPicture.h"
+#include "SkPoint.h"
+#include "SkSize.h"
 
 /**
  * Subclass of SkPicture that override the behavior of the
@@ -20,23 +22,35 @@
  */
 class SK_API SkTileGridPicture : public SkPicture {
 public:
+    struct TileGridInfo {
+        /** Tile placement interval */
+        SkISize  fTileInterval;
+
+        /** Pixel coverage overlap between adjacent tiles */
+        SkISize  fMargin;
+
+        /** Offset added to device-space bounding box positions to convert
+          * them to tile-grid space. This can be used to adjust the "phase"
+          * of the tile grid to match probable query rectangles that will be
+          * used to search into the tile grid. As long as the offset is smaller
+          * or equal to the margin, there is no need to extend the domain of
+          * the tile grid to prevent data loss.
+          */
+        SkIPoint fOffset; 
+    };
     /**
      * Constructor
-     * @param tileWidth horizontal stride between consecutive tiles
-     * @param tileHeight vertical stride between consecutive tiles
      * @param width recording canvas width in device pixels
      * @param height recording canvas height in device pixels
-     * @param borderPixels pixels of overlap between adjacent tiles. Set this
-     *  value to match the border overlap that is applied to tiles by user
-     *  code. Properly setting this value will help improve performance
-     *  when performing tile-aligned playbacks with query regions that
-     *  match tile bounds outset by borderPixels pixels. Such outsets
-     *  are often used to prevent filtering artifacts at tile boundaries.
+     * @param info description of the tiling layout
      */
-    SkTileGridPicture(int tileWidth, int tileHeight, int width, int height, int borderPixels = 0);
+    SkTileGridPicture(int width, int height, const TileGridInfo& info);
+    
     virtual SkBBoxHierarchy* createBBoxHierarchy() const SK_OVERRIDE;
+
 private:
-    int fTileWidth, fTileHeight, fXTileCount, fYTileCount, fBorderPixels;
+    int fXTileCount, fYTileCount;
+    TileGridInfo fInfo;
 };
 
 #endif
diff --git a/src/core/SkTileGrid.cpp b/src/core/SkTileGrid.cpp
index 7e07851..8a29949 100644
--- a/src/core/SkTileGrid.cpp
+++ b/src/core/SkTileGrid.cpp
@@ -8,19 +8,20 @@
 
 #include "SkTileGrid.h"
 
-SkTileGrid::SkTileGrid(int tileWidth, int tileHeight, int xTileCount, int yTileCount,
-    int borderPixels, SkTileGridNextDatumFunctionPtr nextDatumFunction)
+SkTileGrid::SkTileGrid(int xTileCount, int yTileCount, const SkTileGridPicture::TileGridInfo& info,
+    SkTileGridNextDatumFunctionPtr nextDatumFunction)
 {
-    fTileWidth = tileWidth;
-    fTileHeight = tileHeight;
     fXTileCount = xTileCount;
     fYTileCount = yTileCount;
-    // Border padding is offset by 1 as a provision for AA and
+    fInfo = info;
+    // Margin is offset by 1 as a provision for AA and
     // to cancel-out the outset applied by getClipDeviceBounds.
-    fBorderPixels = borderPixels + 1;
+    fInfo.fMargin.fHeight++;
+    fInfo.fMargin.fWidth++;
     fTileCount = fXTileCount * fYTileCount;
     fInsertionCount = 0;
-    fGridBounds = SkIRect::MakeXYWH(0, 0, fTileWidth * fXTileCount, fTileHeight * fYTileCount);
+    fGridBounds = SkIRect::MakeXYWH(0, 0, fInfo.fTileInterval.width() * fXTileCount,
+        fInfo.fTileInterval.height() * fYTileCount);
     fNextDatumFunction = nextDatumFunction;
     fTileData = SkNEW_ARRAY(SkTDArray<void *>, fTileCount);
 }
@@ -36,16 +37,22 @@
 void SkTileGrid::insert(void* data, const SkIRect& bounds, bool) {
     SkASSERT(!bounds.isEmpty());
     SkIRect dilatedBounds = bounds;
-    dilatedBounds.outset(fBorderPixels, fBorderPixels);
-
+    dilatedBounds.outset(fInfo.fMargin.width(), fInfo.fMargin.height());
+    dilatedBounds.offset(fInfo.fOffset);
     if (!SkIRect::Intersects(dilatedBounds, fGridBounds)) {
         return;
     }
 
-    int minTileX = SkMax32(SkMin32(dilatedBounds.left() / fTileWidth, fXTileCount - 1), 0);
-    int maxTileX = SkMax32(SkMin32(dilatedBounds.right() / fTileWidth, fXTileCount - 1), 0);
-    int minTileY = SkMax32(SkMin32(dilatedBounds.top() / fTileHeight, fYTileCount -1), 0);
-    int maxTileY = SkMax32(SkMin32(dilatedBounds.bottom() / fTileHeight, fYTileCount -1), 0);
+    // Note: SkIRects are non-inclusive of the right() column and bottom() row,
+    // hence the "-1"s in the computations of maxTileX and maxTileY.
+    int minTileX = SkMax32(SkMin32(dilatedBounds.left() / fInfo.fTileInterval.width(), 
+        fXTileCount - 1), 0);
+    int maxTileX = SkMax32(SkMin32((dilatedBounds.right() - 1) / fInfo.fTileInterval.width(),
+        fXTileCount - 1), 0);
+    int minTileY = SkMax32(SkMin32(dilatedBounds.top() / fInfo.fTileInterval.height(),
+        fYTileCount -1), 0);
+    int maxTileY = SkMax32(SkMin32((dilatedBounds.bottom() -1) / fInfo.fTileInterval.height(),
+        fYTileCount -1), 0);
 
     for (int x = minTileX; x <= maxTileX; x++) {
         for (int y = minTileY; y <= maxTileY; y++) {
@@ -56,13 +63,18 @@
 }
 
 void SkTileGrid::search(const SkIRect& query, SkTDArray<void*>* results) {
+    SkIRect adjustedQuery = query;
+    adjustedQuery.inset(fInfo.fMargin.width(), fInfo.fMargin.height());
+    adjustedQuery.offset(fInfo.fOffset);
     // Convert the query rectangle from device coordinates to tile coordinates
     // by rounding outwards to the nearest tile boundary so that the resulting tile
     // region includes the query rectangle. (using truncating division to "floor")
-    int tileStartX = (query.left() + fBorderPixels) / fTileWidth;
-    int tileEndX = (query.right() + fTileWidth - fBorderPixels) / fTileWidth;
-    int tileStartY = (query.top() + fBorderPixels) / fTileHeight;
-    int tileEndY = (query.bottom() + fTileHeight - fBorderPixels) / fTileHeight;
+    int tileStartX = adjustedQuery.left() / fInfo.fTileInterval.width();
+    int tileEndX = (adjustedQuery.right() + fInfo.fTileInterval.width() - 1) /
+        fInfo.fTileInterval.width();
+    int tileStartY = adjustedQuery.top() / fInfo.fTileInterval.height();
+    int tileEndY = (adjustedQuery.bottom() + fInfo.fTileInterval.height() - 1) /
+        fInfo.fTileInterval.height();
     if (tileStartX >= fXTileCount || tileStartY >= fYTileCount || tileEndX <= 0 || tileEndY <= 0) {
         return; // query does not intersect the grid
     }
diff --git a/src/core/SkTileGrid.h b/src/core/SkTileGrid.h
index c0fd3dd..c83a0fd 100644
--- a/src/core/SkTileGrid.h
+++ b/src/core/SkTileGrid.h
@@ -11,6 +11,7 @@
 
 #include "SkBBoxHierarchy.h"
 #include "SkPictureStateTree.h"
+#include "SkTileGridPicture.h" // for TileGridInfo
 
 /**
  * Subclass of SkBBoxHierarchy that stores elements in buckets that correspond
@@ -26,7 +27,7 @@
 public:
     typedef void* (*SkTileGridNextDatumFunctionPtr)(SkTDArray<void*>** tileData, SkTDArray<int>& tileIndices);
 
-    SkTileGrid(int tileWidth, int tileHeight, int xTileCount, int yTileCount, int borderPixels,
+    SkTileGrid(int xTileCount, int yTileCount, const SkTileGridPicture::TileGridInfo& info,
         SkTileGridNextDatumFunctionPtr nextDatumFunction);
 
     virtual ~SkTileGrid();
@@ -61,7 +62,8 @@
 private:
     SkTDArray<void*>& tile(int x, int y);
 
-    int fTileWidth, fTileHeight, fXTileCount, fYTileCount, fTileCount, fBorderPixels;
+    int fXTileCount, fYTileCount, fTileCount;
+    SkTileGridPicture::TileGridInfo fInfo;
     SkTDArray<void*>* fTileData;
     int fInsertionCount;
     SkIRect fGridBounds;
diff --git a/src/core/SkTileGridPicture.cpp b/src/core/SkTileGridPicture.cpp
index 8a39d49..7a8d593 100644
--- a/src/core/SkTileGridPicture.cpp
+++ b/src/core/SkTileGridPicture.cpp
@@ -10,18 +10,19 @@
 #include "SkPictureStateTree.h"
 #include "SkTileGrid.h"
 
-
-SkTileGridPicture::SkTileGridPicture(int tileWidth, int tileHeight, int width, int height,
-                                     int borderPixels) {
-    SkASSERT(borderPixels >= 0);
-    fTileWidth = tileWidth;
-    fTileHeight = tileHeight;
-    fXTileCount = (width + tileWidth - 1) / tileWidth;
-    fYTileCount = (height + tileHeight - 1) / tileHeight;
-    fBorderPixels = borderPixels;
+SkTileGridPicture::SkTileGridPicture(int width, int height, const TileGridInfo& info) {
+    SkASSERT(info.fMargin.width() >= 0);
+    SkASSERT(info.fMargin.height() >= 0);
+    fInfo = info;
+    // Note: SkIRects are non-inclusive of the right() column and bottom() row.
+    // For example, an SkIRect at 0,0 with a size of (1,1) will only have
+    // content at pixel (0,0) and will report left=0 and right=1, hence the
+    // "-1"s below.
+    fXTileCount = (width + info.fTileInterval.width() - 1) / info.fTileInterval.width();
+    fYTileCount = (height + info.fTileInterval.height() - 1) / info.fTileInterval.height();
 }
 
 SkBBoxHierarchy* SkTileGridPicture::createBBoxHierarchy() const {
-    return SkNEW_ARGS(SkTileGrid, (fTileWidth, fTileHeight, fXTileCount, fYTileCount,
-        fBorderPixels, SkTileGridNextDatum<SkPictureStateTree::Draw>));
+    return SkNEW_ARGS(SkTileGrid, (fXTileCount, fYTileCount, fInfo,
+         SkTileGridNextDatum<SkPictureStateTree::Draw>));
 }
diff --git a/tests/TileGridTest.cpp b/tests/TileGridTest.cpp
index a72fd69..8ff629c 100644
--- a/tests/TileGridTest.cpp
+++ b/tests/TileGridTest.cpp
@@ -41,7 +41,11 @@
 public:
     static void verifyTileHits(skiatest::Reporter* reporter, SkIRect rect, uint32_t tileMask,
                                int borderPixels = 0) {
-        SkTileGrid grid(10, 10, 2, 2, borderPixels, NULL);
+        SkTileGridPicture::TileGridInfo info;
+        info.fMargin.set(borderPixels, borderPixels);
+        info.fOffset.setZero();
+        info.fTileInterval.set(10 - 2 * borderPixels, 10 - 2 * borderPixels);
+        SkTileGrid grid(2, 2, info, NULL);
         grid.insert(NULL, rect, false);
         REPORTER_ASSERT(reporter, grid.tile(0,0).count() ==
             ((tileMask & kTopLeft_Tile)? 1 : 0));
@@ -55,7 +59,11 @@
 
     static void TestUnalignedQuery(skiatest::Reporter* reporter) {
         // Use SkTileGridPicture to generate a SkTileGrid with a helper
-        SkTileGridPicture picture(10, 10, 20, 20);
+        SkTileGridPicture::TileGridInfo info;
+        info.fMargin.setEmpty();
+        info.fOffset.setZero();
+        info.fTileInterval.set(10, 10);
+        SkTileGridPicture picture(20, 20, info);
         SkRect rect1 = SkRect::MakeXYWH(SkIntToScalar(0), SkIntToScalar(0),
             SkIntToScalar(8), SkIntToScalar(8));
         SkRect rect2 = SkRect::MakeXYWH(SkIntToScalar(11), SkIntToScalar(11),
@@ -107,6 +115,86 @@
         }
     }
 
+    static void TestOverlapOffsetQueryAlignment(skiatest::Reporter* reporter) {
+        // Use SkTileGridPicture to generate a SkTileGrid with a helper
+        SkTileGridPicture::TileGridInfo info;
+        info.fMargin.set(1, 1);
+        info.fOffset.set(-1, -1);
+        info.fTileInterval.set(8, 8);
+        SkTileGridPicture picture(20, 20, info);
+
+        // rect landing entirely in top left tile
+        SkRect rect1 = SkRect::MakeXYWH(SkIntToScalar(0), SkIntToScalar(0),
+            SkIntToScalar(1), SkIntToScalar(1));
+        // rect landing entirely in center tile
+        SkRect rect2 = SkRect::MakeXYWH(SkIntToScalar(12), SkIntToScalar(12),
+            SkIntToScalar(1), SkIntToScalar(1));
+                // rect landing entirely in bottomright tile
+        SkRect rect3 = SkRect::MakeXYWH(SkIntToScalar(19), SkIntToScalar(19),
+            SkIntToScalar(1), SkIntToScalar(1));
+        SkCanvas* canvas = picture.beginRecording(20, 20, SkPicture::kOptimizeForClippedPlayback_RecordingFlag);
+        SkPaint paint;
+        canvas->drawRect(rect1, paint);
+        canvas->drawRect(rect2, paint);
+        canvas->drawRect(rect3, paint);
+        picture.endRecording();
+
+        SkBitmap tileBitmap;
+        tileBitmap.setConfig(SkBitmap::kARGB_8888_Config, 10, 10);
+        tileBitmap.allocPixels();
+        SkBitmap moreThanATileBitmap;
+        moreThanATileBitmap.setConfig(SkBitmap::kARGB_8888_Config, 11, 11);
+        moreThanATileBitmap.allocPixels();
+        // Test parts of top-left tile
+        {
+            // The offset should cancel the top and left borders of the top left tile
+            // So a look-up at interval 0-10 should be grid aligned,
+            SkDevice device(tileBitmap);
+            MockCanvas mockCanvas(&device);
+            picture.draw(&mockCanvas);
+            REPORTER_ASSERT(reporter, 1 == mockCanvas.fRects.count());
+            REPORTER_ASSERT(reporter, rect1 == mockCanvas.fRects[0]);
+        }
+        {
+            // Encroaching border by one pixel
+            SkDevice device(moreThanATileBitmap);
+            MockCanvas mockCanvas(&device);
+            picture.draw(&mockCanvas);
+            REPORTER_ASSERT(reporter, 2 == mockCanvas.fRects.count());
+            REPORTER_ASSERT(reporter, rect1 == mockCanvas.fRects[0]);
+            REPORTER_ASSERT(reporter, rect2 == mockCanvas.fRects[1]);
+        }
+        {
+            // Tile stride is 8 (tileWidth - 2 * border pixels
+            // so translating by 8, should make query grid-aligned
+            // with middle tile.
+            SkDevice device(tileBitmap);
+            MockCanvas mockCanvas(&device);
+            mockCanvas.translate(SkIntToScalar(-8), SkIntToScalar(-8));
+            picture.draw(&mockCanvas);
+            REPORTER_ASSERT(reporter, 1 == mockCanvas.fRects.count());
+            REPORTER_ASSERT(reporter, rect2 == mockCanvas.fRects[0]);
+        }
+        {
+            SkDevice device(tileBitmap);
+            MockCanvas mockCanvas(&device);
+            mockCanvas.translate(SkFloatToScalar(-7.9f), SkFloatToScalar(-7.9f));
+            picture.draw(&mockCanvas);
+            REPORTER_ASSERT(reporter, 2 == mockCanvas.fRects.count());
+            REPORTER_ASSERT(reporter, rect1 == mockCanvas.fRects[0]);
+            REPORTER_ASSERT(reporter, rect2 == mockCanvas.fRects[1]);
+        }
+        {
+            SkDevice device(tileBitmap);
+            MockCanvas mockCanvas(&device);
+            mockCanvas.translate(SkFloatToScalar(-8.1f), SkFloatToScalar(-8.1f));
+            picture.draw(&mockCanvas);
+            REPORTER_ASSERT(reporter, 2 == mockCanvas.fRects.count());
+            REPORTER_ASSERT(reporter, rect2 == mockCanvas.fRects[0]);
+            REPORTER_ASSERT(reporter, rect3 == mockCanvas.fRects[1]);
+        }
+    }
+
     static void Test(skiatest::Reporter* reporter) {
         // Out of bounds
         verifyTileHits(reporter, SkIRect::MakeXYWH(30, 0, 1, 1),  0);
@@ -115,16 +203,19 @@
         verifyTileHits(reporter, SkIRect::MakeXYWH(0, -10, 1, 1),  0);
 
         // Dilation for AA consideration
-        verifyTileHits(reporter, SkIRect::MakeXYWH(0, 0, 8, 8),  kTopLeft_Tile);
-        verifyTileHits(reporter, SkIRect::MakeXYWH(0, 0, 9, 9),  kAll_Tile);
+        verifyTileHits(reporter, SkIRect::MakeXYWH(0, 0, 9, 9),  kTopLeft_Tile);
+        verifyTileHits(reporter, SkIRect::MakeXYWH(0, 0, 10, 10),  kAll_Tile);
+        verifyTileHits(reporter, SkIRect::MakeXYWH(9, 9, 1, 1),  kAll_Tile);
         verifyTileHits(reporter, SkIRect::MakeXYWH(10, 10, 1, 1),  kAll_Tile);
         verifyTileHits(reporter, SkIRect::MakeXYWH(11, 11, 1, 1),  kBottomRight_Tile);
-
+        
         // BorderPixels
-        verifyTileHits(reporter, SkIRect::MakeXYWH(0, 0, 7, 7),  kTopLeft_Tile, 1);
-        verifyTileHits(reporter, SkIRect::MakeXYWH(0, 0, 8, 8),  kAll_Tile, 1);
-        verifyTileHits(reporter, SkIRect::MakeXYWH(11, 11, 1, 1),  kAll_Tile, 1);
-        verifyTileHits(reporter, SkIRect::MakeXYWH(12, 12, 1, 1),  kBottomRight_Tile, 1);
+        verifyTileHits(reporter, SkIRect::MakeXYWH(0, 0, 6, 6),  kTopLeft_Tile, 1);
+        verifyTileHits(reporter, SkIRect::MakeXYWH(0, 0, 7, 7),  kAll_Tile, 1);
+        verifyTileHits(reporter, SkIRect::MakeXYWH(9, 9, 1, 1),  kAll_Tile, 1);
+        verifyTileHits(reporter, SkIRect::MakeXYWH(10, 10, 1, 1),  kBottomRight_Tile, 1);
+        verifyTileHits(reporter, SkIRect::MakeXYWH(17, 17, 1, 1),  kBottomRight_Tile, 1);
+        verifyTileHits(reporter, SkIRect::MakeXYWH(18, 18, 1, 1),  0, 1);
 
         // BBoxes that overlap tiles
         verifyTileHits(reporter, SkIRect::MakeXYWH(5, 5, 10, 1),  kTopLeft_Tile | kTopRight_Tile);
@@ -134,6 +225,7 @@
         verifyTileHits(reporter, SkIRect::MakeXYWH(-10, -10, 40, 40),  kAll_Tile);
 
         TestUnalignedQuery(reporter);
+        TestOverlapOffsetQueryAlignment(reporter);
     }
 };
 
diff --git a/tools/PictureRenderer.cpp b/tools/PictureRenderer.cpp
index 16768c7..caab64a 100644
--- a/tools/PictureRenderer.cpp
+++ b/tools/PictureRenderer.cpp
@@ -42,6 +42,9 @@
 void PictureRenderer::init(SkPicture* pict) {
     SkASSERT(NULL == fPicture);
     SkASSERT(NULL == fCanvas.get());
+    fGridInfo.fMargin.setEmpty();
+    fGridInfo.fOffset.setZero();
+    fGridInfo.fTileInterval.set(1, 1);
     if (fPicture != NULL || NULL != fCanvas.get()) {
         return;
     }
@@ -800,8 +803,8 @@
         case kRTree_BBoxHierarchyType:
             return SkNEW(RTreePicture);
         case kTileGrid_BBoxHierarchyType:
-            return SkNEW_ARGS(SkTileGridPicture, (fGridWidth, fGridHeight, fPicture->width(),
-                fPicture->height()));
+            return SkNEW_ARGS(SkTileGridPicture, (fPicture->width(),
+                fPicture->height(), fGridInfo));
     }
     SkASSERT(0); // invalid bbhType
     return NULL;
diff --git a/tools/PictureRenderer.h b/tools/PictureRenderer.h
index b054a54..47fc7d7 100644
--- a/tools/PictureRenderer.h
+++ b/tools/PictureRenderer.h
@@ -19,6 +19,7 @@
 #include "SkString.h"
 #include "SkTDArray.h"
 #include "SkThreadPool.h"
+#include "SkTileGridPicture.h"
 #include "SkTypes.h"
 
 #if SK_SUPPORT_GPU
@@ -170,8 +171,7 @@
     }
 
     void setGridSize(int width, int height) {
-        fGridWidth = width;
-        fGridHeight = height;
+        fGridInfo.fTileInterval.set(width, height);
     }
 
     bool isUsingBitmapDevice() {
@@ -255,8 +255,6 @@
         : fPicture(NULL)
         , fDeviceType(kBitmap_DeviceType)
         , fBBoxHierarchyType(kNone_BBoxHierarchyType)
-        , fGridWidth(0)
-        , fGridHeight(0)
         , fScaleFactor(SK_Scalar1)
 #if SK_SUPPORT_GPU
         , fGrContext(NULL)
@@ -279,7 +277,7 @@
     BBoxHierarchyType      fBBoxHierarchyType;
     DrawFilterFlags        fDrawFilters[SkDrawFilter::kTypeCount];
     SkString               fDrawFiltersConfig;
-    int                    fGridWidth, fGridHeight; // used when fBBoxHierarchyType is TileGrid
+    SkTileGridPicture::TileGridInfo fGridInfo; // used when fBBoxHierarchyType is TileGrid
 
     void buildBBoxHierarchy();