2 * Copyright 2012 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "SkTileGrid.h"
10 SkTileGrid::SkTileGrid(int xTiles, int yTiles, const SkTileGridFactory::TileGridInfo& info)
15 , fTiles(SkNEW_ARRAY(SkTDArray<Entry>, xTiles * yTiles)) {
16 // Margin is offset by 1 as a provision for AA and
17 // to cancel-out the outset applied by getClipDeviceBounds.
18 fInfo.fMargin.fHeight++;
19 fInfo.fMargin.fWidth++;
22 SkTileGrid::~SkTileGrid() {
23 SkDELETE_ARRAY(fTiles);
26 void SkTileGrid::insert(void* data, const SkRect& fbounds, bool) {
27 SkASSERT(!fbounds.isEmpty());
28 SkIRect dilatedBounds;
29 if (fbounds.isLargest()) {
30 // Dilating the largest SkIRect will overflow. Other nearly-largest rects may overflow too,
31 // but we don't make active use of them like we do the largest.
32 dilatedBounds.setLargest();
34 fbounds.roundOut(&dilatedBounds);
35 dilatedBounds.outset(fInfo.fMargin.width(), fInfo.fMargin.height());
36 dilatedBounds.offset(fInfo.fOffset);
39 const SkIRect gridBounds =
40 { 0, 0, fInfo.fTileInterval.width() * fXTiles, fInfo.fTileInterval.height() * fYTiles };
41 if (!SkIRect::Intersects(dilatedBounds, gridBounds)) {
45 // Note: SkIRects are non-inclusive of the right() column and bottom() row,
46 // hence the "-1"s in the computations of maxX and maxY.
47 int minX = SkMax32(0, SkMin32(dilatedBounds.left() / fInfo.fTileInterval.width(), fXTiles - 1));
48 int minY = SkMax32(0, SkMin32(dilatedBounds.top() / fInfo.fTileInterval.height(), fYTiles - 1));
49 int maxX = SkMax32(0, SkMin32((dilatedBounds.right() - 1) / fInfo.fTileInterval.width(),
51 int maxY = SkMax32(0, SkMin32((dilatedBounds.bottom() - 1) / fInfo.fTileInterval.height(),
54 Entry entry = { fCount++, data };
55 for (int y = minY; y <= maxY; y++) {
56 for (int x = minX; x <= maxX; x++) {
57 fTiles[y * fXTiles + x].push(entry);
62 static int divide_ceil(int x, int y) {
63 return (x + y - 1) / y;
66 // Number of tiles for which data is allocated on the stack in
67 // SkTileGrid::search. If malloc becomes a bottleneck, we may consider
68 // increasing this number. Typical large web page, say 2k x 16k, would
69 // require 512 tiles of size 256 x 256 pixels.
70 static const int kStackAllocationTileCount = 1024;
72 void SkTileGrid::search(const SkRect& query, SkTDArray<void*>* results) const {
74 query.roundOut(&adjusted);
76 // The inset is to counteract the outset that was applied in 'insert'
77 // The outset/inset is to optimize for lookups of size
78 // 'tileInterval + 2 * margin' that are aligned with the tile grid.
79 adjusted.inset(fInfo.fMargin.width(), fInfo.fMargin.height());
80 adjusted.offset(fInfo.fOffset);
81 adjusted.sort(); // in case the inset inverted the rectangle
83 // Convert the query rectangle from device coordinates to tile coordinates
84 // by rounding outwards to the nearest tile boundary so that the resulting tile
85 // region includes the query rectangle.
86 int startX = adjusted.left() / fInfo.fTileInterval.width(),
87 startY = adjusted.top() / fInfo.fTileInterval.height();
88 int endX = divide_ceil(adjusted.right(), fInfo.fTileInterval.width()),
89 endY = divide_ceil(adjusted.bottom(), fInfo.fTileInterval.height());
91 // Logically, we could pin endX to [startX, fXTiles], but we force it
92 // up to (startX, fXTiles] to make sure we hit at least one tile.
93 // This snaps just-out-of-bounds queries to the neighboring border tile.
94 // I don't know if this is an important feature outside of unit tests.
95 startX = SkPin32(startX, 0, fXTiles - 1);
96 startY = SkPin32(startY, 0, fYTiles - 1);
97 endX = SkPin32(endX, startX + 1, fXTiles);
98 endY = SkPin32(endY, startY + 1, fYTiles);
100 const int tilesHit = (endX - startX) * (endY - startY);
101 SkASSERT(tilesHit > 0);
104 // A performance shortcut. The merging code below would work fine here too.
105 const SkTDArray<Entry>& tile = fTiles[startY * fXTiles + startX];
106 results->setCount(tile.count());
107 for (int i = 0; i < tile.count(); i++) {
108 (*results)[i] = tile[i].data;
113 // We've got to merge the data in many tiles into a single sorted and deduplicated stream.
114 // We do a simple k-way merge based on the order the data was inserted.
116 // Gather pointers to the starts and ends of the tiles to merge.
117 SkAutoSTArray<kStackAllocationTileCount, const Entry*> starts(tilesHit), ends(tilesHit);
119 for (int x = startX; x < endX; x++) {
120 for (int y = startY; y < endY; y++) {
121 starts[i] = fTiles[y * fXTiles + x].begin();
122 ends[i] = fTiles[y * fXTiles + x].end();
127 // Merge tiles into results until they're fully consumed.
130 // The tiles themselves are already ordered, so the earliest is at the front of some tile.
131 // It may be at the front of several, even all, tiles.
132 const Entry* earliest = NULL;
133 for (int i = 0; i < starts.count(); i++) {
134 if (starts[i] < ends[i]) {
135 if (NULL == earliest || starts[i]->order < earliest->order) {
136 earliest = starts[i];
141 // If we didn't find an earliest entry, there isn't anything left to merge.
142 if (NULL == earliest) {
146 // We did find an earliest entry. Output it, and step forward every tile that contains it.
147 results->push(earliest->data);
148 for (int i = 0; i < starts.count(); i++) {
149 if (starts[i] < ends[i] && starts[i]->order == earliest->order) {
156 void SkTileGrid::clear() {
157 for (int i = 0; i < fXTiles * fYTiles; i++) {
162 void SkTileGrid::rewindInserts() {
164 for (int i = 0; i < fXTiles * fYTiles; i++) {
165 while (!fTiles[i].isEmpty() && fClient->shouldRewind(fTiles[i].top().data)) {