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