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