RenderQueue.cpp   [plain text]


/*
 * Copyright (C) 2009, 2010, 2011, 2012 Research In Motion Limited. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "config.h"
#include "RenderQueue.h"

#include "BackingStore_p.h"
#include "SurfacePool.h"
#include "WebPageClient.h"
#include "WebPage_p.h"

#include <wtf/NonCopyingSort.h>

#define DEBUG_RENDER_QUEUE 0
#define DEBUG_RENDER_QUEUE_SORT 0

#if DEBUG_RENDER_QUEUE
#include <BlackBerryPlatformLog.h>
#include <wtf/CurrentTime.h>
#endif

namespace BlackBerry {
namespace WebKit {

template<SortDirection sortDirection>
static inline int compareRectOneDirection(const TileIndex& t1, const TileIndex& t2)
{
    switch (sortDirection) {
    case LeftToRight:
        return t1.i() - t2.i();
    case RightToLeft:
        return t2.i() - t1.i();
    case TopToBottom:
        return t1.j() - t2.j();
    case BottomToTop:
        return t2.j() - t1.j();
    default:
        break;
    }
    ASSERT_NOT_REACHED();
    return 0;
}

template<SortDirection primarySortDirection, SortDirection secondarySortDirection>
static bool tileIndexIsLessThan(const TileIndex& t1, const TileIndex& t2)
{
    int primaryResult = compareRectOneDirection<primarySortDirection>(t1, t2);
    if (primaryResult || secondarySortDirection == primarySortDirection)
        return primaryResult < 0;
    return compareRectOneDirection<secondarySortDirection>(t1, t2) < 0;
}

typedef bool (*FuncTileIndexLessThan)(const TileIndex& t1, const TileIndex& t2);
static FuncTileIndexLessThan tileIndexLessThanFunction(SortDirection primary, SortDirection secondary)
{
    static FuncTileIndexLessThan s_tileIndexLessThanFunctions[NumSortDirections][NumSortDirections] = { { 0 } };
    static bool s_initialized = false;
    if (!s_initialized) {
#define ADD_COMPARE_FUNCTION(_primary, _secondary) \
        s_tileIndexLessThanFunctions[_primary][_secondary] = tileIndexIsLessThan<_primary, _secondary>

        ADD_COMPARE_FUNCTION(LeftToRight, LeftToRight);
        ADD_COMPARE_FUNCTION(LeftToRight, RightToLeft);
        ADD_COMPARE_FUNCTION(LeftToRight, TopToBottom);
        ADD_COMPARE_FUNCTION(LeftToRight, BottomToTop);

        ADD_COMPARE_FUNCTION(RightToLeft, LeftToRight);
        ADD_COMPARE_FUNCTION(RightToLeft, RightToLeft);
        ADD_COMPARE_FUNCTION(RightToLeft, TopToBottom);
        ADD_COMPARE_FUNCTION(RightToLeft, BottomToTop);

        ADD_COMPARE_FUNCTION(TopToBottom, LeftToRight);
        ADD_COMPARE_FUNCTION(TopToBottom, RightToLeft);
        ADD_COMPARE_FUNCTION(TopToBottom, TopToBottom);
        ADD_COMPARE_FUNCTION(TopToBottom, BottomToTop);

        ADD_COMPARE_FUNCTION(BottomToTop, LeftToRight);
        ADD_COMPARE_FUNCTION(BottomToTop, RightToLeft);
        ADD_COMPARE_FUNCTION(BottomToTop, TopToBottom);
        ADD_COMPARE_FUNCTION(BottomToTop, BottomToTop);
#undef ADD_COMPARE_FUNCTION

        s_initialized = true;
    }

    return s_tileIndexLessThanFunctions[primary][secondary];
}

class TileIndexLessThan {
public:
    TileIndexLessThan(SortDirection primarySortDirection, SortDirection secondarySortDirection)
        : m_tileIndexIsLessThan(tileIndexLessThanFunction(primarySortDirection, secondarySortDirection))
    {
    }

    bool operator()(const TileIndex& t1, const TileIndex& t2)
    {
        return m_tileIndexIsLessThan(t1, t2);
    }

private:
    FuncTileIndexLessThan m_tileIndexIsLessThan;
};

RenderQueue::RenderQueue(BackingStorePrivate* parent)
    : m_parent(parent)
    , m_rectsAddedToRegularRenderJobsInCurrentCycle(false)
    , m_currentRegularRenderJobsBatchUnderPressure(false)
    , m_primarySortDirection(TopToBottom)
    , m_secondarySortDirection(LeftToRight)
{
}

void RenderQueue::reset()
{
    m_rectsAddedToRegularRenderJobsInCurrentCycle = false;
    m_currentRegularRenderJobsBatchUnderPressure = false;
    m_primarySortDirection = TopToBottom;
    m_secondarySortDirection = LeftToRight;
    m_visibleZoomJobs.clear();
    m_visibleZoomJobsCompleted.clear();
    m_visibleScrollJobs.clear();
    m_visibleScrollJobsCompleted.clear();
    m_nonVisibleScrollJobs.clear();
    m_nonVisibleScrollJobsCompleted.clear();
    m_regularRenderJobsRegion = Platform::IntRectRegion();
    m_currentRegularRenderJobsBatch.clear();
    m_currentRegularRenderJobsBatchRegion = Platform::IntRectRegion();
    m_regularRenderJobsNotRenderedRegion = Platform::IntRectRegion();
    ASSERT(isEmpty());
}

bool RenderQueue::isEmpty(bool shouldPerformRegularRenderJobs) const
{
    return m_visibleZoomJobs.isEmpty() && m_visibleScrollJobs.isEmpty()
        && (!shouldPerformRegularRenderJobs || m_currentRegularRenderJobsBatch.isEmpty())
        && (!shouldPerformRegularRenderJobs || m_regularRenderJobsRegion.isEmpty())
        && m_nonVisibleScrollJobs.isEmpty();
}

bool RenderQueue::hasCurrentRegularRenderJob() const
{
    return !m_currentRegularRenderJobsBatch.isEmpty() || !m_regularRenderJobsRegion.isEmpty();
}

bool RenderQueue::hasCurrentVisibleZoomJob() const
{
    return !m_visibleZoomJobs.isEmpty();
}

bool RenderQueue::hasCurrentVisibleScrollJob() const
{
    return !m_visibleScrollJobs.isEmpty();
}

bool RenderQueue::isCurrentVisibleZoomJob(const TileIndex& index) const
{
    return m_visibleZoomJobs.contains(index);
}

bool RenderQueue::isCurrentVisibleZoomJobCompleted(const TileIndex& index) const
{
    return m_visibleZoomJobsCompleted.contains(index);
}

bool RenderQueue::isCurrentVisibleScrollJob(const TileIndex& index) const
{
    return m_visibleScrollJobs.contains(index);
}

bool RenderQueue::isCurrentVisibleScrollJobCompleted(const TileIndex& index) const
{
    return m_visibleScrollJobsCompleted.contains(index);
}

bool RenderQueue::isCurrentRegularRenderJob(const TileIndex& index, BackingStoreGeometry* geometry) const
{
    Platform::IntRect tileRect(geometry->originOfTile(index), m_parent->tileSize());
    Platform::IntRectRegion::IntersectionState regularJobsState = m_regularRenderJobsRegion.isRectInRegion(tileRect);
    Platform::IntRectRegion::IntersectionState currentRegularJobsState = m_currentRegularRenderJobsBatchRegion.isRectInRegion(tileRect);

    return regularJobsState == Platform::IntRectRegion::ContainedInRegion
        || regularJobsState == Platform::IntRectRegion::PartiallyContainedInRegion
        || currentRegularJobsState == Platform::IntRectRegion::ContainedInRegion
        || currentRegularJobsState == Platform::IntRectRegion::PartiallyContainedInRegion;
}

bool RenderQueue::currentRegularRenderJobBatchUnderPressure() const
{
    return m_currentRegularRenderJobsBatchUnderPressure;
}

void RenderQueue::setCurrentRegularRenderJobBatchUnderPressure(bool currentRegularRenderJobsBatchUnderPressure)
{
    m_currentRegularRenderJobsBatchUnderPressure = currentRegularRenderJobsBatchUnderPressure;
}

void RenderQueue::eventQueueCycled()
{
    // Called by the backing store when the event queue has cycled to allow the
    // render queue to determine if the regular render jobs are under pressure.
    if (m_rectsAddedToRegularRenderJobsInCurrentCycle && m_currentRegularRenderJobsBatchRegion.isEmpty())
        m_currentRegularRenderJobsBatchUnderPressure = true;
    m_rectsAddedToRegularRenderJobsInCurrentCycle = false;
}

TileIndexList RenderQueue::tileIndexesIntersectingRegion(const Platform::IntRectRegion& region, BackingStoreGeometry* geometry) const
{
    TileIndexList indexes;
    TileIndexList allIndexes = m_parent->indexesForBackingStoreRect(geometry->backingStoreRect());

    for (size_t i = 0; i < allIndexes.size(); ++i) {
        Platform::IntRect tileRect(geometry->originOfTile(allIndexes[i]), m_parent->tileSize());
        Platform::IntRectRegion::IntersectionState state = region.isRectInRegion(tileRect);
        if (state == Platform::IntRectRegion::ContainedInRegion || state == Platform::IntRectRegion::PartiallyContainedInRegion)
            indexes.append(allIndexes[i]);
    }
    return indexes;
}

TileIndexList RenderQueue::tileIndexesFullyContainedInRegion(const Platform::IntRectRegion& region, BackingStoreGeometry* geometry) const
{
    TileIndexList indexes;
    TileIndexList allIndexes = m_parent->indexesForBackingStoreRect(geometry->backingStoreRect());

    for (size_t i = 0; i < allIndexes.size(); ++i) {
        Platform::IntRect tileRect(geometry->originOfTile(allIndexes[i]), m_parent->tileSize());
        Platform::IntRectRegion::IntersectionState state = region.isRectInRegion(tileRect);
        if (state == Platform::IntRectRegion::ContainedInRegion)
            indexes.append(allIndexes[i]);
    }
    return indexes;
}

Platform::IntRectRegion RenderQueue::tileRegion(const TileIndexList& indexes, BackingStoreGeometry* geometry) const
{
    Platform::IntRectRegion region;

    // Chances are the tiles comprise a perfect rectangle. If we add rectangles
    // row by row or column by column, IntRectRegion should have an easier time
    // merging them as the individual rows or columns will merge into another
    // plain rectangle.
    Platform::IntRectRegion currentRowOrColumnRegion;
    size_t lastI = 0;
    size_t lastJ = 0;

    for (size_t i = 0; i < indexes.size(); ++i) {
        Platform::IntRect tileRect(geometry->originOfTile(indexes[i]), m_parent->tileSize());
        bool extendsCurrentRowOrColumn = i && (lastI == indexes[i].i() || lastJ == indexes[i].j());

        if (extendsCurrentRowOrColumn)
            currentRowOrColumnRegion = Platform::IntRectRegion::unionRegions(currentRowOrColumnRegion, tileRect);
        else {
            region = Platform::IntRectRegion::unionRegions(region, currentRowOrColumnRegion);
            currentRowOrColumnRegion = tileRect;
        }
        lastI = indexes[i].i();
        lastJ = indexes[i].j();
    }

    return Platform::IntRectRegion::unionRegions(region, currentRowOrColumnRegion);
}

void RenderQueue::addToQueue(JobType type, const Platform::IntRectRegion& region)
{
    if (type == RegularRender) {
        // Flag that we added rects in the current event queue cycle.
        m_rectsAddedToRegularRenderJobsInCurrentCycle = true;

        // We try and detect if this newly added region intersects or is contained in the currently running
        // batch of render jobs. If so, then we have to start the batch over since we decompose individual
        // rects into subrects and might have already rendered one of them. If the web page's content has
        // changed state then this can lead to artifacts. We mark this by noting the batch is now under pressure
        // and the backingstore will attempt to clear it at the next available opportunity.
        std::vector<Platform::IntRect> rectsInRegion = region.rects();
        for (size_t i = 0; i < rectsInRegion.size(); ++i) {
            Platform::IntRectRegion::IntersectionState state = m_currentRegularRenderJobsBatchRegion.isRectInRegion(rectsInRegion[i]);
            if (state == Platform::IntRectRegion::ContainedInRegion || state == Platform::IntRectRegion::PartiallyContainedInRegion) {
                m_regularRenderJobsRegion = Platform::IntRectRegion::unionRegions(m_regularRenderJobsRegion, m_currentRegularRenderJobsBatchRegion);
                m_currentRegularRenderJobsBatch.clear();
                m_currentRegularRenderJobsBatchRegion = Platform::IntRectRegion();
                m_currentRegularRenderJobsBatchUnderPressure = true;
            }
        }
        addToRegularQueue(region);
        return;
    }

    TileIndexList indexes = tileIndexesIntersectingRegion(region, m_parent->frontState());

    switch (type) {
    case VisibleZoom:
        addToScrollZoomQueue(indexes, &m_visibleZoomJobs);
        return;
    case VisibleScroll:
        addToScrollZoomQueue(indexes, &m_visibleScrollJobs);
        return;
    case NonVisibleScroll:
        addToScrollZoomQueue(indexes, &m_nonVisibleScrollJobs);
        return;
    default:
        ASSERT_NOT_REACHED();
    }
}

void RenderQueue::addToRegularQueue(const Platform::IntRectRegion& region)
{
#if DEBUG_RENDER_QUEUE
    std::vector<Platform::IntRect> rectsInRegion = region.rects();
    for (size_t i = 0; i < rectsInRegion.size(); ++i) {
        if (m_regularRenderJobsRegion.isRectInRegion(rectsInRegion[i]) != Platform::IntRectRegion::ContainedInRegion) {
            Platform::logAlways(Platform::LogLevelCritical,
                "RenderQueue::addToRegularQueue region within %s",
                region.extents().toString().c_str());
            break;
        }
    }
#endif

    m_regularRenderJobsRegion = Platform::IntRectRegion::unionRegions(m_regularRenderJobsRegion, region);

    // Do not let the regular render queue grow past a maximum of 3 disjoint rects.
    if (m_regularRenderJobsRegion.numRects() > 2)
        m_regularRenderJobsRegion = m_regularRenderJobsRegion.extents();

    if (!isEmpty())
        m_parent->dispatchRenderJob();
}

void RenderQueue::addToScrollZoomQueue(const TileIndexList& addedTiles, TileIndexList* alreadyQueuedTiles)
{
    Platform::IntRect contentsRect = m_parent->expandedContentsRect();
    for (size_t i = 0; i < addedTiles.size(); ++i) {
        if (alreadyQueuedTiles->contains(addedTiles[i]))
            continue;

        Platform::IntRect tileRect(m_parent->frontState()->originOfTile(addedTiles[i]), m_parent->tileSize());
        if (!contentsRect.intersects(tileRect)) {
#if DEBUG_RENDER_QUEUE
                Platform::logAlways(Platform::LogLevelCritical,
                    "RenderQueue::addToScrollZoomQueue tile at %s outside of expanded contents rect %s, ignoring.",
                    tileRect.toString().c_str(),
                    contentsRect.toString().c_str());
#endif
            continue;
        }

#if DEBUG_RENDER_QUEUE
        Platform::logAlways(Platform::LogLevelCritical,
            "RenderQueue::addToScrollZoomQueue tile at %s",
            m_parent->frontState()->originOfTile(addedTiles[i]).toString().c_str());
#endif
        alreadyQueuedTiles->append(addedTiles[i]);
    }

    if (!isEmpty())
        m_parent->dispatchRenderJob();
}

void RenderQueue::quickSort(TileIndexList* queue)
{
    size_t length = queue->size();
    if (!length)
        return;

    WTF::nonCopyingSort(queue->begin(), queue->end(), TileIndexLessThan(m_primarySortDirection, m_secondarySortDirection));
}

void RenderQueue::updateSortDirection(int lastDeltaX, int lastDeltaY)
{
    bool primaryIsHorizontal = abs(lastDeltaX) >= abs(lastDeltaY);
    if (primaryIsHorizontal) {
        m_primarySortDirection = lastDeltaX <= 0 ? LeftToRight : RightToLeft;
        m_secondarySortDirection = lastDeltaY <= 0 ? TopToBottom : BottomToTop;
    } else {
        m_primarySortDirection = lastDeltaY <= 0 ? TopToBottom : BottomToTop;
        m_secondarySortDirection = lastDeltaX <= 0 ? LeftToRight : RightToLeft;
    }
}

void RenderQueue::visibleContentChanged(const Platform::IntRect& visibleContent)
{
    if (m_visibleScrollJobs.isEmpty() && m_nonVisibleScrollJobs.isEmpty()) {
        ASSERT(m_visibleScrollJobsCompleted.isEmpty() && m_nonVisibleScrollJobsCompleted.isEmpty());
        return;
    }

    TileIndexList visibleTiles = tileIndexesIntersectingRegion(visibleContent, m_parent->frontState());

    // Move visibleScrollJobs to nonVisibleScrollJobs if they do not intersect
    // the visible content rect.
    for (size_t i = 0; i < m_visibleScrollJobs.size(); ++i) {
        if (!visibleTiles.contains(m_visibleScrollJobs[i])) {
            ASSERT(!m_nonVisibleScrollJobs.contains(m_visibleScrollJobs[i]));
            m_nonVisibleScrollJobs.append(m_visibleScrollJobs[i]);
            m_visibleScrollJobs.remove(i);
            --i;
        }
    }

    // Do the same for the completed list.
    for (size_t i = 0; i < m_visibleScrollJobsCompleted.size(); ++i) {
        if (!visibleTiles.contains(m_visibleScrollJobsCompleted[i])) {
            ASSERT(!m_nonVisibleScrollJobsCompleted.contains(m_visibleScrollJobsCompleted[i]));
            m_nonVisibleScrollJobsCompleted.append(m_visibleScrollJobsCompleted[i]);
            m_visibleScrollJobsCompleted.remove(i);
            --i;
        }
    }

    // Move nonVisibleScrollJobs to visibleScrollJobs if they do intersect
    // the visible content rect.
    for (size_t i = 0; i < m_nonVisibleScrollJobs.size(); ++i) {
        if (visibleTiles.contains(m_nonVisibleScrollJobs[i])) {
            ASSERT(!m_visibleScrollJobs.contains(m_nonVisibleScrollJobs[i]));
            m_visibleScrollJobs.append(m_nonVisibleScrollJobs[i]);
            m_nonVisibleScrollJobs.remove(i);
            --i;
        }
    }

    // Do the same for the completed list.
    for (size_t i = 0; i < m_nonVisibleScrollJobsCompleted.size(); ++i) {
        if (visibleTiles.contains(m_nonVisibleScrollJobsCompleted[i])) {
            ASSERT(!m_visibleScrollJobsCompleted.contains(m_nonVisibleScrollJobsCompleted[i]));
            m_visibleScrollJobsCompleted.append(m_nonVisibleScrollJobsCompleted[i]);
            m_nonVisibleScrollJobsCompleted.remove(i);
            --i;
        }
    }

    if (m_visibleScrollJobs.isEmpty() && !m_visibleScrollJobsCompleted.isEmpty())
        scrollZoomJobsCompleted(m_visibleScrollJobs, &m_visibleScrollJobsCompleted, false /*shouldBlit*/);

    if (m_nonVisibleScrollJobs.isEmpty() && !m_nonVisibleScrollJobsCompleted.isEmpty())
        scrollZoomJobsCompleted(m_nonVisibleScrollJobs, &m_nonVisibleScrollJobsCompleted, false /*shouldBlit*/);

    // We shouldn't be empty because the early return above and the fact that this
    // method just shuffles rects from queue to queue hence the total number of
    // rects in the various queues should be conserved.
    ASSERT(!isEmpty());
}

