TiledSurface.mm   [plain text]


/*
 *  TiledSurface.mm
 *  WebCore
 *
 *  Copyright (C) 2009, Apple Inc.  All rights reserved.
 *
 */

#include "config.h"
#include "TiledSurface.h"

#include "WebCoreTextRenderer.h"
#include "WKGraphics.h"
#include "WAKWindow.h"
#include "SystemMemory.h"
#include "SystemTime.h"
#include <CoreGraphics/CoreGraphics.h>
#include <CoreGraphics/CGSRegion.h> 
#include <QuartzCore/QuartzCore.h>
#include <QuartzCore/QuartzCorePrivate.h>
#include <wtf/UnusedParam.h>

#define LOG_TILING 0

@interface WAKView (WebViewExtras)
- (void)_dispatchTileDidDraw:(CALayer*)tile;
- (void)_willStartScrollingOrZooming;
- (void)_didFinishScrollingOrZooming;
@end

@interface TileLayer : CALayer
{
    WebCore::TiledSurface* _tiledSurface;
}
@end

@implementation TileLayer
- (id)initWithTiledSurface:(WebCore::TiledSurface*)tiledSurface
{
    self = [super init];
    if (self)
        _tiledSurface = tiledSurface;
    return self;
}

- (void)removeFromSuperlayer
{
    _tiledSurface = 0;
    [super removeFromSuperlayer];
}

- (void)setNeedsDisplayInRect:(CGRect)rect
{
    [super setNeedsDisplayInRect:rect];
}

- (void)display
{
    ASSERT(WebThreadIsLockedOrDisabled());
    // This may trigger WebKit layout and generate more repaint rects.
    if (_tiledSurface)
        _tiledSurface->prepareToDraw();
    
    [super display];
}

- (void)drawInContext:(CGContextRef)context
{
    if (_tiledSurface)
        _tiledSurface->drawLayer(self, context);
}

- (id<CAAction>)actionForKey:(NSString *)key
{
    UNUSED_PARAM(key);
    // Disable all default actions
    return nil;
}
@end

@interface WebThreadCaller : NSObject
{
    WebCore::TiledSurface* _tiledSurface;
    bool _hasPendingDoLayoutTiles;
    bool _hasPendingUpdateTilingMode;
}
@end

@implementation WebThreadCaller
- (id)initWithTiledSurface:(WebCore::TiledSurface*)tiledSurface
{
    self = [super init];
    if (self)
        _tiledSurface = tiledSurface;
    return self;
}

- (void)resetTiledSurface
{
    _tiledSurface = 0;
}

- (void)doLayoutTiles
{
    if (!WebThreadIsCurrent() && WebThreadIsEnabled()) {
        if (_hasPendingDoLayoutTiles)
            return;
        _hasPendingDoLayoutTiles = true;
        NSInvocation *invocation = WebThreadCreateNSInvocation(self, _cmd);
        WebThreadCallAPI(invocation);
        return;
    }
    _hasPendingDoLayoutTiles = false;
    if (_tiledSurface)
        _tiledSurface->doLayoutTiles();
}

- (void)updateTilingMode
{
    if (!WebThreadIsCurrent() && WebThreadIsEnabled()) {
        if (_hasPendingUpdateTilingMode)
            return;
        _hasPendingUpdateTilingMode = true;
        NSInvocation *invocation = WebThreadCreateNSInvocation(self, _cmd);
        WebThreadCallAPI(invocation);
        return;
    }
    _hasPendingUpdateTilingMode = false;
    if (_tiledSurface)
        _tiledSurface->updateTilingMode();
}
@end

