/* * 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 #define DEBUG_RENDER_QUEUE 0 #define DEBUG_RENDER_QUEUE_SORT 0 #if DEBUG_RENDER_QUEUE #include #include #endif namespace BlackBerry { namespace WebKit { template 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 static bool tileIndexIsLessThan(const TileIndex& t1, const TileIndex& t2) { int primaryResult = compareRectOneDirection(t1, t2); if (primaryResult || secondarySortDirection == primarySortDirection) return primaryResult < 0; return compareRectOneDirection(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 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 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