void RenderQueue::backingStoreRectChanging(const Platform::IntRect&, const Platform::IntRect&)
{
    // We could empty them here instead of in BackingStorePrivate::setBackingStoreRect(),
    // but it seems cleaner to do it there and still avoid code to manually move them.
    ASSERT(m_visibleZoomJobs.isEmpty());
    ASSERT(m_visibleZoomJobsCompleted.isEmpty());
    ASSERT(m_visibleScrollJobs.isEmpty());
    ASSERT(m_visibleScrollJobsCompleted.isEmpty());
    ASSERT(m_nonVisibleScrollJobs.isEmpty());
    ASSERT(m_nonVisibleScrollJobsCompleted.isEmpty());

    // Empty the tile-based current batch and merge its remaining region with
    // the one from other upcoming regular render jobs. We'll pick all of them
    // up next time renderRegularRenderJobs() is called.
    m_regularRenderJobsRegion = Platform::IntRectRegion::unionRegions(m_regularRenderJobsRegion, m_currentRegularRenderJobsBatchRegion);
    m_currentRegularRenderJobsBatchRegion = Platform::IntRectRegion();
    m_currentRegularRenderJobsBatch.clear();
}

void RenderQueue::clear(const Platform::IntRectRegion& region, ClearJobsFlags clearJobsFlags)
{
    clearRegions(region, clearJobsFlags);
    clearTileIndexes(tileIndexesFullyContainedInRegion(region, m_parent->frontState()), clearJobsFlags);
}