namespace WebCore {

static bool canTileAggresively()
{
#if PLATFORM(IPHONE_SIMULATOR)
    return true;
#endif
    static bool canTileAggresively = systemTotalMemory() > 128 << 20;
    return canTileAggresively;
}

#if LOG_TILING
static int totalTileCount;
#endif

TiledSurface::Tile::Tile(TiledSurface* surface, const IntRect& tileRect)
    : m_surface(surface)
    , m_tileLayer(AdoptNS,[[TileLayer alloc] initWithTiledSurface:surface])
    , m_rect(tileRect)
{
    TileLayer* layer = m_tileLayer.get();
    [layer setMinificationFilter:surface->m_tileMinificationFilter];
    [layer setOpaque:m_surface->m_tilesOpaque];
    [layer setEdgeAntialiasingMask:0];
    [m_surface->m_hostCALayer.get() insertSublayer:layer atIndex:0];
    [layer setFrame:m_rect];
    invalidateRect(m_rect);
#if LOG_TILING
    ++totalTileCount;
    NSLog(@"new Tile (%d,%d) %d %d, count %d", tileRect.x(), tileRect.y(), tileRect.width(), tileRect.height(), totalTileCount);
#endif
}
    
TiledSurface::Tile::~Tile() 
{
    [tileLayer() removeFromSuperlayer];
#if LOG_TILING
    --totalTileCount;
    NSLog(@"delete Tile (%d,%d) %d %d, count %d", rect.x(), rect.y(), rect.width(), rect.height(), totalTileCount);
#endif
}

void TiledSurface::Tile::invalidateRect(const IntRect& rectInSurface)
{
    IntRect rect = intersection(rectInSurface, m_rect);
    if (rect.isEmpty())
        return;
    rect.move(IntPoint() - m_rect.topLeft());
    [tileLayer() setNeedsDisplayInRect:rect];
}
    
void TiledSurface::Tile::setRect(const IntRect& tileRect)
{
    if (m_rect == tileRect)
        return;
    m_rect = tileRect;
    TileLayer* layer = m_tileLayer.get();
    [layer setFrame:m_rect];
    [layer setNeedsDisplay];
}

TiledSurface::TiledSurface(CALayer* hostLayer, WAKWindow* window)
    : m_hostCALayer(hostLayer)
    , m_window(window)
    , m_webThreadCaller(AdoptNS, [[WebThreadCaller alloc] initWithTiledSurface:this])
    , m_tilingMode(Normal)
    , m_tileMinificationFilter(kCAFilterLinear)
    , m_tileSize(512, 512)
    , m_tilesOpaque(true)
    , m_didCallWillStartScrollingOrZooming(false)
{
}
    
TiledSurface::~TiledSurface()
{
    [m_webThreadCaller.get() resetTiledSurface];
}

IntSize TiledSurface::size() const
{
    return IntSize([m_hostCALayer.get() size]);
}
    
IntRect TiledSurface::frame() const
{
    return IntRect(IntPoint(), size());
}

bool TiledSurface::tilesOpaque() const
{
    return m_tilesOpaque;
}

void TiledSurface::setTilesOpaque(bool opaque)
{
    if (m_tilesOpaque == opaque)
        return;
    
    MutexLocker locker(m_tileMutex);
    
    m_tilesOpaque = opaque;
    
    unsigned ySize = m_tileGrid.size();
    for (unsigned yIndex = 0; yIndex < ySize; ++yIndex) {
        unsigned xSize =  m_tileGrid[yIndex].size();
        for (unsigned xIndex = 0; xIndex < xSize; ++xIndex) {
            Tile* tile = tileForIndex(xIndex, yIndex);
            if (tile)
                [tile->tileLayer() setOpaque:opaque];
        }
    }
}

TileMinificationFilter TiledSurface::tileMinificationFilter() const
{
    return m_tileMinificationFilter;
}

void TiledSurface::setTileMinificationFilter(TileMinificationFilter filter)
{
    if (m_tileMinificationFilter == filter)
        return;
    
    MutexLocker locker(m_tileMutex);
    
    m_tileMinificationFilter = filter;
    
    unsigned ySize = m_tileGrid.size();
    for (unsigned yIndex = 0; yIndex < ySize; ++yIndex) {
        unsigned xSize =  m_tileGrid[yIndex].size();
        for (unsigned xIndex = 0; xIndex < xSize; ++xIndex) {
            Tile* tile = tileForIndex(xIndex, yIndex);
            if (tile)
                [tile->tileLayer() setMinificationFilter:m_tileMinificationFilter];
        }
    }
}

IntRect TiledSurface::visibleRect() const
{
    CALayer* layer = m_hostCALayer.get();
    CGRect bounds = [layer bounds];
    CGRect rect = bounds;
    CALayer* superlayer = [layer superlayer];
    
    while (superlayer) {
        CGRect rectInSuper = [superlayer convertRect:rect fromLayer:layer];
        rect = CGRectIntersection([superlayer bounds], rectInSuper);
        layer = superlayer;
        superlayer = [layer superlayer];
    }
    
    if (layer != m_hostCALayer)
        rect = [m_hostCALayer.get() convertRect:rect fromLayer:layer];
    
    CGRect visibleRect = CGRectIntegral(rect);
    visibleRect = CGRectIntersection(bounds, visibleRect);
    return enclosingIntRect(visibleRect);
}
    
TiledSurface::Tile* TiledSurface::tileForIndex(unsigned xIndex, unsigned yIndex) const
{
    if (m_tileGrid.size() <= yIndex)
        return 0;
    const Vector<RefPtr<Tile> >& column = m_tileGrid[yIndex];
    if (column.size() <= xIndex)
        return 0;
    return column[xIndex].get();
}
    
IntRect TiledSurface::tileRectForIndex(unsigned xIndex, unsigned yIndex) const
{
    IntRect rect(xIndex * m_tileSize.width() - (m_tileGridOrigin.x() ? m_tileSize.width() - m_tileGridOrigin.x() : 0),
        yIndex * m_tileSize.height() - (m_tileGridOrigin.y() ? m_tileSize.height() - m_tileGridOrigin.y() : 0),
        m_tileSize.width(),
        m_tileSize.height());
    rect.intersect(IntRect(IntPoint(), size()));
    return rect;
}
    
void TiledSurface::tileIndexForPoint(const IntPoint& point, unsigned& xIndex, unsigned& yIndex) const
{
    ASSERT(m_tileGridOrigin.x() < m_tileSize.width());
    ASSERT(m_tileGridOrigin.y() < m_tileSize.height());
    int x = (point.x() + (m_tileGridOrigin.x() ? m_tileSize.width() - m_tileGridOrigin.x() : 0)) / m_tileSize.width();
    int y = (point.y() + (m_tileGridOrigin.y() ? m_tileSize.height() - m_tileGridOrigin.y() : 0)) / m_tileSize.height();
    xIndex = std::max(x, 0);
    yIndex = std::max(y, 0);
}
    
bool TiledSurface::pointsOnSameTile(const IntPoint& a, const IntPoint& b) const
{
    unsigned aXIndex;
    unsigned aYIndex;
    tileIndexForPoint(a, aXIndex, aYIndex);
    unsigned bXIndex;
    unsigned bYIndex;
    tileIndexForPoint(b, bXIndex, bYIndex);
    return aXIndex == bXIndex && aYIndex == bYIndex;
}

void TiledSurface::centerTileGridOrigin(const IntRect& visibleRect)
{
    if (visibleRect.width() > m_tileSize.width() || visibleRect.height() > m_tileSize.height())
        return;
    // Only center if all corners of the visible rect fall to different tiles.
    if (pointsOnSameTile(visibleRect.topLeft(), visibleRect.topRight()) || pointsOnSameTile(visibleRect.topLeft(), visibleRect.bottomLeft()))
        return;
    IntSize size = this->size();
    int coverX = size.width() > m_tileSize.width() ? visibleRect.x() - (m_tileSize.width() - visibleRect.width()) / 2 : 0;
    int coverY = size.height() > m_tileSize.height() ? visibleRect.y() - (m_tileSize.height() - visibleRect.height()) / 2 : 0;
    if (coverX < 0)
        coverX += m_tileSize.width();
    if (coverY < 0)
        coverY += m_tileSize.height();
    coverX %= m_tileSize.width();
    coverY %= m_tileSize.height();
    IntPoint origin(coverX, coverY);
    if (origin != m_tileGridOrigin) {
        coverWithTiles(IntRect(), IntRect());
        m_tileGridOrigin = origin;
        coverWithTiles(visibleRect, visibleRect);
    }
}
    
TiledSurface::Tile* TiledSurface::tileForPoint(const IntPoint& point) const
{
    unsigned xIndex;
    unsigned yIndex;
    tileIndexForPoint(point, xIndex, yIndex);
    return tileForIndex(xIndex, yIndex);
}

bool TiledSurface::tilesCover(const IntRect& rect) const
{
    return tileForPoint(rect.topLeft()) && tileForPoint(rect.topRight()) &&
        tileForPoint(rect.bottomLeft()) && tileForPoint(rect.bottomRight());
}

void TiledSurface::adjustForPageBounds(IntRect& rect) const
{
    // Adjust the rect so that it stays within the bounds and keeps the pixel size.
    IntRect bounds = frame();
    rect.move(rect.x() < bounds.x() ? bounds.x() - rect.x() : 0, 
              rect.y() < bounds.y() ? bounds.y() - rect.y() : 0);
    rect.move(rect.right() > bounds.right() ? bounds.right() - rect.right() : 0, 
              rect.bottom() > bounds.bottom() ? bounds.bottom() - rect.bottom() : 0);
    IntRect intersectRect = intersection(bounds, rect);
    if (intersectRect == rect || m_tilingMode == Minimal)
        return;
    if (intersectRect.isEmpty()) {
        rect = IntRect();
        return;
    }
    int pixels = rect.width() * rect.height();
    if (intersectRect.width() != rect.width())
        intersectRect.inflateY((pixels / intersectRect.width() - intersectRect.height()) / 2);
    else if (intersectRect.height() != rect.height())
        intersectRect.inflateX((pixels / intersectRect.height() - intersectRect.width()) / 2);
    rect = intersection(intersectRect, bounds);
}
    
bool TiledSurface::checkDoSingleTileLayout()
{
    IntSize size = this->size();
    if (size.width() > m_tileSize.width() || size.height() > m_tileSize.height())
        return false;
    
    IntRect frame = this->frame();
    if (m_tileGridOrigin != IntPoint(0, 0)) {
        coverWithTiles(IntRect(), IntRect());
        m_tileGridOrigin = IntPoint(0, 0);
    }
    coverWithTiles(frame, frame);
    return true;
}

void TiledSurface::calculateCoverAndKeepRectForMemoryLevel(const IntRect& visibleRect, IntRect& coverRect, IntRect& keepRect, bool& centerGrid)
{
    // Estimate how large area we want to cover with tiles based on the current memory level.
    int level = systemMemoryLevel();
    coverRect = visibleRect;
    centerGrid = false;
    if (level <= 10 || m_tilingMode == Minimal) {
        centerGrid = true;
        keepRect = coverRect;
    } else if (level <= 15) {
        centerGrid = true;
        keepRect = coverRect;
        keepRect.inflateY(m_tileSize.height() / 3);
    } else if (level <= 20) {
        centerGrid = true;
        coverRect.inflateY(visibleRect.height() / 3);
        keepRect = coverRect;
        keepRect.inflateX(m_tileSize.width() / 3);
        keepRect.inflateY(m_tileSize.height() / 3);
    } else if (level <= 25) {
        coverRect.inflateY(visibleRect.height() / 2);
        keepRect = coverRect;
        keepRect.inflateX(m_tileSize.width() / 3);
        keepRect.inflateY(m_tileSize.height() / 3);
    } else if (level <= 30) {
        coverRect.inflateX(visibleRect.width() / 3);
        coverRect.inflateY(visibleRect.height() / 2);
        keepRect = coverRect;
        keepRect.inflateX(m_tileSize.width() / 3);
        keepRect.inflateY(m_tileSize.height() / 3);
    } else {
        if (canTileAggresively()) {
            // For fast devices only
            coverRect.inflateX(visibleRect.width() / 2);
            coverRect.inflateY(visibleRect.height());
        } else {
            coverRect.inflateX(visibleRect.width() / 3);
            coverRect.inflateY(visibleRect.height() / 2);
        }
        keepRect = coverRect;
        keepRect.inflateX(m_tileSize.width() / 2);
        keepRect.inflateY(m_tileSize.height());
    }
    
    adjustForPageBounds(coverRect);
    adjustForPageBounds(keepRect);
}
    
void TiledSurface::doLayoutTiles()
{
    if (isTileCreationSuspended())
        return;
    MutexLocker locker(m_tileMutex);

    if (checkDoSingleTileLayout())
        return;
    
    IntRect visibleRect = this->visibleRect();
    if (visibleRect.isEmpty()) {
        // Visible rect may become temporarily empty in some cases. Just keep the existing tiles.
        coverWithTiles(IntRect(), frame());
        return;
    }
    
    IntRect targetCoverRect;
    IntRect keepRect;
    bool centerGrid;
    calculateCoverAndKeepRectForMemoryLevel(visibleRect, targetCoverRect, keepRect, centerGrid);

    IntRect coverRect;
    if (!tilesCover(visibleRect)) {
        // If the visible area is not covered try to fix that as fast as possible and add surrounding
        // tiles with another layout.
        coverRect = visibleRect;
    } else
        coverRect = targetCoverRect;
    
    if (centerGrid)
        centerTileGridOrigin(visibleRect);

    coverWithTiles(coverRect, keepRect);
}

void TiledSurface::layoutTiles()
{
    // If the view has shrunk, drop any unneeded tiles synchronously
    IntRect frame = this->frame();
    IntRect tiledArea = unionRect(m_previousCoverRect, m_previousKeepRect);
    if (tiledArea.bottom() > frame.bottom() || tiledArea.right() > frame.right()) {
        MutexLocker locker(m_tileMutex);
        dropTilesOutsideRect(frame);
    }

    // Forward the call to the web thread for asynchronous tile creation and painting
    [m_webThreadCaller.get() doLayoutTiles];
}
    
void TiledSurface::layoutTilesNow()
{
    ASSERT(WebThreadIsLockedOrDisabled());
    MutexLocker locker(m_tileMutex);
    
    if (checkDoSingleTileLayout())
        return;
    IntRect visibleRect = this->visibleRect();
    if (visibleRect.isEmpty())
        return;
    IntRect unusedRect;
    IntRect keepRect;
    bool unusedBool;
    calculateCoverAndKeepRectForMemoryLevel(visibleRect, unusedRect, keepRect, unusedBool);
    keepRect.intersect(frame());

    coverWithTiles(visibleRect, keepRect);
}

void TiledSurface::removeAllNonVisibleTiles()
{
    MutexLocker locker(m_tileMutex);
    
    IntSize size = this->size();
    if (size.width() <= m_tileSize.width() && size.height() <= m_tileSize.height()) {
        dropTilesOutsideRect(IntRect(IntPoint(), size));
        return;
    }

    dropTilesOutsideRect(visibleRect());
}

void TiledSurface::removeAllTiles()
{
    MutexLocker locker(m_tileMutex);
    coverWithTiles(IntRect(), IntRect());
}

void TiledSurface::shrinkToMinimalTiles()
{
    if (checkDoSingleTileLayout())
        return;
    IntRect visibleRect = this->visibleRect();
    centerTileGridOrigin(visibleRect);
    coverWithTiles(visibleRect, visibleRect);
}
    
void TiledSurface::coverWithTiles(const IntRect& coverRect, const IntRect& keepRect)
{
    // Tile mutex must be held when calling this
    ASSERT(!m_tileMutex.tryLock());
    
    IntRect rectToIterate = unionRect(coverRect, keepRect);
    rectToIterate.unite(m_previousCoverRect);
    rectToIterate.unite(m_previousKeepRect);
    
    unsigned minXIndex;
    unsigned minYIndex;
    tileIndexForPoint(IntPoint(rectToIterate.x(), rectToIterate.y()), minXIndex, minYIndex);
    
    unsigned maxXIndex;
    unsigned maxYIndex;
    tileIndexForPoint(IntPoint(rectToIterate.right(), rectToIterate.bottom()), maxXIndex, maxYIndex);

    bool tileAddedOrChanged = false;
    m_tileGrid.resize(maxYIndex + 1);
    for (unsigned yIndex = minYIndex; yIndex <= maxYIndex; ++yIndex) {
        m_tileGrid[yIndex].resize(maxXIndex + 1);
        for (unsigned xIndex = minXIndex; xIndex <= maxXIndex; ++xIndex) {
            IntRect tileRect = tileRectForIndex(xIndex, yIndex);
            RefPtr<Tile>& tile = m_tileGrid[yIndex][xIndex];
            if (tile && (!tileRect.intersects(keepRect) || tile->rect() != tileRect))
                tile = 0;
            if (!tile && tileRect.intersects(coverRect)) {
                tile = Tile::create(this, tileRect);
                tileAddedOrChanged = true;
            }
        }
    }

    m_previousCoverRect = coverRect;
    m_previousKeepRect = keepRect;
    
    // If we created a new tile we need to ensure that all tiles are showing the same version of the content.
    if (tileAddedOrChanged && !m_savedDisplayRects.isEmpty())
        flushSavedDisplayRects();
}

void TiledSurface::dropTilesOutsideRect(const IntRect& keepRect)
{
    IntRect rectToIterate = unionRect(m_previousCoverRect, m_previousKeepRect);
    
    unsigned minXIndex;
    unsigned minYIndex;
    tileIndexForPoint(rectToIterate.topLeft(), minXIndex, minYIndex);
    unsigned maxXIndex;
    unsigned maxYIndex;
    tileIndexForPoint(rectToIterate.bottomRight(), maxXIndex, maxYIndex);
    
    for (unsigned yIndex = minYIndex; yIndex <= maxYIndex; ++yIndex) {
        for (unsigned xIndex = minXIndex; xIndex <= maxXIndex; ++xIndex) {
            Tile* tile = tileForIndex(xIndex, yIndex);
            if (tile && !tile->rect().intersects(keepRect))
                m_tileGrid[yIndex][xIndex] = 0;
        }
    }
}

static bool shouldRepaintInPieces(const CGRect& dirtyRect, CGSRegionObj dirtyRegion)
{
    // Estimate whether or not we should use the unioned rect or the individual rects.
    // We do this by computing the percentage of "wasted space" in the union. If that wasted space
    // is too large, then we will do individual rect painting instead.
    float singlePixels = 0;
    unsigned rectCount = 0;
    
    CGSRegionEnumeratorObj enumerator = CGSRegionEnumerator(dirtyRegion);
    CGRect *subRect;
    while ((subRect = CGSNextRect(enumerator))) {
        ++rectCount;
        singlePixels += subRect->size.width * subRect->size.height;
    }
    CGSReleaseRegionEnumerator(enumerator);
    
    const unsigned cRectThreshold = 10;
    if (rectCount < 2 || rectCount > cRectThreshold)
        return false;
    
    const float cWastedSpaceThreshold = 0.50f;
    float unionPixels = dirtyRect.size.width * dirtyRect.size.height;
    float wastedSpace = 1.f - (singlePixels / unionPixels);
    return wastedSpace > cWastedSpaceThreshold;
}

void TiledSurface::drawLayer(CALayer* layer, CGContextRef context)
{
    // The web lock unlock observer runs after CA commit observer.
    if (!WebThreadIsLockedOrDisabled()) {
        LOG_ERROR("Drawing without holding the web thread lock");
        ASSERT_NOT_REACHED();
    }

    WKSetCurrentGraphicsContext(context);
    
    CGContextSetShouldAntialias(context, NO);
    
    WKFontAntialiasingStateSaver fontAntialiasingState([m_window useOrientationDependentFontAntialiasing]);
    fontAntialiasingState.setup([WAKWindow hasLandscapeOrientation]);
    
    CGRect dirtyRect = CGContextGetClipBoundingBox(context);
    CGRect frame = [layer frame];
    CGContextTranslateCTM(context, -frame.origin.x, -frame.origin.y);
    
    CGSRegionObj drawRegion = (CGSRegionObj)[layer regionBeingDrawn];
    if (drawRegion && shouldRepaintInPieces(dirtyRect, drawRegion)) {
        // Use fine grained repaint rectangles to minimize the amount of painted pixels.
        CGSRegionEnumeratorObj enumerator = CGSRegionEnumerator(drawRegion);
        CGRect *subRect;
        while ((subRect = CGSNextRect(enumerator))) {
            CGRect flippedSubRect = *subRect;
            flippedSubRect.origin.y = frame.size.height - flippedSubRect.origin.y - flippedSubRect.size.height;
            CGRect subRectInSuper = [m_hostCALayer.get() convertRect:flippedSubRect fromLayer:layer];
            WKWindowDrawRect([m_window _windowRef], subRectInSuper);
        }
        CGSReleaseRegionEnumerator(enumerator);
    } else {
        // Simple repaint
        CGRect dirtyRectInSuper = [m_hostCALayer.get() convertRect:dirtyRect fromLayer:layer];
        WKWindowDrawRect([m_window _windowRef], dirtyRectInSuper);
    }
    
    fontAntialiasingState.restore();

    WAKView* view = [m_window contentView];
    if ([view respondsToSelector:@selector(_dispatchTileDidDraw:)])
        [view _dispatchTileDidDraw:layer];
}

void TiledSurface::setNeedsDisplay()
{
    setNeedsDisplayInRect(IntRect(0, 0, std::numeric_limits<int>::max(), std::numeric_limits<int>::max()));
}

void TiledSurface::setNeedsDisplayInRect(const IntRect& dirtyRect)
{
    {
        MutexLocker locker(m_savedDisplayRectMutex);
        if (isPaintingSuspended() || !m_savedDisplayRects.isEmpty()) {
            bool addedFirstRect = m_savedDisplayRects.isEmpty();
            m_savedDisplayRects.append(dirtyRect);
            // Invalidate the host layer layout as a way of signaling that we have new content. It might
            // be better to have a specific callback but this is simpler.
            if (addedFirstRect)
                [m_hostCALayer.get() performSelectorOnMainThread:@selector(setNeedsLayout) withObject:nil waitUntilDone:NO];
            return;
        }
    }
    
    MutexLocker locker(m_tileMutex);
    invalidateTiles(dirtyRect);
}
    
void TiledSurface::invalidateTiles(const IntRect& dirtyRect)
{
    ASSERT(!m_tileMutex.tryLock());

    IntRect coveredRect = unionRect(m_previousCoverRect, m_previousKeepRect);
    IntRect rectToIterate = intersection(dirtyRect, coveredRect);
    
    unsigned minXIndex;
    unsigned minYIndex;
    tileIndexForPoint(rectToIterate.topLeft(), minXIndex, minYIndex);
    unsigned maxXIndex;
    unsigned maxYIndex;
    tileIndexForPoint(rectToIterate.bottomRight(), maxXIndex, maxYIndex);
    
    for (unsigned yIndex = minYIndex; yIndex <= maxYIndex; ++yIndex) {
        for (unsigned xIndex = minXIndex; xIndex <= maxXIndex; ++xIndex) {
            Tile* tile = tileForIndex(xIndex, yIndex);
            if (!tile)
                continue;
            tile->invalidateRect(dirtyRect);
        }
    }
}
    
bool TiledSurface::isTileCreationSuspended() const 
{ 
    return m_tilingMode == Zooming || m_tilingMode == Disabled;
}

bool TiledSurface::isPaintingSuspended() const 
{ 
    return m_tilingMode == Zooming || m_tilingMode == Panning || m_tilingMode == Disabled; 
}

TiledSurface::TilingMode TiledSurface::tilingMode() const
{
    return m_tilingMode;
}

void TiledSurface::updateTilingMode()
{
    ASSERT(WebThreadIsCurrent() || !WebThreadIsEnabled());

    WAKView* view = [m_window contentView];
    if (m_tilingMode == Zooming || m_tilingMode == Panning) {
        if (!m_didCallWillStartScrollingOrZooming && [view respondsToSelector:@selector(_willStartScrollingOrZooming)]) {
            [view _willStartScrollingOrZooming];
            m_didCallWillStartScrollingOrZooming = true;
        }
    } else {
        if (m_didCallWillStartScrollingOrZooming && [view respondsToSelector:@selector(_didFinishScrollingOrZooming)]) {
            [view _didFinishScrollingOrZooming];
            m_didCallWillStartScrollingOrZooming = false;
        }
        if (m_tilingMode == Disabled)
            return;
        layoutTiles();
        if (!m_savedDisplayRects.isEmpty()) {
            MutexLocker locker(m_tileMutex);
            flushSavedDisplayRects();
        }
    }
}

void TiledSurface::setTilingMode(TilingMode tilingMode)
{
    if (tilingMode == m_tilingMode)
        return;
    m_tilingMode = tilingMode;
    [m_webThreadCaller.get() updateTilingMode];
}

void TiledSurface::flushSavedDisplayRects()
{
    ASSERT(!m_tileMutex.tryLock());

    Vector<IntRect> rects;
    {
        MutexLocker locker(m_savedDisplayRectMutex);
        m_savedDisplayRects.swap(rects);
    }
    size_t size = rects.size();
    for (size_t n = 0; n < size; ++n)
        invalidateTiles(rects[n]);
}
    
bool TiledSurface::hasPendingDraw() const
{
    return !m_savedDisplayRects.isEmpty();
}

void TiledSurface::prepareToDraw()
{
    // This will trigger document relayout if needed.
    [[m_window contentView] viewWillDraw];

    if (!m_savedDisplayRects.isEmpty()) {
        MutexLocker locker(m_tileMutex);
        flushSavedDisplayRects();
    }
}

}