1 /*
2  Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
3 
4  This library is free software; you can redistribute it and/or
5  modify it under the terms of the GNU Library General Public
6  License as published by the Free Software Foundation; either
7  version 2 of the License, or (at your option) any later version.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB.  If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  Boston, MA 02110-1301, USA.
18  */
19 
20 #include "config.h"
21 #include "TiledBackingStore.h"
22 
23 #if ENABLE(TILED_BACKING_STORE)
24 
25 #include "GraphicsContext.h"
26 #include "TiledBackingStoreClient.h"
27 
28 namespace WebCore {
29 
30 static const int defaultTileWidth = 512;
31 static const int defaultTileHeight = 512;
32 
innerBottomRight(const IntRect & rect)33 static IntPoint innerBottomRight(const IntRect& rect)
34 {
35     // Actually, the rect does not contain rect.maxX(). Refer to IntRect::contain.
36     return IntPoint(rect.maxX() - 1, rect.maxY() - 1);
37 }
38 
39 
TiledBackingStore(TiledBackingStoreClient * client)40 TiledBackingStore::TiledBackingStore(TiledBackingStoreClient* client)
41     : m_client(client)
42     , m_tileBufferUpdateTimer(new TileTimer(this, &TiledBackingStore::tileBufferUpdateTimerFired))
43     , m_tileCreationTimer(new TileTimer(this, &TiledBackingStore::tileCreationTimerFired))
44     , m_tileSize(defaultTileWidth, defaultTileHeight)
45     , m_tileCreationDelay(0.01)
46     , m_keepAreaMultiplier(2.f, 3.5f)
47     , m_coverAreaMultiplier(1.5f, 2.5f)
48     , m_contentsScale(1.f)
49     , m_pendingScale(0)
50     , m_contentsFrozen(false)
51 {
52 }
53 
~TiledBackingStore()54 TiledBackingStore::~TiledBackingStore()
55 {
56     delete m_tileBufferUpdateTimer;
57     delete m_tileCreationTimer;
58 }
59 
setTileSize(const IntSize & size)60 void TiledBackingStore::setTileSize(const IntSize& size)
61 {
62     m_tileSize = size;
63     m_tiles.clear();
64     startTileCreationTimer();
65 }
66 
setTileCreationDelay(double delay)67 void TiledBackingStore::setTileCreationDelay(double delay)
68 {
69     m_tileCreationDelay = delay;
70 }
71 
setKeepAndCoverAreaMultipliers(const FloatSize & keepMultiplier,const FloatSize & coverMultiplier)72 void TiledBackingStore::setKeepAndCoverAreaMultipliers(const FloatSize& keepMultiplier, const FloatSize& coverMultiplier)
73 {
74     m_keepAreaMultiplier = keepMultiplier;
75     m_coverAreaMultiplier = coverMultiplier;
76     startTileCreationTimer();
77 }
78 
invalidate(const IntRect & contentsDirtyRect)79 void TiledBackingStore::invalidate(const IntRect& contentsDirtyRect)
80 {
81     IntRect dirtyRect(mapFromContents(contentsDirtyRect));
82 
83     Tile::Coordinate topLeft = tileCoordinateForPoint(dirtyRect.location());
84     Tile::Coordinate bottomRight = tileCoordinateForPoint(innerBottomRight(dirtyRect));
85 
86     for (unsigned yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) {
87         for (unsigned xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) {
88             RefPtr<Tile> currentTile = tileAt(Tile::Coordinate(xCoordinate, yCoordinate));
89             if (!currentTile)
90                 continue;
91             currentTile->invalidate(dirtyRect);
92         }
93     }
94 
95     startTileBufferUpdateTimer();
96 }
97 
updateTileBuffers()98 void TiledBackingStore::updateTileBuffers()
99 {
100     if (m_contentsFrozen)
101         return;
102 
103     m_client->tiledBackingStorePaintBegin();
104 
105     Vector<IntRect> paintedArea;
106     Vector<RefPtr<Tile> > dirtyTiles;
107     TileMap::iterator end = m_tiles.end();
108     for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) {
109         if (!it->second->isDirty())
110             continue;
111         dirtyTiles.append(it->second);
112     }
113 
114     if (dirtyTiles.isEmpty()) {
115         m_client->tiledBackingStorePaintEnd(paintedArea);
116         return;
117     }
118 
119     // FIXME: In single threaded case, tile back buffers could be updated asynchronously
120     // one by one and then swapped to front in one go. This would minimize the time spent
121     // blocking on tile updates.
122     unsigned size = dirtyTiles.size();
123     for (unsigned n = 0; n < size; ++n) {
124         Vector<IntRect> paintedRects = dirtyTiles[n]->updateBackBuffer();
125         paintedArea.append(paintedRects);
126         dirtyTiles[n]->swapBackBufferToFront();
127     }
128 
129     m_client->tiledBackingStorePaintEnd(paintedArea);
130 }
131 
paint(GraphicsContext * context,const IntRect & rect)132 void TiledBackingStore::paint(GraphicsContext* context, const IntRect& rect)
133 {
134     context->save();
135 
136     // Assumes the backing store is painted with the scale transform applied.
137     // Since tile content is already scaled, first revert the scaling from the painter.
138     context->scale(FloatSize(1.f / m_contentsScale, 1.f / m_contentsScale));
139 
140     IntRect dirtyRect = mapFromContents(rect);
141 
142     Tile::Coordinate topLeft = tileCoordinateForPoint(dirtyRect.location());
143     Tile::Coordinate bottomRight = tileCoordinateForPoint(innerBottomRight(dirtyRect));
144 
145     for (unsigned yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) {
146         for (unsigned xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) {
147             Tile::Coordinate currentCoordinate(xCoordinate, yCoordinate);
148             RefPtr<Tile> currentTile = tileAt(currentCoordinate);
149             if (currentTile && currentTile->isReadyToPaint())
150                 currentTile->paint(context, dirtyRect);
151             else {
152                 IntRect tileRect = tileRectForCoordinate(currentCoordinate);
153                 IntRect target = intersection(tileRect, dirtyRect);
154                 if (target.isEmpty())
155                     continue;
156                 Tile::paintCheckerPattern(context, FloatRect(target));
157             }
158         }
159     }
160     context->restore();
161 }
162 
adjustVisibleRect()163 void TiledBackingStore::adjustVisibleRect()
164 {
165     IntRect visibleRect = mapFromContents(m_client->tiledBackingStoreVisibleRect());
166     if (m_previousVisibleRect == visibleRect)
167         return;
168     m_previousVisibleRect = visibleRect;
169 
170     startTileCreationTimer();
171 }
172 
setContentsScale(float scale)173 void TiledBackingStore::setContentsScale(float scale)
174 {
175     if (m_pendingScale == m_contentsScale) {
176         m_pendingScale = 0;
177         return;
178     }
179     m_pendingScale = scale;
180     if (m_contentsFrozen)
181         return;
182     commitScaleChange();
183 }
184 
commitScaleChange()185 void TiledBackingStore::commitScaleChange()
186 {
187     m_contentsScale = m_pendingScale;
188     m_pendingScale = 0;
189     m_tiles.clear();
190     createTiles();
191 }
192 
tileDistance(const IntRect & viewport,const Tile::Coordinate & tileCoordinate)193 double TiledBackingStore::tileDistance(const IntRect& viewport, const Tile::Coordinate& tileCoordinate)
194 {
195     if (viewport.intersects(tileRectForCoordinate(tileCoordinate)))
196         return 0;
197 
198     IntPoint viewCenter = viewport.location() + IntSize(viewport.width() / 2, viewport.height() / 2);
199     Tile::Coordinate centerCoordinate = tileCoordinateForPoint(viewCenter);
200 
201     // Manhattan distance, biased so that vertical distances are shorter.
202     const double horizontalBias = 1.3;
203     return abs(centerCoordinate.y() - tileCoordinate.y()) + horizontalBias * abs(centerCoordinate.x() - tileCoordinate.x());
204 }
205 
createTiles()206 void TiledBackingStore::createTiles()
207 {
208     if (m_contentsFrozen)
209         return;
210 
211     IntRect visibleRect = mapFromContents(m_client->tiledBackingStoreVisibleRect());
212     m_previousVisibleRect = visibleRect;
213 
214     if (visibleRect.isEmpty())
215         return;
216 
217     // Remove tiles that extend outside the current contents rect.
218     dropOverhangingTiles();
219 
220     IntRect keepRect = visibleRect;
221     // Inflates to both sides, so divide inflate delta by 2
222     keepRect.inflateX(visibleRect.width() * (m_keepAreaMultiplier.width() - 1.f) / 2);
223     keepRect.inflateY(visibleRect.height() * (m_keepAreaMultiplier.height() - 1.f) / 2);
224     keepRect.intersect(contentsRect());
225 
226     dropTilesOutsideRect(keepRect);
227 
228     IntRect coverRect = visibleRect;
229     // Inflates to both sides, so divide inflate delta by 2
230     coverRect.inflateX(visibleRect.width() * (m_coverAreaMultiplier.width() - 1.f) / 2);
231     coverRect.inflateY(visibleRect.height() * (m_coverAreaMultiplier.height() - 1.f) / 2);
232     coverRect.intersect(contentsRect());
233 
234     // Search for the tile position closest to the viewport center that does not yet contain a tile.
235     // Which position is considered the closest depends on the tileDistance function.
236     double shortestDistance = std::numeric_limits<double>::infinity();
237     Vector<Tile::Coordinate> tilesToCreate;
238     unsigned requiredTileCount = 0;
239     Tile::Coordinate topLeft = tileCoordinateForPoint(coverRect.location());
240     Tile::Coordinate bottomRight = tileCoordinateForPoint(innerBottomRight(coverRect));
241     for (unsigned yCoordinate = topLeft.y(); yCoordinate <= bottomRight.y(); ++yCoordinate) {
242         for (unsigned xCoordinate = topLeft.x(); xCoordinate <= bottomRight.x(); ++xCoordinate) {
243             Tile::Coordinate currentCoordinate(xCoordinate, yCoordinate);
244             if (tileAt(currentCoordinate))
245                 continue;
246             ++requiredTileCount;
247             // Distance is 0 for all currently visible tiles.
248             double distance = tileDistance(visibleRect, currentCoordinate);
249             if (distance > shortestDistance)
250                 continue;
251             if (distance < shortestDistance) {
252                 tilesToCreate.clear();
253                 shortestDistance = distance;
254             }
255             tilesToCreate.append(currentCoordinate);
256         }
257     }
258 
259     // Now construct the tile(s)
260     unsigned tilesToCreateCount = tilesToCreate.size();
261     for (unsigned n = 0; n < tilesToCreateCount; ++n) {
262         Tile::Coordinate coordinate = tilesToCreate[n];
263         setTile(coordinate, Tile::create(this, coordinate));
264     }
265     requiredTileCount -= tilesToCreateCount;
266 
267     // Paint the content of the newly created tiles
268     if (tilesToCreateCount)
269         updateTileBuffers();
270 
271     // Keep creating tiles until the whole coverRect is covered.
272     if (requiredTileCount)
273         m_tileCreationTimer->startOneShot(m_tileCreationDelay);
274 }
275 
dropOverhangingTiles()276 void TiledBackingStore::dropOverhangingTiles()
277 {
278     IntRect contentsRect = this->contentsRect();
279 
280     Vector<Tile::Coordinate> tilesToRemove;
281     TileMap::iterator end = m_tiles.end();
282     for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) {
283         Tile::Coordinate tileCoordinate = it->second->coordinate();
284         IntRect tileRect = it->second->rect();
285         IntRect expectedTileRect = tileRectForCoordinate(tileCoordinate);
286         if (expectedTileRect != tileRect || !contentsRect.contains(tileRect))
287             tilesToRemove.append(tileCoordinate);
288     }
289     unsigned removeCount = tilesToRemove.size();
290     for (unsigned n = 0; n < removeCount; ++n)
291         removeTile(tilesToRemove[n]);
292 }
293 
dropTilesOutsideRect(const IntRect & keepRect)294 void TiledBackingStore::dropTilesOutsideRect(const IntRect& keepRect)
295 {
296     FloatRect keepRectF = keepRect;
297 
298     Vector<Tile::Coordinate> toRemove;
299     TileMap::iterator end = m_tiles.end();
300     for (TileMap::iterator it = m_tiles.begin(); it != end; ++it) {
301         Tile::Coordinate coordinate = it->second->coordinate();
302         FloatRect tileRect = it->second->rect();
303         if (!tileRect.intersects(keepRectF))
304             toRemove.append(coordinate);
305     }
306     unsigned removeCount = toRemove.size();
307     for (unsigned n = 0; n < removeCount; ++n)
308         removeTile(toRemove[n]);
309 }
310 
tileAt(const Tile::Coordinate & coordinate) const311 PassRefPtr<Tile> TiledBackingStore::tileAt(const Tile::Coordinate& coordinate) const
312 {
313     return m_tiles.get(coordinate);
314 }
315 
setTile(const Tile::Coordinate & coordinate,PassRefPtr<Tile> tile)316 void TiledBackingStore::setTile(const Tile::Coordinate& coordinate, PassRefPtr<Tile> tile)
317 {
318     m_tiles.set(coordinate, tile);
319 }
320 
removeTile(const Tile::Coordinate & coordinate)321 void TiledBackingStore::removeTile(const Tile::Coordinate& coordinate)
322 {
323     m_tiles.remove(coordinate);
324 }
325 
mapToContents(const IntRect & rect) const326 IntRect TiledBackingStore::mapToContents(const IntRect& rect) const
327 {
328     return enclosingIntRect(FloatRect(rect.x() / m_contentsScale,
329         rect.y() / m_contentsScale,
330         rect.width() / m_contentsScale,
331         rect.height() / m_contentsScale));
332 }
333 
mapFromContents(const IntRect & rect) const334 IntRect TiledBackingStore::mapFromContents(const IntRect& rect) const
335 {
336     return enclosingIntRect(FloatRect(rect.x() * m_contentsScale,
337         rect.y() * m_contentsScale,
338         rect.width() * m_contentsScale,
339         rect.height() * m_contentsScale));
340 }
341 
contentsRect() const342 IntRect TiledBackingStore::contentsRect() const
343 {
344     return mapFromContents(m_client->tiledBackingStoreContentsRect());
345 }
346 
tileRectForCoordinate(const Tile::Coordinate & coordinate) const347 IntRect TiledBackingStore::tileRectForCoordinate(const Tile::Coordinate& coordinate) const
348 {
349     IntRect rect(coordinate.x() * m_tileSize.width(),
350         coordinate.y() * m_tileSize.height(),
351         m_tileSize.width(),
352         m_tileSize.height());
353 
354     rect.intersect(contentsRect());
355     return rect;
356 }
357 
tileCoordinateForPoint(const IntPoint & point) const358 Tile::Coordinate TiledBackingStore::tileCoordinateForPoint(const IntPoint& point) const
359 {
360     int x = point.x() / m_tileSize.width();
361     int y = point.y() / m_tileSize.height();
362     return Tile::Coordinate(std::max(x, 0), std::max(y, 0));
363 }
364 
365 
startTileBufferUpdateTimer()366 void TiledBackingStore::startTileBufferUpdateTimer()
367 {
368     if (m_tileBufferUpdateTimer->isActive() || m_contentsFrozen)
369         return;
370     m_tileBufferUpdateTimer->startOneShot(0);
371 }
372 
tileBufferUpdateTimerFired(TileTimer *)373 void TiledBackingStore::tileBufferUpdateTimerFired(TileTimer*)
374 {
375     updateTileBuffers();
376 }
377 
startTileCreationTimer()378 void TiledBackingStore::startTileCreationTimer()
379 {
380     if (m_tileCreationTimer->isActive() || m_contentsFrozen)
381         return;
382     m_tileCreationTimer->startOneShot(0);
383 }
384 
tileCreationTimerFired(TileTimer *)385 void TiledBackingStore::tileCreationTimerFired(TileTimer*)
386 {
387     createTiles();
388 }
389 
setContentsFrozen(bool freeze)390 void TiledBackingStore::setContentsFrozen(bool freeze)
391 {
392     if (m_contentsFrozen == freeze)
393         return;
394 
395     m_contentsFrozen = freeze;
396 
397     // Restart the timers. There might be pending invalidations that
398     // were not painted or created because tiles are not created or
399     // painted when in frozen state.
400     if (m_contentsFrozen)
401         return;
402     if (m_pendingScale)
403         commitScaleChange();
404     else {
405         startTileCreationTimer();
406         startTileBufferUpdateTimer();
407     }
408 }
409 
410 }
411 
412 #endif
413