void RenderQueue::clear(const TileIndexList& indexes, BackingStoreGeometry* geometry, ClearJobsFlags clearJobsFlags)
{
    clearRegions(tileRegion(indexes, geometry), clearJobsFlags);
    clearTileIndexes(indexes, clearJobsFlags);
}

void RenderQueue::clearRegions(const Platform::IntRectRegion& region, ClearJobsFlags clearJobsFlags)
{
    if (clearJobsFlags & ClearRegularRenderJobs) {
        m_regularRenderJobsRegion = Platform::IntRectRegion::subtractRegions(m_regularRenderJobsRegion, region);
        m_currentRegularRenderJobsBatchRegion = Platform::IntRectRegion::subtractRegions(m_currentRegularRenderJobsBatchRegion, region);
        m_regularRenderJobsNotRenderedRegion = Platform::IntRectRegion::subtractRegions(m_regularRenderJobsNotRenderedRegion, region);
    }
}

void RenderQueue::clearTileIndexes(const TileIndexList& indexes, ClearJobsFlags clearJobsFlags)
{
    if (m_visibleScrollJobs.isEmpty() && m_nonVisibleScrollJobs.isEmpty())
        ASSERT(m_visibleScrollJobsCompleted.isEmpty() && m_nonVisibleScrollJobsCompleted.isEmpty());

    // Remove all indexes from all queues that are being marked as cleared.
    if (clearJobsFlags & ClearIncompleteZoomJobs) {
        for (size_t i = 0; i < m_visibleZoomJobs.size(); ++i) {
            if (indexes.contains(m_visibleZoomJobs.at(i))) {
                m_visibleZoomJobs.remove(i);
                --i;
            }
        }
    }

    if (clearJobsFlags & ClearIncompleteScrollJobs) {
        for (size_t i = 0; i < m_visibleScrollJobs.size(); ++i) {
            if (indexes.contains(m_visibleScrollJobs.at(i))) {
                m_visibleScrollJobs.remove(i);
                --i;
            }
        }

        for (size_t i = 0; i < m_nonVisibleScrollJobs.size(); ++i) {
            if (indexes.contains(m_nonVisibleScrollJobs.at(i))) {
                m_nonVisibleScrollJobs.remove(i);
                --i;
            }
        }
    }

    if (clearJobsFlags & ClearCompletedJobs) {
        for (size_t i = 0; i < m_visibleZoomJobsCompleted.size(); ++i) {
            if (indexes.contains(m_visibleZoomJobsCompleted.at(i))) {
                m_visibleZoomJobsCompleted.remove(i);
                --i;
            }
        }

        for (size_t i = 0; i < m_visibleScrollJobsCompleted.size(); ++i) {
            if (indexes.contains(m_visibleScrollJobsCompleted.at(i))) {
                m_visibleScrollJobsCompleted.remove(i);
                --i;
            }
        }

        for (size_t i = 0; i < m_nonVisibleScrollJobsCompleted.size(); ++i) {
            if (indexes.contains(m_nonVisibleScrollJobsCompleted.at(i))) {
                m_nonVisibleScrollJobsCompleted.remove(i);
                --i;
            }
        }
    }

    if (clearJobsFlags & ClearRegularRenderJobs) {
        for (size_t i = 0; i < m_currentRegularRenderJobsBatch.size(); ++i) {
            if (indexes.contains(m_currentRegularRenderJobsBatch.at(i))) {
                m_currentRegularRenderJobsBatch.remove(i);
                --i;
            }
        }
    }

    if (m_visibleZoomJobs.isEmpty() && !m_visibleZoomJobsCompleted.isEmpty())
        scrollZoomJobsCompleted(m_visibleZoomJobs, &m_visibleZoomJobsCompleted, false /*shouldBlit*/);

    if (m_visibleScrollJobs.isEmpty() && !m_visibleScrollJobsCompleted.isEmpty())
        scrollZoomJobsCompleted(m_visibleScrollJobs, &m_visibleScrollJobsCompleted, false /*shouldBlit*/);

    if (m_nonVisibleScrollJobs.isEmpty() && !m_nonVisibleScrollJobsCompleted.isEmpty())
        scrollZoomJobsCompleted(m_nonVisibleScrollJobs, &m_nonVisibleScrollJobsCompleted, false /*shouldBlit*/);
}

