1/*
2 * Copyright 2011, The Android Open Source Project
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *  * Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 *  * Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#define LOG_TAG "TileGrid"
27#define LOG_NDEBUG 1
28
29#include "config.h"
30#include "TileGrid.h"
31
32#include "AndroidLog.h"
33#include "DrawQuadData.h"
34#include "GLWebViewState.h"
35#include "PaintTileOperation.h"
36#include "Tile.h"
37#include "TileTexture.h"
38#include "TilesManager.h"
39
40#include <wtf/CurrentTime.h>
41
42#define EXPANDED_BOUNDS_INFLATE 1
43#define EXPANDED_PREFETCH_BOUNDS_Y_INFLATE 1
44
45namespace WebCore {
46
47TileGrid::TileGrid(bool isBaseSurface)
48    : m_prevTileY(0)
49    , m_scale(1)
50    , m_isBaseSurface(isBaseSurface)
51{
52    m_dirtyRegion.setEmpty();
53#ifdef DEBUG_COUNT
54    ClassTracker::instance()->increment("TileGrid");
55#endif
56}
57
58TileGrid::~TileGrid()
59{
60#ifdef DEBUG_COUNT
61    ClassTracker::instance()->decrement("TileGrid");
62#endif
63    removeTiles();
64}
65
66bool TileGrid::isReady()
67{
68    bool tilesAllReady = true;
69    bool tilesVisible = false;
70    for (unsigned int i = 0; i < m_tiles.size(); i++) {
71        Tile* tile = m_tiles[i];
72        if (tile->isTileVisible(m_area)) {
73            tilesVisible = true;
74            if (!tile->isTileReady()) {
75                tilesAllReady = false;
76                break;
77            }
78        }
79    }
80    // For now, if no textures are available, consider ourselves as ready
81    // in order to unblock the zooming process.
82    // FIXME: have a better system -- maybe keeping the last scale factor
83    // able to fully render everything
84    ALOGV("TG %p, ready %d, visible %d, texturesRemain %d",
85          this, tilesAllReady, tilesVisible,
86          TilesManager::instance()->layerTexturesRemain());
87
88    return !TilesManager::instance()->layerTexturesRemain()
89            || !tilesVisible || tilesAllReady;
90}
91
92bool TileGrid::isMissingContent()
93{
94    for (unsigned int i = 0; i < m_tiles.size(); i++)
95        if (m_tiles[i]->isTileVisible(m_area) && !m_tiles[i]->frontTexture())
96            return true;
97    return false;
98}
99
100bool TileGrid::swapTiles()
101{
102    int swaps = 0;
103    for (unsigned int i = 0; i < m_tiles.size(); i++)
104        if (m_tiles[i]->swapTexturesIfNeeded())
105            swaps++;
106    ALOGV("TG %p swapping, swaps = %d", this, swaps);
107    return swaps != 0;
108}
109
110IntRect TileGrid::computeTilesArea(const IntRect& contentArea, float scale)
111{
112    IntRect computedArea;
113    IntRect area(contentArea.x() * scale,
114                 contentArea.y() * scale,
115                 ceilf(contentArea.width() * scale),
116                 ceilf(contentArea.height() * scale));
117
118    ALOGV("TG prepare, scale %f, area %d x %d", scale, area.width(), area.height());
119
120    if (area.width() == 0 && area.height() == 0) {
121        computedArea.setWidth(0);
122        computedArea.setHeight(0);
123        return computedArea;
124    }
125
126    int tileWidth = TilesManager::tileWidth();
127    int tileHeight = TilesManager::tileHeight();
128
129    computedArea.setX(area.x() / tileWidth);
130    computedArea.setY(area.y() / tileHeight);
131    float right = (area.x() + area.width()) / (float) tileWidth;
132    float bottom = (area.y() + area.height()) / (float) tileHeight;
133    computedArea.setWidth(ceilf(right) - computedArea.x());
134    computedArea.setHeight(ceilf(bottom) - computedArea.y());
135    return computedArea;
136}
137
138void TileGrid::prepareGL(GLWebViewState* state, float scale,
139                         const IntRect& prepareArea, const IntRect& fullContentArea,
140                         TilePainter* painter, int regionFlags, bool isLowResPrefetch,
141                         bool updateWithBlit)
142{
143    // first, how many tiles do we need
144    m_area = computeTilesArea(prepareArea, scale);
145    if (m_area.isEmpty())
146        return;
147
148    ALOGV("prepare TileGrid %p with scale %.2f, prepareArea "
149          " %d, %d - %d x %d, corresponding to %d, %d x - %d x %d tiles",
150          this, scale,
151          prepareArea.x(), prepareArea.y(),
152          prepareArea.width(), prepareArea.height(),
153          m_area.x(), m_area.y(),
154          m_area.width(), m_area.height());
155
156    bool goingDown = m_prevTileY < m_area.y();
157    m_prevTileY = m_area.y();
158
159    TilesManager* tilesManager = TilesManager::instance();
160    if (scale != m_scale)
161        tilesManager->removeOperationsForFilter(new ScaleFilter(painter, m_scale));
162
163    m_scale = scale;
164
165    // apply dirty region to affected tiles
166    if (!m_dirtyRegion.isEmpty()) {
167        for (unsigned int i = 0; i < m_tiles.size(); i++)
168            m_tiles[i]->markAsDirty(m_dirtyRegion);
169
170        // log inval region for the base surface
171        if (m_isBaseSurface && tilesManager->getProfiler()->enabled()) {
172            SkRegion::Iterator iterator(m_dirtyRegion);
173            while (!iterator.done()) {
174                SkIRect r = iterator.rect();
175                tilesManager->getProfiler()->nextInval(r, scale);
176                iterator.next();
177            }
178        }
179        m_dirtyRegion.setEmpty();
180    }
181
182    if (regionFlags & StandardRegion) {
183        for (int i = 0; i < m_area.width(); i++) {
184            if (goingDown) {
185                for (int j = 0; j < m_area.height(); j++)
186                    prepareTile(m_area.x() + i, m_area.y() + j,
187                                painter, state, isLowResPrefetch, false, updateWithBlit);
188            } else {
189                for (int j = m_area.height() - 1; j >= 0; j--)
190                    prepareTile(m_area.x() + i, m_area.y() + j,
191                                painter, state, isLowResPrefetch, false, updateWithBlit);
192            }
193        }
194    }
195
196    if (regionFlags & ExpandedRegion) {
197        IntRect fullArea = computeTilesArea(fullContentArea, scale);
198        IntRect expandedArea = m_area;
199
200        // on systems reporting highEndGfx=true and useMinimalMemory not set, use expanded bounds
201        if (tilesManager->highEndGfx() && !tilesManager->useMinimalMemory())
202            expandedArea.inflate(EXPANDED_BOUNDS_INFLATE);
203
204        if (isLowResPrefetch)
205            expandedArea.inflateY(EXPANDED_PREFETCH_BOUNDS_Y_INFLATE);
206
207        // clip painting area to content
208        expandedArea.intersect(fullArea);
209
210        for (int i = expandedArea.x(); i < expandedArea.maxX(); i++)
211            for (int j = expandedArea.y(); j < expandedArea.maxY(); j++)
212                if (!m_area.contains(i, j))
213                    prepareTile(i, j, painter, state, isLowResPrefetch, true, updateWithBlit);
214    }
215}
216
217void TileGrid::markAsDirty(const SkRegion& invalRegion)
218{
219    ALOGV("TG %p markAsDirty, current region empty %d, new empty %d",
220          this, m_dirtyRegion.isEmpty(), invalRegion.isEmpty());
221    m_dirtyRegion.op(invalRegion, SkRegion::kUnion_Op);
222}
223
224void TileGrid::prepareTile(int x, int y, TilePainter* painter,
225                           GLWebViewState* state, bool isLowResPrefetch,
226                           bool isExpandPrefetch, bool shouldTryUpdateWithBlit)
227{
228    Tile* tile = getTile(x, y);
229    if (!tile) {
230        bool isLayerTile = !m_isBaseSurface;
231        tile = new Tile(isLayerTile);
232        m_tiles.append(tile);
233    }
234
235    ALOGV("preparing tile %p at %d, %d, painter is %p", tile, x, y, painter);
236
237    tile->setContents(x, y, m_scale, isExpandPrefetch);
238
239    if (shouldTryUpdateWithBlit && tryBlitFromContents(tile, painter))
240        return;
241
242    if (tile->isDirty() || !tile->frontTexture())
243        tile->reserveTexture();
244
245    if (tile->backTexture() && tile->isDirty()) {
246        TilesManager* tilesManager = TilesManager::instance();
247
248        // if a scheduled repaint is still outstanding, update it with the new painter
249        if (tile->isRepaintPending() && tilesManager->tryUpdateOperationWithPainter(tile, painter))
250            return;
251
252        ALOGV("painting TG %p's tile %d %d for LG %p, scale %f", this, x, y, painter, m_scale);
253        PaintTileOperation *operation = new PaintTileOperation(tile, painter,
254                                                               state, isLowResPrefetch);
255        tilesManager->scheduleOperation(operation);
256    }
257}
258
259bool TileGrid::tryBlitFromContents(Tile* tile, TilePainter* painter)
260{
261    return tile->frontTexture()
262           && !tile->frontTexture()->isPureColor()
263           && tile->frontTexture()->m_ownTextureId
264           && !tile->isRepaintPending()
265           && painter->blitFromContents(tile);
266}
267
268Tile* TileGrid::getTile(int x, int y)
269{
270    for (unsigned int i = 0; i <m_tiles.size(); i++) {
271        Tile* tile = m_tiles[i];
272        if (tile->x() == x && tile->y() == y)
273            return tile;
274    }
275    return 0;
276}
277
278unsigned int TileGrid::getImageTextureId()
279{
280    if (m_tiles.size() == 1) {
281        if (m_tiles[0]->frontTexture())
282            return m_tiles[0]->frontTexture()->m_ownTextureId;
283    }
284    return 0;
285}
286
287int TileGrid::nbTextures(const IntRect& area, float scale)
288{
289    IntRect tileBounds = computeTilesArea(area, scale);
290    int numberTextures = tileBounds.width() * tileBounds.height();
291
292    // add the number of dirty tiles in the bounds, as they take up double
293    // textures for double buffering
294    for (unsigned int i = 0; i <m_tiles.size(); i++) {
295        Tile* tile = m_tiles[i];
296        if (tile->isDirty()
297                && tile->x() >= tileBounds.x() && tile->x() <= tileBounds.maxX()
298                && tile->y() >= tileBounds.y() && tile->y() <= tileBounds.maxY())
299            numberTextures++;
300    }
301    return numberTextures;
302}
303
304void TileGrid::drawGL(const IntRect& visibleContentArea, float opacity,
305                      const TransformationMatrix* transform,
306                      const Color* background)
307{
308    m_area = computeTilesArea(visibleContentArea, m_scale);
309    if (m_area.width() == 0 || m_area.height() == 0)
310        return;
311
312    float invScale = 1.0 / m_scale;
313    const float tileWidth = TilesManager::tileWidth() * invScale;
314    const float tileHeight = TilesManager::tileHeight() * invScale;
315
316    int drawn = 0;
317
318    SkRegion missingRegion;
319    bool semiOpaqueBaseSurface =
320        background ? (background->hasAlpha() && background->alpha() > 0) : false;
321    if (semiOpaqueBaseSurface) {
322        SkIRect totalArea = SkIRect::MakeXYWH(m_area.x(), m_area.y(),
323                                              m_area.width(), m_area.height());
324        missingRegion = SkRegion(totalArea);
325    }
326
327    bool usePointSampling =
328        TilesManager::instance()->shader()->usePointSampling(m_scale, transform);
329
330    float minTileX =  visibleContentArea.x() / tileWidth;
331    float minTileY =  visibleContentArea.y() / tileWidth;
332    float maxTileWidth = visibleContentArea.maxX() / tileWidth;
333    float maxTileHeight = visibleContentArea.maxY() / tileWidth;
334    ALOGV("minTileX, minTileY, maxTileWidth, maxTileHeight %f, %f, %f %f",
335          minTileX, minTileY, maxTileWidth, maxTileHeight);
336    for (unsigned int i = 0; i < m_tiles.size(); i++) {
337        Tile* tile = m_tiles[i];
338
339        bool tileInView = tile->isTileVisible(m_area);
340        if (tileInView) {
341            SkRect rect;
342            rect.fLeft = tile->x() * tileWidth;
343            rect.fTop = tile->y() * tileHeight;
344            rect.fRight = rect.fLeft + tileWidth;
345            rect.fBottom = rect.fTop + tileHeight;
346            ALOGV("tile %p (layer tile: %d) %d,%d at scale %.2f vs %.2f [ready: %d] dirty: %d",
347                  tile, tile->isLayerTile(), tile->x(), tile->y(),
348                  tile->scale(), m_scale, tile->isTileReady(), tile->isDirty());
349
350            bool forceBaseBlending = background ? background->hasAlpha() : false;
351
352            float left = std::max(minTileX - tile->x(), 0.0f);
353            float top = std::max(minTileY - tile->y(), 0.0f);
354            float right = std::min(maxTileWidth - tile->x(), 1.0f);
355            float bottom = std::min(maxTileHeight - tile->y(), 1.0f);
356            if (left > 1.0f || top > 1.0f || right < 0.0f || bottom < 0.0f) {
357                ALOGE("Unexpected portion:left, top, right, bottom %f %f %f %f",
358                      left, top, right, bottom);
359                left = 0.0f;
360                top = 0.0f;
361                right = 1.0f;
362                bottom = 1.0f;
363            }
364            FloatRect fillPortion(left, top, right - left, bottom - top);
365
366            bool success = tile->drawGL(opacity, rect, m_scale, transform,
367                                        forceBaseBlending, usePointSampling, fillPortion);
368            if (semiOpaqueBaseSurface && success) {
369                // Cut the successful drawn tile area from the missing region.
370                missingRegion.op(SkIRect::MakeXYWH(tile->x(), tile->y(), 1, 1),
371                                 SkRegion::kDifference_Op);
372            }
373            if (tile->frontTexture())
374                drawn++;
375        }
376
377        // log tile information for base, high res tiles
378        if (m_isBaseSurface && background)
379            TilesManager::instance()->getProfiler()->nextTile(tile, invScale, tileInView);
380    }
381
382    // Draw missing Regions with blend turned on
383    if (semiOpaqueBaseSurface)
384        drawMissingRegion(missingRegion, opacity, background);
385
386    ALOGV("TG %p drew %d tiles, scale %f",
387          this, drawn, m_scale);
388}
389
390void TileGrid::drawMissingRegion(const SkRegion& region, float opacity,
391                                     const Color* background)
392{
393    SkRegion::Iterator iterator(region);
394    const float tileWidth = TilesManager::tileWidth() / m_scale;
395    const float tileHeight = TilesManager::tileHeight() / m_scale;
396    while (!iterator.done()) {
397        SkIRect r = iterator.rect();
398        SkRect rect;
399        rect.fLeft = r.x() * tileWidth;
400        rect.fTop =  r.y() * tileHeight;
401        rect.fRight = rect.fLeft + tileWidth * r.width();
402        rect.fBottom = rect.fTop + tileHeight * r.height();
403        ALOGV("draw tile x y, %d %d (%d %d) opacity %f", r.x(), r.y(),
404              r.width(), r.height(), opacity);
405        // Skia is using pre-multiplied color.
406        Color postAlpha = Color(background->red() * background->alpha() / 255,
407                                background->green() * background->alpha() / 255,
408                                background->blue() * background->alpha() / 255,
409                                background->alpha() );
410
411        PureColorQuadData backGroundData(postAlpha, BaseQuad, 0, &rect, opacity);
412        TilesManager::instance()->shader()->drawQuad(&backGroundData);
413        iterator.next();
414    }
415}
416
417void TileGrid::removeTiles()
418{
419    for (unsigned int i = 0; i < m_tiles.size(); i++) {
420        delete m_tiles[i];
421    }
422    m_tiles.clear();
423}
424
425void TileGrid::discardTextures()
426{
427    ALOGV("TG %p discarding textures", this);
428    for (unsigned int i = 0; i < m_tiles.size(); i++)
429        m_tiles[i]->discardTextures();
430}
431
432} // namespace WebCore
433