bool RenderQueue::regularRenderJobsPreviouslyAttemptedButNotRendered(const Platform::IntRect& rect)
{
    return m_regularRenderJobsNotRenderedRegion.isRectInRegion(rect) != Platform::IntRectRegion::NotInRegion;
}

void RenderQueue::render(bool shouldPerformRegularRenderJobs)
{
    // We request a layout here to ensure that we're executing jobs in the correct
    // order. If we didn't request a layout here then the jobs below could result
    // in a layout and that layout can alter this queue. So request layout if needed
    // to ensure that the queues below are in constant state before performing the
    // next rendering job.

#if DEBUG_RENDER_QUEUE
    // Start the time measurement.
    double time = WTF::currentTime();
#endif

    m_parent->requestLayoutIfNeeded();
    m_parent->updateTileMatrixIfNeeded();

#if DEBUG_RENDER_QUEUE
    double elapsed = WTF::currentTime() - time;
    if (elapsed)
        Platform::logAlways(Platform::LogLevelCritical, "RenderQueue::render layout elapsed=%f", elapsed);
#endif

    // Empty the queues in a precise order of priority.
    if (!m_visibleZoomJobs.isEmpty())
        renderScrollZoomJobs(&m_visibleZoomJobs, &m_visibleZoomJobsCompleted, true /*allAtOnceIfPossible*/, true /*shouldBlitWhenCompleted*/);
    else if (!m_visibleScrollJobs.isEmpty())
        renderScrollZoomJobs(&m_visibleScrollJobs, &m_visibleScrollJobsCompleted, false /*allAtOnceIfPossible*/, true /*shouldBlitWhenCompleted*/);
    else if (shouldPerformRegularRenderJobs && (!m_currentRegularRenderJobsBatch.isEmpty() || !m_regularRenderJobsRegion.isEmpty())) {
        bool allAtOnceIfPossible = currentRegularRenderJobBatchUnderPressure();
        renderRegularRenderJobs(allAtOnceIfPossible);
    } else if (!m_nonVisibleScrollJobs.isEmpty())
        renderScrollZoomJobs(&m_nonVisibleScrollJobs, &m_nonVisibleScrollJobsCompleted, false /*allAtOnceIfPossible*/, false /*shouldBlitWhenCompleted*/);
}

void RenderQueue::renderRegularRenderJobs(bool allAtOnceIfPossible)
{
#if DEBUG_RENDER_QUEUE
    // Start the time measurement.
    double time = WTF::currentTime();
#endif

    ASSERT(!m_currentRegularRenderJobsBatch.isEmpty() || !m_regularRenderJobsRegion.isEmpty());

    if (allAtOnceIfPossible && !m_currentRegularRenderJobsBatchRegion.isEmpty()) {
        // If there is a current batch of jobs already, merge it back into the
        // whole area so that we will pick up all of it for the next run.
        m_regularRenderJobsRegion = Platform::IntRectRegion::unionRegions(m_regularRenderJobsRegion, m_currentRegularRenderJobsBatchRegion);

        // Clear this since we're about to render everything.
        m_currentRegularRenderJobsBatchRegion = Platform::IntRectRegion();
        m_currentRegularRenderJobsBatch.clear();
    }

    Platform::IntRect contentsRect = m_parent->expandedContentsRect();

    // Start new batch if needed.
    if (m_currentRegularRenderJobsBatchRegion.isEmpty()) {
        // Don't try to render anything outside the whole contents rectangle.
        m_regularRenderJobsRegion = Platform::IntRectRegion::intersectRegions(m_regularRenderJobsRegion, contentsRect);

        // Split the current regular render job region into tiles.
        // Discard regions outside of the region covered by these tiles.
        // They'll be rendered when the geometry changes.
        m_regularRenderJobsRegion = Platform::IntRectRegion::intersectRegions(m_regularRenderJobsRegion, m_parent->frontState()->backingStoreRect());
        m_currentRegularRenderJobsBatch = tileIndexesIntersectingRegion(m_regularRenderJobsRegion, m_parent->frontState());

        // Create a region object that will be checked when adding new rects before
        // this batch has been completed.
        m_currentRegularRenderJobsBatchRegion = m_regularRenderJobsRegion;

        // Clear the former region since it is now part of this batch.
        m_regularRenderJobsRegion = Platform::IntRectRegion();

#if DEBUG_RENDER_QUEUE
        Platform::logAlways(Platform::LogLevelCritical,
            "RenderQueue::renderRegularRenderJobs batch size is %d!",
            m_currentRegularRenderJobsBatch.size());
#endif
    }

    Platform::IntRectRegion changingRegion = m_currentRegularRenderJobsBatchRegion;
    Platform::IntRectRegion changingRegionScheduledForLater = m_regularRenderJobsRegion;
    Platform::IntRectRegion regionNotRenderedThisTime;

    TileIndexList* outstandingJobs = &m_currentRegularRenderJobsBatch;
    TileIndexList completedJobs;
    TileIndexList tilesToRender;

    unsigned numberOfAvailableBackBuffers = SurfacePool::globalSurfacePool()->numberOfAvailableBackBuffers();

    while (!outstandingJobs->isEmpty() && numberOfAvailableBackBuffers) {
        // Render as many tiles at once as we can handle.
        for (size_t i = 0; i < outstandingJobs->size() && numberOfAvailableBackBuffers; ++i) {
            TileIndex index = (*outstandingJobs)[i];

            BackingStoreGeometry* geometry = m_parent->frontState();
            Platform::IntRect tileRect(geometry->originOfTile(index), m_parent->tileSize());

            if (!contentsRect.intersects(tileRect)) {
                // This is a safety fallback, at this point we should only
                // encounter tiles outside of the contents rect if we're in
                // a second run of the same batch and the contents size has
                // changed since the last run. Otherwise, see the cropping above.
#if DEBUG_RENDER_QUEUE
                Platform::logAlways(Platform::LogLevelCritical,
                    "RenderQueue::renderRegularRenderJobs tile at %s outside of expanded contents rect %s, ignoring.",
                    tileRect.toString().c_str(),
                    contentsRect.toString().c_str());
#endif
                outstandingJobs->remove(i);
                --i;
                m_currentRegularRenderJobsBatchRegion = Platform::IntRectRegion::intersectRegions(m_currentRegularRenderJobsBatchRegion, contentsRect);
                continue;
            }

            if (m_parent->shouldSuppressNonVisibleRegularRenderJobs()) {
                // If a tile is not visible, remember it as not rendered to be
                // picked up later by updateTilesForScrollOrNotRenderedRegion()
                // or updateTilesAfterBackingStoreRectChange().
                Platform::IntRectRegion tileRegionNotRendered;

                if (!m_parent->isTileVisible(index, geometry)) {
                    tileRegionNotRendered = Platform::IntRectRegion::intersectRegions(tileRect, m_currentRegularRenderJobsBatchRegion);
                    regionNotRenderedThisTime = Platform::IntRectRegion::unionRegions(regionNotRenderedThisTime, tileRegionNotRendered);
                }

                if (!tileRegionNotRendered.isEmpty()) {
#if DEBUG_RENDER_QUEUE
                    Platform::logAlways(Platform::LogLevelCritical,
                        "RenderQueue::renderRegularRenderJobs region within %s not completely rendered!",
                        tileRegionNotRendered.extents().toString().c_str());
#endif
                    outstandingJobs->remove(i);
                    --i;
                    continue;
                }
            }

            tilesToRender.append(index);
            --numberOfAvailableBackBuffers;
        }

        if (tilesToRender.isEmpty())
            break;

        // This will also clear the rendered tiles in outstandingJobs.
        TileIndexList renderedTiles = m_parent->render(tilesToRender);

        // Update number of available back buffers now that we've used them.
        numberOfAvailableBackBuffers = SurfacePool::globalSurfacePool()->numberOfAvailableBackBuffers();

        ASSERT(!renderedTiles.isEmpty());
        if (renderedTiles.isEmpty()) {
#if DEBUG_RENDER_QUEUE
            Platform::logAlways(Platform::LogLevelCritical,
                "RenderQueue::renderRegularRenderJobs no tiles rendered (%d attempted), available back buffers: %d",
                tilesToRender.size(),
                numberOfAvailableBackBuffers);
#endif
            break; // Something bad happened, make sure not to try again for now.
        }

        for (size_t i = 0; i < renderedTiles.size(); ++i) {
            if (!completedJobs.contains(renderedTiles[i]))
                completedJobs.append(renderedTiles[i]);
        }

        if (!allAtOnceIfPossible)
            break; // We can do the rest next time.
    }

    const Platform::IntRectRegion renderedRegion = tileRegion(completedJobs, m_parent->frontState());

    m_regularRenderJobsNotRenderedRegion = Platform::IntRectRegion::unionRegions(m_regularRenderJobsNotRenderedRegion, regionNotRenderedThisTime);
    m_currentRegularRenderJobsBatchRegion = Platform::IntRectRegion::subtractRegions(m_currentRegularRenderJobsBatchRegion, regionNotRenderedThisTime);

#if DEBUG_RENDER_QUEUE
    // Stop the time measurement.
    double elapsed = WTF::currentTime() - time;
    Platform::logAlways(Platform::LogLevelCritical,
        "RenderQueue::renderRegularRenderJobs within %s: completed = %d, outstanding = %d, elapsed=%f",
        changingRegion.extents().toString().c_str(),
        completedJobs.size(),
        outstandingJobs->size(),
        elapsed);
#endif

    // Make sure we didn't alter state of the queues that should have been empty
    // before this method was called.
    ASSERT(m_visibleZoomJobs.isEmpty());
    ASSERT(m_visibleScrollJobs.isEmpty());

    if (m_currentRegularRenderJobsBatchRegion.isEmpty()) {
        // If the whole scheduled area has been rendered, all outstanding jobs
        // should have been cleared as well.
        ASSERT(outstandingJobs->isEmpty());
        m_currentRegularRenderJobsBatchUnderPressure = false;

        // Notify about the newly rendered content.
        // In case we picked up an unfinished batch from before, because we
        // paint a larger area it's possible that we also rendered other
        // regions that were originally scheduled for later.
        Platform::IntRectRegion wholeScheduledRegion = Platform::IntRectRegion::unionRegions(changingRegion, changingRegionScheduledForLater);
        Platform::IntRectRegion renderedChangedRegion = Platform::IntRectRegion::intersectRegions(wholeScheduledRegion, renderedRegion);

        // Blit since this batch is now complete.
        m_parent->didRenderContent(renderedChangedRegion);
    }

    if (m_parent->shouldSuppressNonVisibleRegularRenderJobs() && !regionNotRenderedThisTime.isEmpty())
        m_parent->updateTilesForScrollOrNotRenderedRegion(false /*checkLoading*/);
}

void RenderQueue::renderScrollZoomJobs(TileIndexList* outstandingJobs, TileIndexList* completedJobs, bool allAtOnceIfPossible, bool shouldBlitWhenCompleted)
{
    ASSERT(!outstandingJobs->isEmpty());

#if DEBUG_RENDER_QUEUE || DEBUG_RENDER_QUEUE_SORT
    // Start the time measurement.
    double time = WTF::currentTime();
    double elapsed;
#endif

    Platform::IntRect contentsRect = m_parent->expandedContentsRect();

    unsigned numberOfAvailableBackBuffers = SurfacePool::globalSurfacePool()->numberOfAvailableBackBuffers();

    // If we take multiple turns to render, we sort to make them appear in the right order.
    if (!allAtOnceIfPossible && outstandingJobs->size() > numberOfAvailableBackBuffers)
        quickSort(outstandingJobs);

#if DEBUG_RENDER_QUEUE_SORT
    // Stop the time measurement
    elapsed = WTF::currentTime() - time;
    Platform::logAlways(Platform::LogLevelCritical, "RenderQueue::renderScrollZoomJobs sort elapsed=%f", elapsed);
#endif

    TileIndexList tilesToRender;

    while (!outstandingJobs->isEmpty() && numberOfAvailableBackBuffers) {
        // Render as many tiles at once as we can handle.
        for (size_t i = 0; i < outstandingJobs->size() && numberOfAvailableBackBuffers; ++i) {
            TileIndex index = (*outstandingJobs)[i];
            Platform::IntRect tileRect(m_parent->frontState()->originOfTile(index), m_parent->tileSize());

            // Make sure we only try to render tiles containing contents.
            if (!contentsRect.intersects(tileRect)) {
#if DEBUG_RENDER_QUEUE
                Platform::logAlways(Platform::LogLevelCritical,
                    "RenderQueue::renderScrollZoomJobs tile at %s outside of expanded contents rect %s, ignoring.",
                    tileRect.toString().c_str(),
                    contentsRect.toString().c_str());
#endif
                outstandingJobs->remove(i);
                --i;
                continue;
            }

            tilesToRender.append((*outstandingJobs)[i]);
            --numberOfAvailableBackBuffers;
        }

        if (tilesToRender.isEmpty())
            break;

        // This will clear the rendered tiles in outstandingJobs.
        TileIndexList renderedTiles = m_parent->render(tilesToRender);

        // Update number of available back buffers now that we've used them.
        numberOfAvailableBackBuffers = SurfacePool::globalSurfacePool()->numberOfAvailableBackBuffers();

        ASSERT(!renderedTiles.isEmpty());
        if (renderedTiles.isEmpty()) {
#if DEBUG_RENDER_QUEUE
            Platform::logAlways(Platform::LogLevelCritical,
                "RenderQueue::renderScrollZoomJobs no tiles rendered (%d attempted, available back buffers: %d)",
                tilesToRender.size(),
                numberOfAvailableBackBuffers);
#endif
            break; // Something bad happened, make sure not to try again for now.
        }

        for (size_t i = 0; i < renderedTiles.size(); ++i) {
            if (!completedJobs->contains(renderedTiles[i]))
                completedJobs->append(renderedTiles[i]);
        }

        if (!allAtOnceIfPossible)
            break; // We can do the rest next time.
    }

#if DEBUG_RENDER_QUEUE
    // Stop the time measurement.
    elapsed = WTF::currentTime() - time;
    Platform::logAlways(Platform::LogLevelCritical,
        "RenderQueue::renderScrollZoomJobs completed=%d, outstanding=%d, elapsed=%f",
        completedJobs->size(),
        outstandingJobs->size(),
        elapsed);
#endif

    if (outstandingJobs->isEmpty())
        scrollZoomJobsCompleted(*outstandingJobs, completedJobs, shouldBlitWhenCompleted);
}

void RenderQueue::scrollZoomJobsCompleted(const TileIndexList& outstandingJobs, TileIndexList* completedJobs, bool shouldBlit)
{
    // Get rid of the completed list!
    ASSERT(outstandingJobs.isEmpty());
    completedJobs->clear();

    // Now blit to the screen if we are done!
    if (shouldBlit)
        m_parent->didRenderContent(m_parent->visibleContentsRect());
}

} // namespace WebKit
} // namespace BlackBerry