| /* |
| * 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 "BackingStore.h" |
| |
| #include "BackingStoreClient.h" |
| #include "BackingStoreTile.h" |
| #include "BackingStoreVisualizationViewportAccessor.h" |
| #include "BackingStore_p.h" |
| #include "FatFingers.h" |
| #include "Frame.h" |
| #include "FrameView.h" |
| #include "GraphicsContext.h" |
| #include "InspectorController.h" |
| #include "InspectorInstrumentation.h" |
| #include "Page.h" |
| #include "SurfacePool.h" |
| #include "WebPage.h" |
| #include "WebPageClient.h" |
| #include "WebPageCompositorClient.h" |
| #include "WebPageCompositor_p.h" |
| #include "WebPage_p.h" |
| #include "WebSettings.h" |
| |
| #include <BlackBerryPlatformExecutableMessage.h> |
| #include <BlackBerryPlatformGraphics.h> |
| #include <BlackBerryPlatformIntRectRegion.h> |
| #include <BlackBerryPlatformLog.h> |
| #include <BlackBerryPlatformMessage.h> |
| #include <BlackBerryPlatformMessageClient.h> |
| #include <BlackBerryPlatformScreen.h> |
| #include <BlackBerryPlatformSettings.h> |
| #include <BlackBerryPlatformViewportAccessor.h> |
| #include <BlackBerryPlatformWindow.h> |
| |
| #include <SkImageDecoder.h> |
| |
| #include <wtf/CurrentTime.h> |
| #include <wtf/MathExtras.h> |
| #include <wtf/NotFound.h> |
| |
| #define SUPPRESS_NON_VISIBLE_REGULAR_RENDER_JOBS 0 |
| #define ENABLE_SCROLLBARS 1 |
| #define ENABLE_REPAINTONSCROLL 1 |
| #define DEBUG_BACKINGSTORE 0 |
| #define DEBUG_CHECKERBOARD 0 |
| #define DEBUG_WEBCORE_REQUESTS 0 |
| #define DEBUG_VISUALIZE 0 |
| #define DEBUG_TILEMATRIX 0 |
| |
| using namespace WebCore; |
| using namespace std; |
| |
| using BlackBerry::Platform::Graphics::Window; |
| using BlackBerry::Platform::IntRect; |
| using BlackBerry::Platform::IntPoint; |
| using BlackBerry::Platform::IntSize; |
| |
| namespace BlackBerry { |
| namespace WebKit { |
| |
| WebPage* BackingStorePrivate::s_currentBackingStoreOwner = 0; |
| |
| typedef std::pair<int, int> Divisor; |
| typedef Vector<Divisor> DivisorList; |
| // FIXME: Cache this and/or use a smarter algorithm. |
| static DivisorList divisors(unsigned n) |
| { |
| DivisorList divisors; |
| for (unsigned i = 1; i <= n; ++i) |
| if (!(n % i)) |
| divisors.append(std::make_pair(i, n / i)); |
| return divisors; |
| } |
| |
| static bool divisorIsPerfectWidth(Divisor divisor, Platform::IntSize size, int tileWidth) |
| { |
| return size.width() <= divisor.first * tileWidth && abs(size.width() - divisor.first * tileWidth) < tileWidth; |
| } |
| |
| static bool divisorIsPerfectHeight(Divisor divisor, Platform::IntSize size, int tileHeight) |
| { |
| return size.height() <= divisor.second * tileHeight && abs(size.height() - divisor.second * tileHeight) < tileHeight; |
| } |
| |
| static bool divisorIsPreferredDirection(Divisor divisor, BackingStorePrivate::TileMatrixDirection direction) |
| { |
| if (direction == BackingStorePrivate::Vertical) |
| return divisor.second > divisor.first; |
| return divisor.first > divisor.second; |
| } |
| |
| // Compute best divisor given the ratio determined by size. |
| static Divisor bestDivisor(Platform::IntSize size, int tileWidth, int tileHeight, |
| int minimumNumberOfTilesWide, int minimumNumberOfTilesHigh, |
| BackingStorePrivate::TileMatrixDirection direction) |
| { |
| // The point of this function is to determine the number of tiles in each |
| // dimension. We do this by looking to match the tile matrix width/height |
| // ratio as closely as possible with the width/height ratio of the contents. |
| // We also look at the direction passed to give preference to one dimension |
| // over another. This method could probably be made faster, but it gets the |
| // job done. |
| SurfacePool* surfacePool = SurfacePool::globalSurfacePool(); |
| ASSERT(!surfacePool->isEmpty()); |
| |
| // Store a static list of possible divisors. |
| static DivisorList divisorList = divisors(surfacePool->size()); |
| |
| // The ratio we're looking to best imitate. |
| float ratio = static_cast<float>(size.width()) / static_cast<float>(size.height()); |
| |
| Divisor bestDivisor; |
| for (size_t i = 0; i < divisorList.size(); ++i) { |
| Divisor divisor = divisorList[i]; |
| |
| const bool isPerfectWidth = divisorIsPerfectWidth(divisor, size, tileWidth); |
| const bool isPerfectHeight = divisorIsPerfectHeight(divisor, size, tileHeight); |
| const bool isValidWidth = divisor.first >= minimumNumberOfTilesWide || isPerfectWidth; |
| const bool isValidHeight = divisor.second >= minimumNumberOfTilesHigh || isPerfectHeight; |
| if (!isValidWidth || !isValidHeight) |
| continue; |
| |
| if (isPerfectWidth || isPerfectHeight) { |
| bestDivisor = divisor; // Found a perfect fit! |
| #if DEBUG_TILEMATRIX |
| BBLOG(BlackBerry::Platform::LogLevelCritical, "bestDivisor found perfect size isPerfectWidth=%s isPerfectHeight=%s", |
| isPerfectWidth ? "true" : "false", |
| isPerfectHeight ? "true" : "false"); |
| #endif |
| break; |
| } |
| |
| // Store basis of comparison. |
| if (!bestDivisor.first || !bestDivisor.second) { |
| bestDivisor = divisor; |
| continue; |
| } |
| |
| // If the current best divisor agrees with the preferred tile matrix direction, |
| // then continue if the current candidate does not. |
| if (divisorIsPreferredDirection(bestDivisor, direction) && !divisorIsPreferredDirection(divisor, direction)) |
| continue; |
| |
| // Compare ratios. |
| float diff1 = fabs((static_cast<float>(divisor.first) / static_cast<float>(divisor.second)) - ratio); |
| float diff2 = fabs((static_cast<float>(bestDivisor.first) / static_cast<float>(bestDivisor.second)) - ratio); |
| if (diff1 < diff2) |
| bestDivisor = divisor; |
| } |
| |
| return bestDivisor; |
| } |
| |
| struct BackingStoreMutexLocker { |
| BackingStoreMutexLocker(BackingStorePrivate* backingStorePrivate) |
| : m_backingStorePrivate(backingStorePrivate) |
| { |
| m_backingStorePrivate->lockBackingStore(); |
| } |
| |
| ~BackingStoreMutexLocker() |
| { |
| m_backingStorePrivate->unlockBackingStore(); |
| } |
| |
| private: |
| BackingStorePrivate* m_backingStorePrivate; |
| }; |
| |
| Platform::IntRect BackingStoreGeometry::backingStoreRect() const |
| { |
| return Platform::IntRect(backingStoreOffset(), backingStoreSize()); |
| } |
| |
| Platform::IntSize BackingStoreGeometry::backingStoreSize() const |
| { |
| return Platform::IntSize(numberOfTilesWide() * BackingStorePrivate::tileWidth(), numberOfTilesHigh() * BackingStorePrivate::tileHeight()); |
| } |
| |
| BackingStorePrivate::BackingStorePrivate() |
| : m_suspendScreenUpdates(0) |
| , m_suspendBackingStoreUpdates(0) |
| , m_resumeOperation(BackingStore::None) |
| , m_suspendRenderJobs(false) |
| , m_suspendRegularRenderJobs(false) |
| , m_isScrollingOrZooming(false) |
| , m_webPage(0) |
| , m_client(0) |
| , m_renderQueue(adoptPtr(new RenderQueue(this))) |
| , m_defersBlit(true) |
| , m_hasBlitJobs(false) |
| , m_webPageBackgroundColor(WebCore::Color::white) |
| , m_currentWindowBackBuffer(0) |
| , m_preferredTileMatrixDimension(Vertical) |
| #if USE(ACCELERATED_COMPOSITING) |
| , m_isDirectRenderingAnimationMessageScheduled(false) |
| #endif |
| { |
| m_frontState = reinterpret_cast<unsigned>(new BackingStoreGeometry); |
| m_backState = reinterpret_cast<unsigned>(new BackingStoreGeometry); |
| |
| // Need a recursive mutex to achieve a global lock. |
| pthread_mutexattr_t attr; |
| pthread_mutexattr_init(&attr); |
| pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); |
| pthread_mutex_init(&m_mutex, &attr); |
| pthread_mutexattr_destroy(&attr); |
| } |
| |
| BackingStorePrivate::~BackingStorePrivate() |
| { |
| BackingStoreGeometry* front = reinterpret_cast<BackingStoreGeometry*>(m_frontState); |
| delete front; |
| m_frontState = 0; |
| |
| BackingStoreGeometry* back = reinterpret_cast<BackingStoreGeometry*>(m_backState); |
| delete back; |
| m_backState = 0; |
| |
| pthread_mutex_destroy(&m_mutex); |
| } |
| |
| void BackingStorePrivate::instrumentBeginFrame() |
| { |
| WebCore::InspectorInstrumentation::didBeginFrame(WebPagePrivate::core(m_webPage)); |
| } |
| |
| void BackingStorePrivate::instrumentCancelFrame() |
| { |
| WebCore::InspectorInstrumentation::didCancelFrame(WebPagePrivate::core(m_webPage)); |
| } |
| |
| bool BackingStorePrivate::shouldDirectRenderingToWindow() const |
| { |
| // Direct rendering doesn't work with OpenGL compositing code paths due to |
| // a race condition on which thread's EGL context gets to make the surface |
| // current, see PR 105750. |
| // As a workaround, we will be using compositor to draw the root layer. |
| if (isOpenGLCompositing()) |
| return false; |
| |
| if (m_webPage->settings()->isDirectRenderingToWindowEnabled()) |
| return true; |
| |
| // If the BackingStore is inactive, see if there's a compositor to do the |
| // work of rendering the root layer. |
| if (!isActive()) |
| return !m_webPage->d->compositorDrawsRootLayer(); |
| |
| const BackingStoreGeometry* currentState = frontState(); |
| const unsigned tilesNecessary = minimumNumberOfTilesWide() * minimumNumberOfTilesHigh(); |
| const unsigned tilesAvailable = currentState->numberOfTilesWide() * currentState->numberOfTilesHigh(); |
| return tilesAvailable < tilesNecessary; |
| } |
| |
| bool BackingStorePrivate::isOpenGLCompositing() const |
| { |
| if (Window* window = m_webPage->client()->window()) |
| return window->windowUsage() == Window::GLES2Usage; |
| |
| // If there's no window, OpenGL rendering is currently the only option. |
| return true; |
| } |
| |
| void BackingStorePrivate::suspendScreenAndBackingStoreUpdates() |
| { |
| if (m_suspendScreenUpdates) { |
| BBLOG(BlackBerry::Platform::LogLevelInfo, |
| "Screen and backingstore already suspended, increasing suspend counter."); |
| } |
| |
| ++m_suspendBackingStoreUpdates; |
| |
| // Make sure the user interface thread gets the message before we proceed |
| // because blitVisibleContents() can be called from the user interface |
| // thread and it must honor this flag. |
| ++m_suspendScreenUpdates; |
| |
| BlackBerry::Platform::userInterfaceThreadMessageClient()->syncToCurrentMessage(); |
| } |
| |
| void BackingStorePrivate::resumeScreenAndBackingStoreUpdates(BackingStore::ResumeUpdateOperation op) |
| { |
| ASSERT(m_suspendScreenUpdates); |
| ASSERT(m_suspendBackingStoreUpdates); |
| |
| // Both variables are similar except for the timing of setting them. |
| ASSERT(m_suspendScreenUpdates == m_suspendBackingStoreUpdates); |
| |
| if (!m_suspendScreenUpdates || !m_suspendBackingStoreUpdates) { |
| BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelCritical, |
| "Call mismatch: Screen and backingstore haven't been suspended, therefore won't resume!"); |
| return; |
| } |
| |
| // Out of all nested resume calls, resume with the maximum-impact operation. |
| if (op == BackingStore::RenderAndBlit |
| || (m_resumeOperation == BackingStore::None && op == BackingStore::Blit)) |
| m_resumeOperation = op; |
| |
| if (m_suspendScreenUpdates >= 2 && m_suspendBackingStoreUpdates >= 2) { // we're still suspended |
| BBLOG(BlackBerry::Platform::LogLevelInfo, |
| "Screen and backingstore still suspended, decreasing suspend counter."); |
| --m_suspendBackingStoreUpdates; |
| --m_suspendScreenUpdates; |
| return; |
| } |
| |
| --m_suspendBackingStoreUpdates; |
| |
| op = m_resumeOperation; |
| m_resumeOperation = BackingStore::None; |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| if (op != BackingStore::None) { |
| if (isOpenGLCompositing() && !isActive()) { |
| m_webPage->d->setCompositorDrawsRootLayer(true); |
| m_webPage->d->setNeedsOneShotDrawingSynchronization(); |
| --m_suspendScreenUpdates; |
| BlackBerry::Platform::userInterfaceThreadMessageClient()->syncToCurrentMessage(); |
| return; |
| } |
| |
| m_webPage->d->setNeedsOneShotDrawingSynchronization(); |
| } |
| #endif |
| |
| // For the direct rendering case, there is no such operation as blit, |
| // we have to render to get anything to the screen. |
| if (shouldDirectRenderingToWindow() && op == BackingStore::Blit) |
| op = BackingStore::RenderAndBlit; |
| |
| // Do some rendering if necessary. |
| if (op == BackingStore::RenderAndBlit) |
| renderVisibleContents(); |
| |
| // Make sure the user interface thread gets the message before we proceed |
| // because blitVisibleContents() can be called from the user interface |
| // thread and it must honor this flag. |
| --m_suspendScreenUpdates; |
| BlackBerry::Platform::userInterfaceThreadMessageClient()->syncToCurrentMessage(); |
| |
| if (op == BackingStore::None) |
| return; |
| #if USE(ACCELERATED_COMPOSITING) |
| // It needs layout and render before committing root layer if we set OSDS |
| if (m_webPage->d->needsOneShotDrawingSynchronization()) |
| m_webPage->d->requestLayoutIfNeeded(); |
| |
| // This will also blit since we set the OSDS flag above. |
| m_webPage->d->commitRootLayerIfNeeded(); |
| #else |
| // Do some blitting if necessary. |
| if ((op == BackingStore::Blit || op == BackingStore::RenderAndBlit) && !shouldDirectRenderingToWindow()) |
| blitVisibleContents(); |
| #endif |
| } |
| |
| void BackingStorePrivate::repaint(const Platform::IntRect& windowRect, |
| bool contentChanged, bool immediate) |
| { |
| if (m_suspendBackingStoreUpdates) |
| return; |
| |
| // If immediate is true, then we're being asked to perform synchronously. |
| // NOTE: WebCore::ScrollView will call this method with immediate:true and contentChanged:false. |
| // This is a special case introduced specifically for the Apple's windows port and can be safely ignored I believe. |
| // Now this method will be called from WebPagePrivate::repaint(). |
| |
| if (contentChanged && !windowRect.isEmpty()) { |
| // This windowRect is in untransformed coordinates relative to the viewport, but |
| // it needs to be transformed coordinates relative to the transformed contents. |
| Platform::IntRect rect = m_webPage->d->mapToTransformed(m_client->mapFromViewportToContents(windowRect)); |
| rect.inflate(1 /*dx*/, 1 /*dy*/); // Account for anti-aliasing of previous rendering runs. |
| |
| // FIXME: This should not explicitely depend on WebCore::. |
| WebCore::IntRect tmpRect = rect; |
| m_client->clipToTransformedContentsRect(tmpRect); |
| |
| rect = tmpRect; |
| if (rect.isEmpty()) |
| return; |
| |
| #if DEBUG_WEBCORE_REQUESTS |
| BBLOG(BlackBerry::Platform::LogLevelCritical, |
| "BackingStorePrivate::repaint rect=%d,%d %dx%d contentChanged=%s immediate=%s", |
| rect.x(), rect.y(), rect.width(), rect.height(), |
| (contentChanged ? "true" : "false"), |
| (immediate ? "true" : "false")); |
| #endif |
| |
| if (immediate) { |
| if (render(rect)) { |
| if (!shouldDirectRenderingToWindow() && !m_webPage->d->commitRootLayerIfNeeded()) |
| blitVisibleContents(); |
| m_webPage->d->m_client->notifyPixelContentRendered(rect); |
| } |
| } else |
| m_renderQueue->addToQueue(RenderQueue::RegularRender, rect); |
| } |
| } |
| |
| void BackingStorePrivate::slowScroll(const Platform::IntSize& delta, const Platform::IntRect& windowRect, bool immediate) |
| { |
| ASSERT(BlackBerry::Platform::webKitThreadMessageClient()->isCurrentThread()); |
| |
| #if DEBUG_BACKINGSTORE |
| // Start the time measurement... |
| double time = WTF::currentTime(); |
| #endif |
| |
| scrollingStartedHelper(delta); |
| |
| // This windowRect is in untransformed coordinates relative to the viewport, but |
| // it needs to be transformed coordinates relative to the transformed contents. |
| Platform::IntRect rect = m_webPage->d->mapToTransformed(m_client->mapFromViewportToContents(windowRect)); |
| |
| if (immediate) { |
| if (render(rect) && !isSuspended() && !shouldDirectRenderingToWindow() && !m_webPage->d->commitRootLayerIfNeeded()) |
| blitVisibleContents(); |
| } else { |
| m_renderQueue->addToQueue(RenderQueue::VisibleScroll, rect); |
| // We only blit here if the client did not generate the scroll as the client |
| // now supports blitting asynchronously during scroll operations. |
| if (!m_client->isClientGeneratedScroll() && !shouldDirectRenderingToWindow()) |
| blitVisibleContents(); |
| } |
| |
| #if DEBUG_BACKINGSTORE |
| // Stop the time measurement. |
| double elapsed = WTF::currentTime() - time; |
| BBLOG(BlackBerry::Platform::LogLevelCritical, "BackingStorePrivate::slowScroll elapsed=%f", elapsed); |
| #endif |
| } |
| |
| void BackingStorePrivate::scroll(const Platform::IntSize& delta, |
| const Platform::IntRect& scrollViewRect, |
| const Platform::IntRect& clipRect) |
| { |
| ASSERT(BlackBerry::Platform::webKitThreadMessageClient()->isCurrentThread()); |
| |
| // If we are direct rendering then we are forced to go down the slow path |
| // to scrolling. |
| if (shouldDirectRenderingToWindow()) { |
| Platform::IntRect viewportRect(Platform::IntPoint(0, 0), m_webPage->d->transformedViewportSize()); |
| slowScroll(delta, m_webPage->d->mapFromTransformed(viewportRect), true /*immediate*/); |
| return; |
| } |
| |
| #if DEBUG_BACKINGSTORE |
| // Start the time measurement... |
| double time = WTF::currentTime(); |
| #endif |
| |
| scrollingStartedHelper(delta); |
| |
| // We only blit here if the client did not generate the scroll as the client |
| // now supports blitting asynchronously during scroll operations. |
| if (!m_client->isClientGeneratedScroll()) |
| blitVisibleContents(); |
| |
| #if DEBUG_BACKINGSTORE |
| // Stop the time measurement. |
| double elapsed = WTF::currentTime() - time; |
| BBLOG(BlackBerry::Platform::LogLevelCritical, "BackingStorePrivate::scroll dx=%d, dy=%d elapsed=%f", delta.width(), delta.height(), elapsed); |
| #endif |
| } |
| |
| void BackingStorePrivate::scrollingStartedHelper(const Platform::IntSize& delta) |
| { |
| // Notify the render queue so that it can shuffle accordingly. |
| m_renderQueue->updateSortDirection(delta.width(), delta.height()); |
| m_renderQueue->visibleContentChanged(visibleContentsRect()); |
| |
| // Scroll the actual backingstore. |
| scrollBackingStore(delta.width(), delta.height()); |
| |
| // Add any newly visible tiles that have not been previously rendered to the queue |
| // and check if the tile was previously rendered by regular render job. |
| updateTilesForScrollOrNotRenderedRegion(); |
| } |
| |
| bool BackingStorePrivate::shouldSuppressNonVisibleRegularRenderJobs() const |
| { |
| #if SUPPRESS_NON_VISIBLE_REGULAR_RENDER_JOBS |
| return true; |
| #else |
| // Always suppress when loading as this drastically decreases page loading |
| // time... |
| return m_client->isLoading(); |
| #endif |
| } |
| |
| bool BackingStorePrivate::shouldPerformRenderJobs() const |
| { |
| return (m_webPage->isVisible() || shouldDirectRenderingToWindow()) && !m_suspendRenderJobs && !m_suspendBackingStoreUpdates && !m_renderQueue->isEmpty(!m_suspendRegularRenderJobs); |
| } |
| |
| bool BackingStorePrivate::shouldPerformRegularRenderJobs() const |
| { |
| return shouldPerformRenderJobs() && !m_suspendRegularRenderJobs; |
| } |
| |
| static const BlackBerry::Platform::Message::Type RenderJobMessageType = BlackBerry::Platform::Message::generateUniqueMessageType(); |
| class RenderJobMessage : public BlackBerry::Platform::ExecutableMessage { |
| public: |
| RenderJobMessage(BlackBerry::Platform::MessageDelegate* delegate) |
| : BlackBerry::Platform::ExecutableMessage(delegate, BlackBerry::Platform::ExecutableMessage::UniqueCoalescing, RenderJobMessageType) |
| { } |
| }; |
| |
| void BackingStorePrivate::dispatchRenderJob() |
| { |
| BlackBerry::Platform::MessageDelegate* messageDelegate = BlackBerry::Platform::createMethodDelegate(&BackingStorePrivate::renderJob, this); |
| BlackBerry::Platform::webKitThreadMessageClient()->dispatchMessage(new RenderJobMessage(messageDelegate)); |
| } |
| |
| void BackingStorePrivate::renderJob() |
| { |
| if (!shouldPerformRenderJobs()) |
| return; |
| |
| instrumentBeginFrame(); |
| |
| #if DEBUG_BACKINGSTORE |
| BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelCritical, "BackingStorePrivate::renderJob"); |
| #endif |
| |
| m_renderQueue->render(!m_suspendRegularRenderJobs); |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| m_webPage->d->commitRootLayerIfNeeded(); |
| #endif |
| |
| if (shouldPerformRenderJobs()) |
| dispatchRenderJob(); |
| } |
| |
| Platform::IntRect BackingStorePrivate::expandedContentsRect() const |
| { |
| return Platform::IntRect(Platform::IntPoint(0, 0), expandedContentsSize()); |
| } |
| |
| Platform::IntRect BackingStorePrivate::visibleContentsRect() const |
| { |
| return intersection(m_client->transformedVisibleContentsRect(), |
| Platform::IntRect(Platform::IntPoint(0, 0), m_client->transformedContentsSize())); |
| } |
| |
| Platform::IntRect BackingStorePrivate::unclippedVisibleContentsRect() const |
| { |
| return m_client->transformedVisibleContentsRect(); |
| } |
| |
| bool BackingStorePrivate::shouldMoveLeft(const Platform::IntRect& backingStoreRect) const |
| { |
| return canMoveX(backingStoreRect) |
| && backingStoreRect.x() > visibleContentsRect().x() |
| && backingStoreRect.x() > expandedContentsRect().x(); |
| } |
| |
| bool BackingStorePrivate::shouldMoveRight(const Platform::IntRect& backingStoreRect) const |
| { |
| return canMoveX(backingStoreRect) |
| && backingStoreRect.right() < visibleContentsRect().right() |
| && backingStoreRect.right() < expandedContentsRect().right(); |
| } |
| |
| bool BackingStorePrivate::shouldMoveUp(const Platform::IntRect& backingStoreRect) const |
| { |
| return canMoveY(backingStoreRect) |
| && backingStoreRect.y() > visibleContentsRect().y() |
| && backingStoreRect.y() > expandedContentsRect().y(); |
| } |
| |
| bool BackingStorePrivate::shouldMoveDown(const Platform::IntRect& backingStoreRect) const |
| { |
| return canMoveY(backingStoreRect) |
| && backingStoreRect.bottom() < visibleContentsRect().bottom() |
| && backingStoreRect.bottom() < expandedContentsRect().bottom(); |
| } |
| |
| bool BackingStorePrivate::canMoveX(const Platform::IntRect& backingStoreRect) const |
| { |
| return backingStoreRect.width() > visibleContentsRect().width(); |
| } |
| |
| bool BackingStorePrivate::canMoveY(const Platform::IntRect& backingStoreRect) const |
| { |
| return backingStoreRect.height() > visibleContentsRect().height(); |
| } |
| |
| bool BackingStorePrivate::canMoveLeft(const Platform::IntRect& rect) const |
| { |
| Platform::IntRect backingStoreRect = rect; |
| Platform::IntRect visibleContentsRect = this->visibleContentsRect(); |
| Platform::IntRect contentsRect = this->expandedContentsRect(); |
| backingStoreRect.move(-tileWidth(), 0); |
| return backingStoreRect.right() >= visibleContentsRect.right() |
| && backingStoreRect.x() >= contentsRect.x(); |
| } |
| |
| bool BackingStorePrivate::canMoveRight(const Platform::IntRect& rect) const |
| { |
| Platform::IntRect backingStoreRect = rect; |
| Platform::IntRect visibleContentsRect = this->visibleContentsRect(); |
| Platform::IntRect contentsRect = this->expandedContentsRect(); |
| backingStoreRect.move(tileWidth(), 0); |
| return backingStoreRect.x() <= visibleContentsRect.x() |
| && (backingStoreRect.right() <= contentsRect.right() |
| || (backingStoreRect.right() - contentsRect.right()) < tileWidth()); |
| } |
| |
| bool BackingStorePrivate::canMoveUp(const Platform::IntRect& rect) const |
| { |
| Platform::IntRect backingStoreRect = rect; |
| Platform::IntRect visibleContentsRect = this->visibleContentsRect(); |
| Platform::IntRect contentsRect = this->expandedContentsRect(); |
| backingStoreRect.move(0, -tileHeight()); |
| return backingStoreRect.bottom() >= visibleContentsRect.bottom() |
| && backingStoreRect.y() >= contentsRect.y(); |
| } |
| |
| bool BackingStorePrivate::canMoveDown(const Platform::IntRect& rect) const |
| { |
| Platform::IntRect backingStoreRect = rect; |
| Platform::IntRect visibleContentsRect = this->visibleContentsRect(); |
| Platform::IntRect contentsRect = this->expandedContentsRect(); |
| backingStoreRect.move(0, tileHeight()); |
| return backingStoreRect.y() <= visibleContentsRect.y() |
| && (backingStoreRect.bottom() <= contentsRect.bottom() |
| || (backingStoreRect.bottom() - contentsRect.bottom()) < tileHeight()); |
| } |
| |
| Platform::IntRect BackingStorePrivate::backingStoreRectForScroll(int deltaX, int deltaY, const Platform::IntRect& rect) const |
| { |
| // The current rect. |
| Platform::IntRect backingStoreRect = rect; |
| |
| // This method uses the delta values to describe the backingstore rect |
| // given the current scroll direction and the viewport position. However, |
| // this method can be called with no deltas whatsoever for instance when |
| // the contents size changes or the orientation changes. In this case, we |
| // want to use the previous scroll direction to describe the backingstore |
| // rect. This will result in less checkerboard. |
| if (!deltaX && !deltaY) { |
| deltaX = m_previousDelta.width(); |
| deltaY = m_previousDelta.height(); |
| } |
| m_previousDelta = Platform::IntSize(deltaX, deltaY); |
| |
| // Return to origin if need be. |
| if (!canMoveX(backingStoreRect) && backingStoreRect.x()) |
| backingStoreRect.setX(0); |
| |
| if (!canMoveY(backingStoreRect) && backingStoreRect.y()) |
| backingStoreRect.setY(0); |
| |
| // Move the rect left. |
| while (shouldMoveLeft(backingStoreRect) || (deltaX > 0 && canMoveLeft(backingStoreRect))) |
| backingStoreRect.move(-tileWidth(), 0); |
| |
| // Move the rect right. |
| while (shouldMoveRight(backingStoreRect) || (deltaX < 0 && canMoveRight(backingStoreRect))) |
| backingStoreRect.move(tileWidth(), 0); |
| |
| // Move the rect up. |
| while (shouldMoveUp(backingStoreRect) || (deltaY > 0 && canMoveUp(backingStoreRect))) |
| backingStoreRect.move(0, -tileHeight()); |
| |
| // Move the rect down. |
| while (shouldMoveDown(backingStoreRect) || (deltaY < 0 && canMoveDown(backingStoreRect))) |
| backingStoreRect.move(0, tileHeight()); |
| |
| return backingStoreRect; |
| } |
| |
| void BackingStorePrivate::setBackingStoreRect(const Platform::IntRect& backingStoreRect, double scale) |
| { |
| if (!m_webPage->isVisible()) |
| return; |
| |
| if (!isActive()) { |
| m_webPage->d->setShouldResetTilesWhenShown(true); |
| return; |
| } |
| |
| Platform::IntRect currentBackingStoreRect = frontState()->backingStoreRect(); |
| double currentScale = frontState()->scale(); |
| |
| if (backingStoreRect == currentBackingStoreRect && scale == currentScale) |
| return; |
| |
| #if DEBUG_TILEMATRIX |
| BBLOG(BlackBerry::Platform::LogLevelCritical, "BackingStorePrivate::setBackingStoreRect changed from (%d,%d %dx%d) to (%d,%d %dx%d)", |
| currentBackingStoreRect.x(), |
| currentBackingStoreRect.y(), |
| currentBackingStoreRect.width(), |
| currentBackingStoreRect.height(), |
| backingStoreRect.x(), |
| backingStoreRect.y(), |
| backingStoreRect.width(), |
| backingStoreRect.height()); |
| #endif |
| |
| BackingStoreGeometry* currentState = frontState(); |
| TileMap currentMap = currentState->tileMap(); |
| |
| TileIndexList indexesToFill = indexesForBackingStoreRect(backingStoreRect); |
| |
| ASSERT(static_cast<int>(indexesToFill.size()) == currentMap.size()); |
| |
| TileMap newTileMap; |
| TileMap leftOverTiles; |
| |
| // Iterate through our current tile map and add tiles that are rendered with |
| // our new backing store rect. |
| TileMap::const_iterator tileMapEnd = currentMap.end(); |
| for (TileMap::const_iterator it = currentMap.begin(); it != tileMapEnd; ++it) { |
| TileIndex oldIndex = it->key; |
| BackingStoreTile* tile = it->value; |
| |
| // Reset the old index. |
| resetTile(oldIndex, tile, false /*resetBackground*/); |
| |
| // Origin of last committed render for tile in transformed content coordinates. |
| Platform::IntPoint origin = originOfLastRenderForTile(oldIndex, tile, currentBackingStoreRect); |
| |
| // If the new backing store rect contains this origin, then insert the tile there |
| // and mark it as no longer shifted. Note: Platform::IntRect::contains checks for a 1x1 rect |
| // below and to the right of the origin so it is correct usage here. |
| if (backingStoreRect.contains(origin)) { |
| TileIndex newIndex = indexOfTile(origin, backingStoreRect); |
| Platform::IntRect rect(origin, tileSize()); |
| if (m_renderQueue->regularRenderJobsPreviouslyAttemptedButNotRendered(rect)) { |
| // If the render queue previously tried to render this tile, but the |
| // backingstore wasn't in the correct place or the tile wasn't visible |
| // at the time then we can't simply restore the tile since the content |
| // is now invalid as far as WebKit is concerned. Instead, we clear |
| // the tile here of the region and then put the tile in the render |
| // queue again. |
| |
| // Intersect the tile with the not rendered region to get the areas |
| // of the tile that we need to clear. |
| Platform::IntRectRegion tileNotRenderedRegion = Platform::IntRectRegion::intersectRegions(m_renderQueue->regularRenderJobsNotRenderedRegion(), rect); |
| clearAndUpdateTileOfNotRenderedRegion(newIndex, tile, tileNotRenderedRegion, backingStoreRect); |
| #if DEBUG_BACKINGSTORE |
| Platform::IntRect extents = tileNotRenderedRegion.extents(); |
| BBLOG(BlackBerry::Platform::LogLevelCritical, "BackingStorePrivate::setBackingStoreRect did clear tile %d,%d %dx%d", |
| extents.x(), extents.y(), extents.width(), extents.height()); |
| #endif |
| } else { |
| // Mark as needing update. |
| if (!tile->frontBuffer()->isRendered(currentState->scale()) |
| && !isCurrentVisibleJob(newIndex, tile, backingStoreRect)) |
| updateTile(origin, false /*immediate*/); |
| } |
| |
| // Do some bookkeeping with shifting tiles... |
| tile->clearShift(); |
| tile->setCommitted(true); |
| |
| size_t i = indexesToFill.find(newIndex); |
| ASSERT(i != WTF::notFound); |
| indexesToFill.remove(i); |
| newTileMap.add(newIndex, tile); |
| } else { |
| // Store this tile and index so we can add it to the remaining left over spots... |
| leftOverTiles.add(oldIndex, tile); |
| } |
| } |
| |
| ASSERT(static_cast<int>(indexesToFill.size()) == leftOverTiles.size()); |
| size_t i = 0; |
| TileMap::const_iterator leftOverEnd = leftOverTiles.end(); |
| for (TileMap::const_iterator it = leftOverTiles.begin(); it != leftOverEnd; ++it) { |
| TileIndex oldIndex = it->key; |
| BackingStoreTile* tile = it->value; |
| if (i >= indexesToFill.size()) { |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| TileIndex newIndex = indexesToFill.at(i); |
| |
| // Origin of last committed render for tile in transformed content coordinates. |
| Platform::IntPoint originOfOld = originOfLastRenderForTile(oldIndex, tile, currentBackingStoreRect); |
| // Origin of the new index for the new backing store rect. |
| Platform::IntPoint originOfNew = originOfTile(newIndex, backingStoreRect); |
| |
| // Mark as needing update. |
| updateTile(originOfNew, false /*immediate*/); |
| |
| tile->clearShift(); |
| tile->setCommitted(false); |
| tile->setHorizontalShift((originOfOld.x() - originOfNew.x()) / tileWidth()); |
| tile->setVerticalShift((originOfOld.y() - originOfNew.y()) / tileHeight()); |
| |
| newTileMap.add(newIndex, tile); |
| |
| ++i; |
| } |
| |
| // Checks to make sure we haven't lost any tiles. |
| ASSERT(currentMap.size() == newTileMap.size()); |
| |
| backState()->setScale(scale); |
| backState()->setNumberOfTilesWide(backingStoreRect.width() / tileWidth()); |
| backState()->setNumberOfTilesHigh(backingStoreRect.height() / tileHeight()); |
| backState()->setBackingStoreOffset(backingStoreRect.location()); |
| backState()->setTileMap(newTileMap); |
| |
| swapState(); |
| } |
| |
| BackingStorePrivate::TileIndexList BackingStorePrivate::indexesForBackingStoreRect(const Platform::IntRect& backingStoreRect) const |
| { |
| TileIndexList indexes; |
| int numberOfTilesWide = backingStoreRect.width() / tileWidth(); |
| int numberOfTilesHigh = backingStoreRect.height() / tileHeight(); |
| for (int y = 0; y < numberOfTilesHigh; ++y) { |
| for (int x = 0; x < numberOfTilesWide; ++x) { |
| TileIndex index(x, y); |
| indexes.append(index); |
| } |
| } |
| return indexes; |
| } |
| |
| Platform::IntPoint BackingStorePrivate::originOfLastRenderForTile(const TileIndex& index, |
| BackingStoreTile* tile, |
| const Platform::IntRect& backingStoreRect) const |
| { |
| return originOfTile(indexOfLastRenderForTile(index, tile), backingStoreRect); |
| } |
| |
| TileIndex BackingStorePrivate::indexOfLastRenderForTile(const TileIndex& index, BackingStoreTile* tile) const |
| { |
| return TileIndex(index.i() + tile->horizontalShift(), index.j() + tile->verticalShift()); |
| } |
| |
| TileIndex BackingStorePrivate::indexOfTile(const Platform::IntPoint& origin, |
| const Platform::IntRect& backingStoreRect) const |
| { |
| int offsetX = origin.x() - backingStoreRect.x(); |
| int offsetY = origin.y() - backingStoreRect.y(); |
| if (offsetX) |
| offsetX = offsetX / tileWidth(); |
| if (offsetY) |
| offsetY = offsetY / tileHeight(); |
| return TileIndex(offsetX, offsetY); |
| } |
| |
| void BackingStorePrivate::clearAndUpdateTileOfNotRenderedRegion(const TileIndex& index, BackingStoreTile* tile, |
| const Platform::IntRectRegion& tileNotRenderedRegion, |
| const Platform::IntRect& backingStoreRect, |
| bool update) |
| { |
| if (tileNotRenderedRegion.isEmpty()) |
| return; |
| |
| // Intersect the tile with the not rendered region to get the areas |
| // of the tile that we need to clear. |
| IntRectList tileNotRenderedRegionRects = tileNotRenderedRegion.rects(); |
| for (size_t i = 0; i < tileNotRenderedRegionRects.size(); ++i) { |
| Platform::IntRect tileNotRenderedRegionRect = tileNotRenderedRegionRects.at(i); |
| // Clear the render queue of this rect. |
| m_renderQueue->clear(tileNotRenderedRegionRect, true /*clearRegularRenderJobs*/); |
| |
| if (update) { |
| // Add it again as a regular render job. |
| m_renderQueue->addToQueue(RenderQueue::RegularRender, tileNotRenderedRegionRect); |
| } |
| } |
| |
| // Find the origin of this tile. |
| Platform::IntPoint origin = originOfTile(index, backingStoreRect); |
| |
| // Map to tile coordinates. |
| Platform::IntRectRegion translatedRegion(tileNotRenderedRegion); |
| translatedRegion.move(-origin.x(), -origin.y()); |
| |
| // If the region in question is already marked as not rendered, return early |
| if (Platform::IntRectRegion::intersectRegions(tile->frontBuffer()->renderedRegion(), translatedRegion).isEmpty()) |
| return; |
| |
| // Clear the tile of this region. The back buffer region is invalid anyway, but the front |
| // buffer must not be manipulated without synchronization with the compositing thread, or |
| // we have a race. |
| // Instead of using the customary sequence of copy-back, modify and swap, we send a synchronous |
| // message to the compositing thread to avoid the copy-back step and save memory bandwidth. |
| // The trade-off is that the WebKit thread might wait a little longer for the compositing thread |
| // than it would from a waitForCurrentMessage() call. |
| |
| ASSERT(Platform::webKitThreadMessageClient()->isCurrentThread()); |
| if (!Platform::webKitThreadMessageClient()->isCurrentThread()) |
| return; |
| |
| Platform::userInterfaceThreadMessageClient()->dispatchSyncMessage( |
| Platform::createMethodCallMessage(&BackingStorePrivate::clearRenderedRegion, |
| this, tile, translatedRegion)); |
| } |
| |
| void BackingStorePrivate::clearRenderedRegion(BackingStoreTile* tile, const Platform::IntRectRegion& region) |
| { |
| ASSERT(Platform::userInterfaceThreadMessageClient()->isCurrentThread()); |
| if (!Platform::userInterfaceThreadMessageClient()->isCurrentThread()) |
| return; |
| |
| tile->frontBuffer()->clearRenderedRegion(region); |
| } |
| |
| bool BackingStorePrivate::isCurrentVisibleJob(const TileIndex& index, BackingStoreTile* tile, const Platform::IntRect& backingStoreRect) const |
| { |
| // First check if the whole rect is in the queue. |
| Platform::IntRect wholeRect = Platform::IntRect(originOfTile(index, backingStoreRect), tileSize()); |
| if (m_renderQueue->isCurrentVisibleScrollJob(wholeRect) || m_renderQueue->isCurrentVisibleScrollJobCompleted(wholeRect)) |
| return true; |
| |
| // Second check if the individual parts of the non-rendered region are in the regular queue. |
| IntRectList tileNotRenderedRegionRects = tile->frontBuffer()->notRenderedRegion().rects(); |
| for (size_t i = 0; i < tileNotRenderedRegionRects.size(); ++i) { |
| Platform::IntRect tileNotRenderedRegionRect = tileNotRenderedRegionRects.at(i); |
| Platform::IntPoint origin = originOfTile(index, backingStoreRect); |
| |
| // Map to transformed contents coordinates. |
| tileNotRenderedRegionRect.move(origin.x(), origin.y()); |
| |
| if (!m_renderQueue->isCurrentRegularRenderJob(tileNotRenderedRegionRect)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void BackingStorePrivate::scrollBackingStore(int deltaX, int deltaY) |
| { |
| ASSERT(BlackBerry::Platform::webKitThreadMessageClient()->isCurrentThread()); |
| |
| if (!m_webPage->isVisible()) |
| return; |
| |
| if (!isActive()) { |
| m_webPage->d->setShouldResetTilesWhenShown(true); |
| return; |
| } |
| |
| // Calculate our new preferred matrix dimension. |
| if (deltaX || deltaY) |
| m_preferredTileMatrixDimension = abs(deltaX) > abs(deltaY) ? Horizontal : Vertical; |
| |
| // Calculate our preferred matrix geometry. |
| Divisor divisor = bestDivisor(expandedContentsSize(), |
| tileWidth(), tileHeight(), |
| minimumNumberOfTilesWide(), minimumNumberOfTilesHigh(), |
| m_preferredTileMatrixDimension); |
| |
| #if DEBUG_TILEMATRIX |
| BBLOG(BlackBerry::Platform::LogLevelCritical, "BackingStorePrivate::scrollBackingStore divisor %dx%d", |
| divisor.first, |
| divisor.second); |
| #endif |
| |
| // Initialize a rect with that new geometry. |
| Platform::IntRect backingStoreRect(0, 0, divisor.first * tileWidth(), divisor.second * tileHeight()); |
| |
| // Scroll that rect so that it fits our contents and viewport and scroll delta. |
| backingStoreRect = backingStoreRectForScroll(deltaX, deltaY, backingStoreRect); |
| |
| ASSERT(!backingStoreRect.isEmpty()); |
| |
| setBackingStoreRect(backingStoreRect, m_webPage->d->currentScale()); |
| } |
| |
| bool BackingStorePrivate::renderDirectToWindow(const Platform::IntRect& rect) |
| { |
| requestLayoutIfNeeded(); |
| |
| Platform::IntRect dirtyRect = rect; |
| dirtyRect.intersect(unclippedVisibleContentsRect()); |
| |
| if (dirtyRect.isEmpty()) |
| return false; |
| |
| Platform::IntRect screenRect = m_client->mapFromTransformedContentsToTransformedViewport(dirtyRect); |
| windowFrontBufferState()->clearBlittedRegion(screenRect); |
| |
| paintDefaultBackground(dirtyRect, m_webPage->webkitThreadViewportAccessor(), true /*flush*/); |
| |
| const Platform::IntPoint origin = unclippedVisibleContentsRect().location(); |
| // We don't need a buffer since we're direct rendering to window. |
| renderContents(0, origin, dirtyRect); |
| windowBackBufferState()->addBlittedRegion(screenRect); |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| m_isDirectRenderingAnimationMessageScheduled = false; |
| |
| if (m_webPage->d->isAcceleratedCompositingActive()) { |
| BlackBerry::Platform::userInterfaceThreadMessageClient()->dispatchSyncMessage( |
| BlackBerry::Platform::createMethodCallMessage( |
| &BackingStorePrivate::drawAndBlendLayersForDirectRendering, |
| this, dirtyRect)); |
| } |
| #endif |
| |
| invalidateWindow(screenRect); |
| return true; |
| } |
| |
| bool BackingStorePrivate::render(const Platform::IntRect& rect) |
| { |
| if (!m_webPage->isVisible()) |
| return false; |
| |
| requestLayoutIfNeeded(); |
| |
| if (shouldDirectRenderingToWindow()) |
| return renderDirectToWindow(rect); |
| |
| // If direct rendering is off, even though we're not active, someone else |
| // has to render the root layer. There are no tiles available for us to |
| // draw to. |
| if (!isActive()) |
| return false; |
| |
| TileRectList tileRectList = mapFromTransformedContentsToTiles(rect); |
| if (tileRectList.isEmpty()) |
| return false; |
| |
| #if DEBUG_BACKINGSTORE |
| BBLOG(BlackBerry::Platform::LogLevelCritical, |
| "BackingStorePrivate::render rect=(%d,%d %dx%d), m_suspendBackingStoreUpdates = %s", |
| rect.x(), rect.y(), rect.width(), rect.height(), |
| m_suspendBackingStoreUpdates ? "true" : "false"); |
| #endif |
| |
| BackingStoreGeometry* currentState = frontState(); |
| TileMap currentMap = currentState->tileMap(); |
| double currentScale = currentState->scale(); |
| |
| for (size_t i = 0; i < tileRectList.size(); ++i) { |
| TileRect tileRect = tileRectList[i]; |
| TileIndex index = tileRect.first; |
| Platform::IntRect dirtyTileRect = tileRect.second; |
| BackingStoreTile* tile = currentMap.get(index); |
| |
| // This dirty tile rect is in tile coordinates, but it needs to be in |
| // transformed contents coordinates. |
| Platform::IntRect dirtyRect = mapFromTilesToTransformedContents(tileRect); |
| |
| // If the tile has been created, but this is the first time we are painting on it |
| // then it hasn't been given a default background yet so that we can save time during |
| // startup. That's why we are doing it here instead... |
| if (!tile->backgroundPainted()) |
| tile->paintBackground(); |
| |
| tile->backBuffer()->setScale(currentScale); |
| |
| // Paint default background if contents rect is empty. |
| if (!expandedContentsRect().isEmpty()) { |
| // Otherwise we should clip the contents size and render the content. |
| dirtyRect.intersect(expandedContentsRect()); |
| |
| dirtyTileRect.intersect(tileContentsRect(index, expandedContentsRect(), currentState)); |
| |
| // We probably have extra tiles since the contents size is so small. |
| // Save some cycles here... |
| if (dirtyRect.isEmpty()) |
| continue; |
| } |
| |
| BlackBerry::Platform::Graphics::Buffer* nativeBuffer |
| = tile->backBuffer()->nativeBuffer(); |
| |
| // TODO: This code is only needed for EGLImage code path, but preferrably BackingStore |
| // should not know that, and the synchronization should be in BlackBerry::Platform::Graphics |
| // if possible. |
| if (isOpenGLCompositing()) |
| SurfacePool::globalSurfacePool()->waitForBuffer(tile->backBuffer()); |
| |
| // Modify the buffer only after we've waited for the buffer to become available above. |
| |
| // If we're not yet committed, then commit only after the tile has back buffer has been |
| // swapped in so it has some valid content. |
| // Otherwise the compositing thread could pick up the tile while its front buffer is still invalid. |
| bool wasCommitted = tile->isCommitted(); |
| if (wasCommitted) |
| copyPreviousContentsToBackSurfaceOfTile(dirtyTileRect, tile); |
| else |
| tile->backBuffer()->clearRenderedRegion(); |
| |
| // FIXME: modify render to take a Vector<IntRect> parameter so we're not recreating |
| // GraphicsContext on the stack each time. |
| renderContents(nativeBuffer, originOfTile(index), dirtyRect); |
| |
| // Add the newly rendered region to the tile so it can keep track for blits. |
| tile->backBuffer()->addRenderedRegion(dirtyTileRect); |
| |
| // Thanks to the copyPreviousContentsToBackSurfaceOfTile() call above, we know that |
| // the rendered region of the back buffer contains the rendered region of the front buffer. |
| // Assert this just to make sure. |
| // For previously uncommitted tiles, the front buffer's rendered region is not relevant. |
| ASSERT(!wasCommitted || tile->backBuffer()->isRendered(tile->frontBuffer()->renderedRegion(), currentScale)); |
| |
| // We will need a swap here because of the shared back buffer. |
| tile->swapBuffers(); |
| |
| if (!wasCommitted) { |
| // Commit the tile only after it has valid front buffer contents. Now, the compositing thread |
| // can finally start blitting this tile. |
| tile->clearShift(); |
| tile->setCommitted(true); |
| } |
| |
| // Before clearing the render region, wait for the compositing thread to stop using the |
| // buffer, in order to avoid a race on its rendered region. |
| BlackBerry::Platform::userInterfaceThreadMessageClient()->syncToCurrentMessage(); |
| tile->backBuffer()->clearRenderedRegion(); |
| } |
| |
| return true; |
| } |
| |
| void BackingStorePrivate::requestLayoutIfNeeded() const |
| { |
| m_webPage->d->requestLayoutIfNeeded(); |
| } |
| |
| bool BackingStorePrivate::renderVisibleContents() |
| { |
| Platform::IntRect renderRect = shouldDirectRenderingToWindow() ? visibleContentsRect() : visibleTilesRect(); |
| if (render(renderRect)) { |
| m_renderQueue->clear(renderRect, true /*clearRegularRenderJobs*/); |
| return true; |
| } |
| return false; |
| } |
| |
| bool BackingStorePrivate::renderBackingStore() |
| { |
| return render(frontState()->backingStoreRect()); |
| } |
| |
| void BackingStorePrivate::copyPreviousContentsToBackSurfaceOfWindow() |
| { |
| Platform::IntRectRegion previousContentsRegion |
| = Platform::IntRectRegion::subtractRegions(windowFrontBufferState()->blittedRegion(), windowBackBufferState()->blittedRegion()); |
| |
| if (previousContentsRegion.isEmpty()) |
| return; |
| |
| if (Window* window = m_webPage->client()->window()) |
| window->copyFromFrontToBack(previousContentsRegion); |
| windowBackBufferState()->addBlittedRegion(previousContentsRegion); |
| } |
| |
| void BackingStorePrivate::copyPreviousContentsToBackSurfaceOfTile(const Platform::IntRect& rect, |
| BackingStoreTile* tile) |
| { |
| Platform::IntRectRegion previousContentsRegion |
| = Platform::IntRectRegion::subtractRegions(tile->frontBuffer()->renderedRegion(), rect); |
| |
| IntRectList previousContentsRects = previousContentsRegion.rects(); |
| for (size_t i = 0; i < previousContentsRects.size(); ++i) { |
| Platform::IntRect previousContentsRect = previousContentsRects.at(i); |
| tile->backBuffer()->addRenderedRegion(previousContentsRect); |
| |
| BlackBerry::Platform::Graphics::blitToBuffer( |
| tile->backBuffer()->nativeBuffer(), previousContentsRect, |
| tile->frontBuffer()->nativeBuffer(), previousContentsRect); |
| } |
| } |
| |
| void BackingStorePrivate::paintDefaultBackground(const Platform::IntRect& dstRect, Platform::ViewportAccessor* viewportAccessor, bool flush) |
| { |
| Platform::IntRect clippedDstRect = dstRect; |
| |
| // Because of rounding it is possible that overScrollRect could be off-by-one larger |
| // than the surface size of the window. We prevent this here, by clamping |
| // it to ensure that can't happen. |
| clippedDstRect.intersect(Platform::IntRect(Platform::IntPoint(0, 0), surfaceSize())); |
| |
| if (clippedDstRect.isEmpty()) |
| return; |
| |
| // We have to paint the default background in the case of overzoom and |
| // make sure it is invalidated. |
| const Platform::IntRect pixelContentsRect = viewportAccessor->pixelContentsRect(); |
| Platform::IntRectRegion overScrollRegion = Platform::IntRectRegion::subtractRegions( |
| clippedDstRect, viewportAccessor->pixelViewportFromContents(pixelContentsRect)); |
| |
| IntRectList overScrollRects = overScrollRegion.rects(); |
| for (size_t i = 0; i < overScrollRects.size(); ++i) { |
| Platform::IntRect overScrollRect = overScrollRects.at(i); |
| |
| if (m_webPage->settings()->isEnableDefaultOverScrollBackground()) { |
| fillWindow(BlackBerry::Platform::Graphics::DefaultBackgroundPattern, |
| overScrollRect, overScrollRect.location(), 1.0 /*contentsScale*/); |
| } else { |
| Color color(m_webPage->settings()->overScrollColor()); |
| clearWindow(overScrollRect, color.red(), color.green(), color.blue(), color.alpha()); |
| } |
| } |
| } |
| |
| void BackingStorePrivate::blitVisibleContents(bool force) |
| { |
| // Blitting must never happen for direct rendering case. |
| // Use invalidateWindow() instead. |
| ASSERT(!shouldDirectRenderingToWindow()); |
| if (shouldDirectRenderingToWindow()) { |
| BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelCritical, |
| "BackingStore::blitVisibleContents operation not supported in direct rendering mode"); |
| return; |
| } |
| |
| if (!m_webPage->isVisible() || m_suspendScreenUpdates) { |
| // Avoid client going into busy loop while blit is impossible. |
| if (force) |
| m_hasBlitJobs = false; |
| return; |
| } |
| |
| if (!BlackBerry::Platform::userInterfaceThreadMessageClient()->isCurrentThread()) { |
| BlackBerry::Platform::userInterfaceThreadMessageClient()->dispatchMessage( |
| BlackBerry::Platform::createMethodCallMessage( |
| &BackingStorePrivate::blitVisibleContents, this, force)); |
| return; |
| } |
| |
| if (m_defersBlit && !force) { |
| #if USE(ACCELERATED_COMPOSITING) |
| // If there's a WebPageCompositorClient, let it schedule the blit. |
| if (WebPageCompositorPrivate* compositor = m_webPage->d->compositor()) { |
| if (WebPageCompositorClient* client = compositor->client()) { |
| client->invalidate(0); |
| return; |
| } |
| } |
| #endif |
| |
| m_hasBlitJobs = true; |
| return; |
| } |
| |
| m_hasBlitJobs = false; |
| |
| Platform::ViewportAccessor* viewportAccessor = m_webPage->client()->userInterfaceViewportAccessor(); |
| const Platform::IntRect dstRect = viewportAccessor->destinationSurfaceRect(); |
| |
| const Platform::IntRect pixelViewportRect = viewportAccessor->pixelViewportRect(); |
| const Platform::FloatRect documentViewportRect = viewportAccessor->documentFromPixelContents(pixelViewportRect); |
| Platform::IntRect pixelSrcRect = pixelViewportRect; |
| Platform::FloatRect documentSrcRect = documentViewportRect; |
| |
| #if DEBUG_VISUALIZE |
| // Substitute a srcRect that consists of the whole backingstore geometry |
| // instead of the normal viewport so we can visualize the entire |
| // backingstore and what it is doing when we scroll and zoom! |
| Platform::ViewportAccessor* debugViewportAccessor = new BackingStoreVisualizationViewportAccessor(viewportAccessor, this); |
| if (isActive()) { |
| viewportAccessor = debugViewportAccessor; |
| documentSrcRect = debugViewportAccessor->documentViewportRect(); |
| pixelSrcRect = debugViewportAccessor->pixelViewportRect(); |
| } |
| #endif |
| |
| #if DEBUG_BACKINGSTORE |
| BlackBerry::Platform::logAlways(BlackBerry::Platform::LogLevelCritical, |
| "BackingStorePrivate::blitVisibleContents(): dstRect=(%d,%d) %dx%d, documentSrcRect=(%f, %f) %f x %f, scale=%f", |
| dstRect.x(), dstRect.y(), dstRect.width(), dstRect.height(), |
| documentSrcRect.x(), documentSrcRect.y(), documentSrcRect.width(), documentSrcRect.height(), |
| viewportAccessor->scale()); |
| #endif |
| |
| Vector<TileBuffer*> blittedTiles; |
| |
| if (isActive() && !m_webPage->d->compositorDrawsRootLayer()) { |
| paintDefaultBackground(dstRect, viewportAccessor, false /*flush*/); |
| |
| BackingStoreGeometry* currentState = frontState(); |
| TileMap currentMap = currentState->tileMap(); |
| double currentScale = currentState->scale(); |
| |
| const Platform::IntRect transformedContentsRect = Platform::IntRect(Platform::IntPoint(0, 0), m_client->transformedContentsSize()); |
| |
| // For blitting backingstore tiles, we need the srcRect to be specified |
| // in backingstore tile pixel coordinates. If our viewport accessor is |
| // at a different scale, we calculate those coordinates by ourselves. |
| const Platform::IntRect transformedSrcRect = currentScale == viewportAccessor->scale() |
| ? pixelSrcRect |
| : viewportAccessor->roundFromDocumentContents(documentSrcRect, currentScale); |
| |
| Platform::IntRect clippedTransformedSrcRect = transformedSrcRect; |
| const Platform::IntPoint origin = transformedSrcRect.location(); |
| |
| // FIXME: This should not explicitly depend on WebCore::. |
| TransformationMatrix transformation; |
| if (!transformedSrcRect.isEmpty()) |
| transformation = TransformationMatrix::rectToRect(FloatRect(FloatPoint(0.0, 0.0), WebCore::IntSize(transformedSrcRect.size())), WebCore::IntRect(dstRect)); |
| |
| #if DEBUG_CHECKERBOARD |
| bool blitCheckered = false; |
| #endif |
| |
| // Don't clip to contents if it is empty so we can still paint default background. |
| if (!transformedContentsRect.isEmpty()) { |
| clippedTransformedSrcRect.intersect(transformedContentsRect); |
| if (clippedTransformedSrcRect.isEmpty()) { |
| invalidateWindow(dstRect); |
| return; |
| } |
| |
| Platform::IntRectRegion transformedSrcRegion = clippedTransformedSrcRect; |
| Platform::IntRectRegion backingStoreRegion = currentState->backingStoreRect(); |
| Platform::IntRectRegion checkeredRegion |
| = Platform::IntRectRegion::subtractRegions(transformedSrcRegion, backingStoreRegion); |
| |
| // Blit checkered to those parts that are not covered by the backingStoreRect. |
| IntRectList checkeredRects = checkeredRegion.rects(); |
| for (size_t i = 0; i < checkeredRects.size(); ++i) { |
| Platform::IntRect clippedDstRect = transformation.mapRect(Platform::IntRect( |
| Platform::IntPoint(checkeredRects.at(i).x() - origin.x(), checkeredRects.at(i).y() - origin.y()), |
| checkeredRects.at(i).size())); |
| // To eliminate 1 pixel inflation due to transformation rounding. |
| clippedDstRect.intersect(dstRect); |
| #if DEBUG_CHECKERBOARD |
| blitCheckered = true; |
| #endif |
| |
| fillWindow(BlackBerry::Platform::Graphics::CheckerboardPattern, |
| clippedDstRect, checkeredRects.at(i).location(), transformation.a()); |
| } |
| } |
| |
| // Get the list of tile rects that makeup the content. |
| TileRectList tileRectList = mapFromTransformedContentsToTiles(clippedTransformedSrcRect, currentState); |
| for (size_t i = 0; i < tileRectList.size(); ++i) { |
| TileRect tileRect = tileRectList[i]; |
| TileIndex index = tileRect.first; |
| Platform::IntRect dirtyTileRect = tileRect.second; |
| BackingStoreTile* tile = currentMap.get(index); |
| TileBuffer* tileBuffer = tile->frontBuffer(); |
| |
| // This dirty rect is in tile coordinates, but it needs to be in |
| // transformed contents coordinates. |
| Platform::IntRect dirtyRect |
| = mapFromTilesToTransformedContents(tileRect, currentState->backingStoreRect()); |
| |
| // Don't clip to contents if it is empty so we can still paint default background. |
| if (!transformedContentsRect.isEmpty()) { |
| // Otherwise we should clip the contents size and blit. |
| dirtyRect.intersect(transformedContentsRect); |
| |
| // We probably have extra tiles since the contents size is so small. |
| // Save some cycles here... |
| if (dirtyRect.isEmpty()) |
| continue; |
| } |
| |
| // Now, this dirty rect is in transformed coordinates relative to the |
| // transformed contents, but ultimately it needs to be transformed |
| // coordinates relative to the viewport. |
| dirtyRect.move(-origin.x(), -origin.y()); |
| |
| // Save some cycles here... |
| if (dirtyRect.isEmpty() || dirtyTileRect.isEmpty()) |
| continue; |
| |
| TileRect wholeTileRect; |
| wholeTileRect.first = index; |
| wholeTileRect.second = this->tileRect(); |
| |
| bool committed = tile->isCommitted(); |
| bool rendered = tileBuffer->isRendered(dirtyTileRect, currentScale); |
| bool paintCheckered = !committed || !rendered; |
| |
| if (paintCheckered) { |
| Platform::IntRect dirtyRectT = transformation.mapRect(dirtyRect); |
| |
| if (!transformation.isIdentity()) { |
| // Because of rounding it is possible that dirtyRect could be off-by-one larger |
| // than the surface size of the dst buffer. We prevent this here, by clamping |
| // it to ensure that can't happen. |
| dirtyRectT.intersect(Platform::IntRect(Platform::IntPoint(0, 0), surfaceSize())); |
| } |
| const Platform::IntPoint contentsOrigin(dirtyRect.x() + origin.x(), dirtyRect.y() + origin.y()); |
| #if DEBUG_CHECKERBOARD |
| blitCheckered = true; |
| #endif |
| fillWindow(BlackBerry::Platform::Graphics::CheckerboardPattern, |
| dirtyRectT, contentsOrigin, transformation.a()); |
| } |
| |
| // Blit the visible buffer here if we have visible zoom jobs. |
| if (m_renderQueue->hasCurrentVisibleZoomJob()) { |
| |
| // Needs to be in same coordinate system as dirtyRect. |
| Platform::IntRect visibleTileBufferRect = m_visibleTileBufferRect; |
| visibleTileBufferRect.move(-origin.x(), -origin.y()); |
| |
| // Clip to the visibleTileBufferRect. |
| dirtyRect.intersect(visibleTileBufferRect); |
| |
| // Clip to the dirtyRect. |
| visibleTileBufferRect.intersect(dirtyRect); |
| |
| if (!dirtyRect.isEmpty() && !visibleTileBufferRect.isEmpty()) { |
| BackingStoreTile* visibleTileBuffer |
| = SurfacePool::globalSurfacePool()->visibleTileBuffer(); |
| ASSERT(visibleTileBuffer->size() == visibleContentsRect().size()); |
| |
| // The offset of the current viewport with the visble tile buffer. |
| Platform::IntPoint difference = origin - m_visibleTileBufferRect.location(); |
| Platform::IntSize offset = Platform::IntSize(difference.x(), difference.y()); |
| |
| // Map to the visibleTileBuffer coordinates. |
| Platform::IntRect dirtyTileRect = visibleTileBufferRect; |
| dirtyTileRect.move(offset.width(), offset.height()); |
| |
| Platform::IntRect dirtyRectT = transformation.mapRect(dirtyRect); |
| |
| if (!transformation.isIdentity()) { |
| // Because of rounding it is possible that dirtyRect could be off-by-one larger |
| // than the surface size of the dst buffer. We prevent this here, by clamping |
| // it to ensure that can't happen. |
| dirtyRectT.intersect(Platform::IntRect(Platform::IntPoint(0, 0), surfaceSize())); |
| } |
| |
| blitToWindow(dirtyRectT, |
| visibleTileBuffer->frontBuffer()->nativeBuffer(), |
| dirtyTileRect, |
| false /*blend*/, 255); |
| } |
| } else if (committed) { |
| // Intersect the rendered region. |
| Platform::IntRectRegion renderedRegion = tileBuffer->renderedRegion(); |
| IntRectList dirtyRenderedRects = renderedRegion.rects(); |
| for (size_t i = 0; i < dirtyRenderedRects.size(); ++i) { |
| TileRect tileRect; |
| tileRect.first = index; |
| tileRect.second = intersection(dirtyTileRect, dirtyRenderedRects.at(i)); |
| if (tileRect.second.isEmpty()) |
| continue; |
| // Blit the rendered parts. |
| blitTileRect(tileBuffer, tileRect, origin, transformation, currentState); |
| } |
| blittedTiles.append(tileBuffer); |
| } |
| } |
| } |
| |
| // TODO: This code is only needed for EGLImage code path, but preferrably BackingStore |
| // should not know that, and the synchronization should be in BlackBerry::Platform::Graphics |
| // if possible. |
| if (isOpenGLCompositing()) |
| SurfacePool::globalSurfacePool()->notifyBuffersComposited(blittedTiles); |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| if (WebPageCompositorPrivate* compositor = m_webPage->d->compositor()) { |
| compositor->drawLayers(dstRect, documentSrcRect); |
| if (compositor->drawsRootLayer()) |
| paintDefaultBackground(dstRect, viewportAccessor, false /*flush*/); |
| } |
| #endif |
| |
| #if ENABLE_SCROLLBARS |
| if (isScrollingOrZooming() && m_client->isMainFrame()) { |
| blitHorizontalScrollbar(); |
| blitVerticalScrollbar(); |
| } |
| #endif |
| |
| #if DEBUG_VISUALIZE |
| if (debugViewportAccessor) { |
| Platform::Graphics::Buffer* targetBuffer = buffer(); |
| Platform::IntRect wkViewport = debugViewportAccessor->roundToPixelFromDocumentContents(Platform::IntRect(m_client->visibleContentsRect())); |
| Platform::IntRect uiViewport = debugViewportAccessor->roundToPixelFromDocumentContents(documentViewportRect); |
| wkViewport.move(-pixelSrcRect.x(), -pixelSrcRect.y()); |
| uiViewport.move(-pixelSrcRect.x(), -pixelSrcRect.y()); |
| |
| // Draw a blue rect for the webkit thread viewport. |
| Platform::Graphics::clearBuffer(targetBuffer, Platform::IntRect(wkViewport.x(), wkViewport.y(), wkViewport.width(), 1), 0, 0, 255, 255); |
| Platform::Graphics::clearBuffer(targetBuffer, Platform::IntRect(wkViewport.x(), wkViewport.y(), 1, wkViewport.height()), 0, 0, 255, 255); |
| Platform::Graphics::clearBuffer(targetBuffer, Platform::IntRect(wkViewport.x(), wkViewport.bottom() - 1, wkViewport.width(), 1), 0, 0, 255, 255); |
| Platform::Graphics::clearBuffer(targetBuffer, Platform::IntRect(wkViewport.right() - 1, wkViewport.y(), 1, wkViewport.height()), 0, 0, 255, 255); |
| |
| // Draw a red rect for the ui thread viewport. |
| Platform::Graphics::clearBuffer(targetBuffer, Platform::IntRect(uiViewport.x(), uiViewport.y(), uiViewport.width(), 1), 255, 0, 0, 255); |
| Platform::Graphics::clearBuffer(targetBuffer, Platform::IntRect(uiViewport.x(), uiViewport.y(), 1, uiViewport.height()), 255, 0, 0, 255); |
| Platform::Graphics::clearBuffer(targetBuffer, Platform::IntRect(uiViewport.x(), uiViewport.bottom() - 1, uiViewport.width(), 1), 255, 0, 0, 255); |
| Platform::Graphics::clearBuffer(targetBuffer, Platform::IntRect(uiViewport.right() - 1, uiViewport.y(), 1, uiViewport.height()), 255, 0, 0, 255); |
| |
| delete debugViewportAccessor; |
| } |
| #endif |
| |
| #if DEBUG_CHECKERBOARD |
| static double lastCheckeredTime = 0; |
| |
| if (blitCheckered && !lastCheckeredTime) { |
| lastCheckeredTime = WTF::currentTime(); |
| BBLOG(BlackBerry::Platform::LogLevelCritical, |
| "Blitting checkered pattern at %f\n", lastCheckeredTime); |
| } else if (blitCheckered && lastCheckeredTime) { |
| BBLOG(BlackBerry::Platform::LogLevelCritical, |
| "Blitting checkered pattern at %f\n", WTF::currentTime()); |
| } else if (!blitCheckered && lastCheckeredTime) { |
| double time = WTF::currentTime(); |
| BBLOG(BlackBerry::Platform::LogLevelCritical, |
| "Blitting over checkered pattern at %f took %f\n", time, time - lastCheckeredTime); |
| lastCheckeredTime = 0; |
| } |
| #endif |
| |
| invalidateWindow(dstRect); |
| } |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| void BackingStorePrivate::compositeContents(WebCore::LayerRenderer* layerRenderer, const WebCore::TransformationMatrix& transform, const WebCore::FloatRect& contents, bool contentsOpaque) |
| { |
| const Platform::IntRect transformedContentsRect = Platform::IntRect(Platform::IntPoint(0, 0), m_client->transformedContentsSize()); |
| Platform::IntRect transformedContents = enclosingIntRect(m_webPage->d->m_transformationMatrix->mapRect(contents)); |
| transformedContents.intersect(transformedContentsRect); |
| if (transformedContents.isEmpty()) |
| return; |
| |
| if (!isActive()) |
| return; |
| |
| if (m_webPage->d->compositorDrawsRootLayer()) |
| return; |
| |
| BackingStoreGeometry* currentState = frontState(); |
| TileMap currentMap = currentState->tileMap(); |
| Vector<TileBuffer*> compositedTiles; |
| |
| Platform::IntRectRegion transformedContentsRegion = transformedContents; |
| Platform::IntRectRegion backingStoreRegion = currentState->backingStoreRect(); |
| Platform::IntRectRegion checkeredRegion |
| = Platform::IntRectRegion::subtractRegions(transformedContentsRegion, backingStoreRegion); |
| |
| // Blit checkered to those parts that are not covered by the backingStoreRect. |
| IntRectList checkeredRects = checkeredRegion.rects(); |
| for (size_t i = 0; i < checkeredRects.size(); ++i) |
| layerRenderer->drawCheckerboardPattern(transform, m_webPage->d->mapFromTransformedFloatRect(WebCore::IntRect(checkeredRects.at(i)))); |
| |
| // Get the list of tile rects that makeup the content. |
| TileRectList tileRectList = mapFromTransformedContentsToTiles(transformedContents, currentState); |
| for (size_t i = 0; i < tileRectList.size(); ++i) { |
| TileRect tileRect = tileRectList[i]; |
| TileIndex index = tileRect.first; |
| Platform::IntRect dirtyTileRect = tileRect.second; |
| BackingStoreTile* tile = currentMap.get(index); |
| TileBuffer* tileBuffer = tile->frontBuffer(); |
| |
| // This dirty rect is in tile coordinates, but it needs to be in |
| // transformed contents coordinates. |
| Platform::IntRect dirtyRect = mapFromTilesToTransformedContents(tileRect, currentState->backingStoreRect()); |
| |
| if (!dirtyRect.intersects(transformedContents)) |
| continue; |
| |
| TileRect wholeTileRect; |
| wholeTileRect.first = index; |
| wholeTileRect.second = this->tileRect(); |
| |
| Platform::IntRect wholeRect = mapFromTilesToTransformedContents(wholeTileRect, currentState->backingStoreRect()); |
| |
| bool committed = tile->isCommitted(); |
| |
| if (!committed) |
| layerRenderer->drawCheckerboardPattern(transform, m_webPage->d->mapFromTransformedFloatRect(Platform::FloatRect(dirtyRect))); |
| else { |
| layerRenderer->compositeBuffer(transform, m_webPage->d->mapFromTransformedFloatRect(Platform::FloatRect(wholeRect)), tileBuffer->nativeBuffer(), contentsOpaque, 1.0f); |
| compositedTiles.append(tileBuffer); |
| // Intersect the rendered region. |
| Platform::IntRectRegion notRenderedRegion = Platform::IntRectRegion::subtractRegions(dirtyTileRect, tileBuffer->renderedRegion()); |
| IntRectList notRenderedRects = notRenderedRegion.rects(); |
| for (size_t i = 0; i < notRenderedRects.size(); ++i) |
| layerRenderer->drawCheckerboardPattern(transform, m_webPage->d->mapFromTransformedFloatRect(Platform::FloatRect(notRenderedRects.at(i)))); |
| } |
| } |
| |
| SurfacePool::globalSurfacePool()->notifyBuffersComposited(compositedTiles); |
| } |
| #endif |
| |
| Platform::IntRect BackingStorePrivate::blitTileRect(TileBuffer* tileBuffer, |
| const TileRect& tileRect, |
| const Platform::IntPoint& origin, |
| const WebCore::TransformationMatrix& matrix, |
| BackingStoreGeometry* state) |
| { |
| if (!m_webPage->isVisible() || !isActive()) |
| return Platform::IntRect(); |
| |
| Platform::IntRect dirtyTileRect = tileRect.second; |
| |
| // This dirty rect is in tile coordinates, but it needs to be in |
| // transformed contents coordinates. |
| Platform::IntRect dirtyRect = mapFromTilesToTransformedContents(tileRect, state->backingStoreRect()); |
| |
| // Now, this dirty rect is in transformed coordinates relative to the |
| // transformed contents, but ultimately it needs to be transformed |
| // coordinates relative to the viewport. |
| dirtyRect.move(-origin.x(), -origin.y()); |
| dirtyRect = matrix.mapRect(dirtyRect); |
| |
| if (!matrix.isIdentity()) { |
| // Because of rounding it is possible that dirtyRect could be off-by-one larger |
| // than the surface size of the dst buffer. We prevent this here, by clamping |
| // it to ensure that can't happen. |
| dirtyRect.intersect(Platform::IntRect(Platform::IntPoint(0, 0), surfaceSize())); |
| } |
| |
| ASSERT(!dirtyRect.isEmpty()); |
| ASSERT(!dirtyTileRect.isEmpty()); |
| if (dirtyRect.isEmpty() || dirtyTileRect.isEmpty()) |
| return Platform::IntRect(); |
| |
| blitToWindow(dirtyRect, tileBuffer->nativeBuffer(), dirtyTileRect, |
| false /*blend*/, 255); |
| return dirtyRect; |
| } |
| |
| void BackingStorePrivate::blitHorizontalScrollbar() |
| { |
| if (!m_webPage->isVisible()) |
| return; |
| |
| m_webPage->client()->drawHorizontalScrollbar(); |
| } |
| |
| void BackingStorePrivate::blitVerticalScrollbar() |
| { |
| if (!m_webPage->isVisible()) |
| return; |
| |
| m_webPage->client()->drawVerticalScrollbar(); |
| } |
| |
| bool BackingStorePrivate::isTileVisible(const TileIndex& index) const |
| { |
| TileRect tileRect; |
| tileRect.first = index; |
| tileRect.second = this->tileRect(); |
| return mapFromTilesToTransformedContents(tileRect).intersects(visibleContentsRect()); |
| } |
| |
| bool BackingStorePrivate::isTileVisible(const Platform::IntPoint& origin) const |
| { |
| return Platform::IntRect(origin, tileSize()).intersects(visibleContentsRect()); |
| } |
| |
| Platform::IntRect BackingStorePrivate::visibleTilesRect() const |
| { |
| BackingStoreGeometry* currentState = frontState(); |
| TileMap currentMap = currentState->tileMap(); |
| |
| Platform::IntRect rect; |
| TileMap::const_iterator end = currentMap.end(); |
| for (TileMap::const_iterator it = currentMap.begin(); it != end; ++it) { |
| TileRect tileRect; |
| tileRect.first = it->key; |
| tileRect.second = this->tileRect(); |
| Platform::IntRect tile = mapFromTilesToTransformedContents(tileRect); |
| if (tile.intersects(visibleContentsRect())) |
| rect = Platform::unionOfRects(rect, tile); |
| } |
| return rect; |
| } |
| |
| Platform::IntRect BackingStorePrivate::tileVisibleContentsRect(const TileIndex& index) const |
| { |
| if (!isTileVisible(index)) |
| return Platform::IntRect(); |
| |
| return tileContentsRect(index, visibleContentsRect()); |
| } |
| |
| Platform::IntRect BackingStorePrivate::tileUnclippedVisibleContentsRect(const TileIndex& index) const |
| { |
| if (!isTileVisible(index)) |
| return Platform::IntRect(); |
| |
| return tileContentsRect(index, unclippedVisibleContentsRect()); |
| } |
| |
| Platform::IntRect BackingStorePrivate::tileContentsRect(const TileIndex& index, |
| const Platform::IntRect& contents) const |
| { |
| return tileContentsRect(index, contents, frontState()); |
| } |
| |
| Platform::IntRect BackingStorePrivate::tileContentsRect(const TileIndex& index, |
| const Platform::IntRect& contents, |
| BackingStoreGeometry* state) const |
| { |
| TileRectList tileRectList = mapFromTransformedContentsToTiles(contents, state); |
| for (size_t i = 0; i < tileRectList.size(); ++i) { |
| TileRect tileRect = tileRectList[i]; |
| if (index == tileRect.first) |
| return tileRect.second; |
| } |
| return Platform::IntRect(); |
| } |
| |
| void BackingStorePrivate::resetRenderQueue() |
| { |
| m_renderQueue->reset(); |
| } |
| |
| void BackingStorePrivate::clearVisibleZoom() |
| { |
| m_renderQueue->clearVisibleZoom(); |
| } |
| |
| void BackingStorePrivate::resetTiles(bool resetBackground) |
| { |
| BackingStoreGeometry* currentState = frontState(); |
| TileMap currentMap = currentState->tileMap(); |
| |
| TileMap::const_iterator end = currentMap.end(); |
| for (TileMap::const_iterator it = currentMap.begin(); it != end; ++it) |
| resetTile(it->key, it->value, resetBackground); |
| } |
| |
| void BackingStorePrivate::updateTiles(bool updateVisible, bool immediate) |
| { |
| if (!isActive()) |
| return; |
| |
| BackingStoreGeometry* currentState = frontState(); |
| TileMap currentMap = currentState->tileMap(); |
| |
| TileMap::const_iterator end = currentMap.end(); |
| for (TileMap::const_iterator it = currentMap.begin(); it != end; ++it) { |
| bool isVisible = isTileVisible(it->key); |
| if (!updateVisible && isVisible) |
| continue; |
| updateTile(it->key, immediate); |
| } |
| } |
| |
| void BackingStorePrivate::updateTilesForScrollOrNotRenderedRegion(bool checkLoading) |
| { |
| // This method looks at all the tiles and if they are visible, but not completely |
| // rendered or we are loading, then it updates them. For all tiles, visible and |
| // non-visible, if a previous attempt was made to render them during a regular |
| // render job, but they were not visible at the time, then update them and if |
| // they are currently visible, reset them. |
| |
| BackingStoreGeometry* currentState = frontState(); |
| TileMap currentMap = currentState->tileMap(); |
| Platform::IntRect backingStoreRect = currentState->backingStoreRect(); |
| |
| bool isLoading = m_client->loadState() == WebPagePrivate::Committed; |
| bool forceVisible = checkLoading && isLoading; |
| |
| TileMap::const_iterator end = currentMap.end(); |
| for (TileMap::const_iterator it = currentMap.begin(); it != end; ++it) { |
| TileIndex index = it->key; |
| BackingStoreTile* tile = it->value; |
| bool isVisible = isTileVisible(index); |
| // The rect in transformed contents coordinates. |
| Platform::IntRect rect(originOfTile(index), tileSize()); |
| if (tile->isCommitted() |
| && m_renderQueue->regularRenderJobsPreviouslyAttemptedButNotRendered(rect)) { |
| // If the render queue previously tried to render this tile, but the |
| // tile wasn't visible at the time we can't simply restore the tile |
| // since the content is now invalid as far as WebKit is concerned. |
| // Instead, we clear that part of the tile if it is visible and then |
| // put the tile in the render queue again. |
| if (isVisible) { |
| // Intersect the tile with the not rendered region to get the areas |
| // of the tile that we need to clear. |
| Platform::IntRectRegion tileNotRenderedRegion |
| = Platform::IntRectRegion::intersectRegions( |
| m_renderQueue->regularRenderJobsNotRenderedRegion(), |
| rect); |
| clearAndUpdateTileOfNotRenderedRegion(index, |
| tile, |
| tileNotRenderedRegion, |
| backingStoreRect, |
| false /*update*/); |
| #if DEBUG_BACKINGSTORE |
| Platform::IntRect extents = tileNotRenderedRegion.extents(); |
| BBLOG(BlackBerry::Platform::LogLevelCritical, |
| "BackingStorePrivate::updateTilesForScroll did clear tile %d,%d %dx%d", |
| extents.x(), extents.y(), extents.width(), extents.height()); |
| #endif |
| } |
| updateTile(index, false /*immediate*/); |
| } else if (isVisible |
| && (forceVisible || !tile->frontBuffer()->isRendered(tileVisibleContentsRect(index), currentState->scale())) |
| && !isCurrentVisibleJob(index, tile, backingStoreRect)) |
| updateTile(index, false /*immediate*/); |
| } |
| } |
| |
| void BackingStorePrivate::resetTile(const TileIndex& index, BackingStoreTile* tile, bool resetBackground) |
| { |
| if (!m_webPage->isVisible()) |
| return; |
| |
| if (!isActive()) { |
| m_webPage->d->setShouldResetTilesWhenShown(true); |
| return; |
| } |
| |
| TileRect tileRect; |
| tileRect.first = index; |
| tileRect.second = this->tileRect(); |
| // Only clear regular render jobs if we're clearing the background too. |
| m_renderQueue->clear(mapFromTilesToTransformedContents(tileRect), resetBackground /*clearRegularRenderJobs*/); |
| if (resetBackground) |
| tile->reset(); |
| } |
| |
| void BackingStorePrivate::updateTile(const TileIndex& index, bool immediate) |
| { |
| if (!isActive()) |
| return; |
| |
| TileRect tileRect; |
| tileRect.first = index; |
| tileRect.second = this->tileRect(); |
| Platform::IntRect updateRect = mapFromTilesToTransformedContents(tileRect); |
| RenderQueue::JobType jobType = isTileVisible(index) ? RenderQueue::VisibleScroll : RenderQueue::NonVisibleScroll; |
| if (immediate) |
| render(updateRect); |
| else |
| m_renderQueue->addToQueue(jobType, updateRect); |
| } |
| |
| void BackingStorePrivate::updateTile(const Platform::IntPoint& origin, bool immediate) |
| { |
| if (!isActive()) |
| return; |
| |
| Platform::IntRect updateRect = Platform::IntRect(origin, tileSize()); |
| RenderQueue::JobType jobType = isTileVisible(origin) ? RenderQueue::VisibleScroll : RenderQueue::NonVisibleScroll; |
| if (immediate) |
| render(updateRect); |
| else |
| m_renderQueue->addToQueue(jobType, updateRect); |
| } |
| |
| Platform::IntRect BackingStorePrivate::mapFromTilesToTransformedContents(const BackingStorePrivate::TileRect& tileRect) const |
| { |
| return mapFromTilesToTransformedContents(tileRect, frontState()->backingStoreRect()); |
| } |
| |
| Platform::IntRect BackingStorePrivate::mapFromTilesToTransformedContents(const BackingStorePrivate::TileRect& tileRect, const Platform::IntRect& backingStoreRect) const |
| { |
| TileIndex index = tileRect.first; |
| Platform::IntRect rect = tileRect.second; |
| // The origin of the tile including the backing store offset. |
| const Platform::IntPoint originOfTile = this->originOfTile(index, backingStoreRect); |
| rect.move(originOfTile.x(), originOfTile.y()); |
| return rect; |
| } |
| |
| BackingStorePrivate::TileRectList BackingStorePrivate::mapFromTransformedContentsToAbsoluteTileBoundaries(const Platform::IntRect& rect) const |
| { |
| if (!m_webPage->isVisible() || !isActive()) { |
| ASSERT_NOT_REACHED(); |
| return TileRectList(); |
| } |
| |
| TileRectList tileRectList; |
| int firstXOffset = rect.x() / tileWidth(); |
| int firstYOffset = rect.y() / tileHeight(); |
| int lastXOffset = (rect.right() - 1) / tileWidth(); |
| int lastYOffset = (rect.bottom() - 1) / tileHeight(); |
| for (int i = firstXOffset; i <= lastXOffset; ++i) { |
| for (int j = firstYOffset; j <= lastYOffset; ++j) { |
| const int dstX = (i == firstXOffset) ? rect.x() : i * tileWidth(); |
| const int dstY = (j == firstYOffset) ? rect.y() : j * tileHeight(); |
| const int dstRight = (i == lastXOffset) ? rect.right() : (i + 1) * tileWidth(); |
| const int dstBottom = (j == lastYOffset) ? rect.bottom() : (j + 1) * tileHeight(); |
| const int srcX = dstX % tileWidth(); |
| const int srcY = dstY % tileHeight(); |
| TileRect tileRect; |
| tileRect.first = TileIndex(i, j); |
| tileRect.second = Platform::IntRect(srcX, srcY, dstRight - dstX, dstBottom - dstY); |
| tileRectList.append(tileRect); |
| } |
| } |
| return tileRectList; |
| } |
| |
| |
| BackingStorePrivate::TileRectList BackingStorePrivate::mapFromTransformedContentsToTiles(const Platform::IntRect& rect) const |
| { |
| return mapFromTransformedContentsToTiles(rect, frontState()); |
| } |
| |
| BackingStorePrivate::TileRectList BackingStorePrivate::mapFromTransformedContentsToTiles(const Platform::IntRect& rect, BackingStoreGeometry* state) const |
| { |
| TileMap tileMap = state->tileMap(); |
| |
| TileRectList tileRectList; |
| TileMap::const_iterator end = tileMap.end(); |
| for (TileMap::const_iterator it = tileMap.begin(); it != end; ++it) { |
| TileIndex index = it->key; |
| BackingStoreTile* tile = it->value; |
| |
| // Need to map the rect to tile coordinates. |
| Platform::IntRect r = rect; |
| |
| // The origin of the tile including the backing store offset. |
| const Platform::IntPoint originOfTile = this->originOfTile(index, state->backingStoreRect()); |
| |
| r.move(-(originOfTile.x()), -(originOfTile.y())); |
| |
| // Do we intersect the current tile or no? |
| r.intersect(tile->rect()); |
| if (r.isEmpty()) |
| continue; |
| |
| // If we do append to list and Voila! |
| TileRect tileRect; |
| tileRect.first = index; |
| tileRect.second = r; |
| tileRectList.append(tileRect); |
| } |
| return tileRectList; |
| } |
| |
| void BackingStorePrivate::updateTileMatrixIfNeeded() |
| { |
| ASSERT(BlackBerry::Platform::webKitThreadMessageClient()->isCurrentThread()); |
| |
| // This will update the tile matrix. |
| scrollBackingStore(0, 0); |
| } |
| |
| void BackingStorePrivate::contentsSizeChanged(const Platform::IntSize&) |
| { |
| updateTileMatrixIfNeeded(); |
| } |
| |
| void BackingStorePrivate::scrollChanged(const Platform::IntPoint&) |
| { |
| // FIXME: Need to do anything here? |
| } |
| |
| void BackingStorePrivate::transformChanged() |
| { |
| if (!m_webPage->isVisible()) |
| return; |
| |
| if (!isActive()) { |
| m_renderQueue->reset(); |
| m_renderQueue->addToQueue(RenderQueue::VisibleZoom, visibleContentsRect()); |
| m_webPage->d->setShouldResetTilesWhenShown(true); |
| return; |
| } |
| |
| BackingStoreGeometry* currentState = frontState(); |
| TileMap currentMap = currentState->tileMap(); |
| |
| bool hasCurrentVisibleZoomJob = m_renderQueue->hasCurrentVisibleZoomJob(); |
| bool isLoading = m_client->isLoading(); |
| if (isLoading) { |
| if (!hasCurrentVisibleZoomJob) |
| m_visibleTileBufferRect = visibleContentsRect(); // Cache this for blitVisibleContents. |
| |
| // Add the currently visible tiles to the render queue as visible zoom jobs. |
| TileRectList tileRectList = mapFromTransformedContentsToTiles(visibleContentsRect()); |
| for (size_t i = 0; i < tileRectList.size(); ++i) { |
| TileRect tileRect = tileRectList[i]; |
| TileIndex index = tileRect.first; |
| Platform::IntRect dirtyTileRect = tileRect.second; |
| BackingStoreTile* tile = currentMap.get(index); |
| |
| // Invalidate the whole rect. |
| tileRect.second = this->tileRect(); |
| Platform::IntRect wholeRect = mapFromTilesToTransformedContents(tileRect); |
| m_renderQueue->addToQueue(RenderQueue::VisibleZoom, wholeRect); |
| |
| // Copy the visible contents into the visibleTileBuffer if we don't have |
| // any current visible zoom jobs. |
| if (!hasCurrentVisibleZoomJob) { |
| // Map to the destination's coordinate system. |
| Platform::IntPoint difference = this->originOfTile(index) - m_visibleTileBufferRect.location(); |
| Platform::IntSize offset = Platform::IntSize(difference.x(), difference.y()); |
| Platform::IntRect dirtyRect = dirtyTileRect; |
| dirtyRect.move(offset.width(), offset.height()); |
| |
| BackingStoreTile* visibleTileBuffer |
| = SurfacePool::globalSurfacePool()->visibleTileBuffer(); |
| ASSERT(visibleTileBuffer->size() == Platform::IntSize(m_webPage->d->transformedViewportSize())); |
| BlackBerry::Platform::Graphics::blitToBuffer( |
| visibleTileBuffer->frontBuffer()->nativeBuffer(), dirtyRect, |
| tile->frontBuffer()->nativeBuffer(), dirtyTileRect); |
| } |
| } |
| } |
| |
| m_renderQueue->reset(); |
| resetTiles(true /*resetBackground*/); |
| } |
| |
| void BackingStorePrivate::orientationChanged() |
| { |
| ASSERT(BlackBerry::Platform::webKitThreadMessageClient()->isCurrentThread()); |
| updateTileMatrixIfNeeded(); |
| createVisibleTileBuffer(); |
| } |
| |
| void BackingStorePrivate::actualVisibleSizeChanged(const Platform::IntSize& size) |
| { |
| } |
| |
| static void createVisibleTileBufferForWebPage(WebPagePrivate* page) |
| { |
| ASSERT(page); |
| SurfacePool* surfacePool = SurfacePool::globalSurfacePool(); |
| surfacePool->initializeVisibleTileBuffer(page->transformedViewportSize()); |
| } |
| |
| void BackingStorePrivate::createSurfaces() |
| { |
| BackingStoreGeometry* currentState = frontState(); |
| TileMap currentMap = currentState->tileMap(); |
| |
| ASSERT(currentMap.isEmpty()); |
| |
| if (m_webPage->isVisible()) { |
| // This method is only to be called as part of setting up a new web page instance and |
| // before said instance is made visible so as to ensure a consistent definition of web |
| // page visibility. That is, a web page is said to be visible when explicitly made visible. |
| ASSERT_NOT_REACHED(); |
| return; |
| } |
| |
| SurfacePool* surfacePool = SurfacePool::globalSurfacePool(); |
| surfacePool->initialize(tileSize()); |
| |
| if (surfacePool->isEmpty()) // Settings specify 0 tiles / no backing store. |
| return; |
| |
| const Divisor divisor = bestDivisor(expandedContentsSize(), tileWidth(), tileHeight(), minimumNumberOfTilesWide(), minimumNumberOfTilesHigh(), m_preferredTileMatrixDimension); |
| |
| int numberOfTilesWide = divisor.first; |
| int numberOfTilesHigh = divisor.second; |
| |
| const SurfacePool::TileList tileList = surfacePool->tileList(); |
| ASSERT(static_cast<int>(tileList.size()) >= (numberOfTilesWide * numberOfTilesHigh)); |
| |
| TileMap newTileMap; |
| for (int y = 0; y < numberOfTilesHigh; ++y) { |
| for (int x = 0; x < numberOfTilesWide; ++x) { |
| TileIndex index(x, y); |
| newTileMap.add(index, tileList.at(x + y * numberOfTilesWide)); |
| } |
| } |
| |
| // Set the initial state of the backingstore geometry. |
| backState()->setScale(m_webPage->d->currentScale()); |
| backState()->setNumberOfTilesWide(divisor.first); |
| backState()->setNumberOfTilesHigh(divisor.second); |
| backState()->setTileMap(newTileMap); |
| |
| // Swap back/front state. |
| swapState(); |
| |
| createVisibleTileBufferForWebPage(m_webPage->d); |
| |
| // Don't try to blit to screen unless we have a buffer. |
| if (!buffer()) |
| suspendScreenAndBackingStoreUpdates(); |
| } |
| |
| void BackingStorePrivate::createVisibleTileBuffer() |
| { |
| if (!m_webPage->isVisible() || !isActive()) |
| return; |
| |
| createVisibleTileBufferForWebPage(m_webPage->d); |
| } |
| |
| Platform::IntPoint BackingStorePrivate::originOfTile(const TileIndex& index) const |
| { |
| return originOfTile(index, frontState()->backingStoreRect()); |
| } |
| |
| Platform::IntPoint BackingStorePrivate::originOfTile(const TileIndex& index, const Platform::IntRect& backingStoreRect) const |
| { |
| return Platform::IntPoint(backingStoreRect.x() + (index.i() * tileWidth()), |
| backingStoreRect.y() + (index.j() * tileHeight())); |
| } |
| |
| int BackingStorePrivate::minimumNumberOfTilesWide() const |
| { |
| // The minimum number of tiles wide required to fill the viewport + 1 tile extra to allow scrolling. |
| return static_cast<int>(ceilf(m_client->transformedViewportSize().width() / static_cast<float>(tileWidth()))) + 1; |
| } |
| |
| int BackingStorePrivate::minimumNumberOfTilesHigh() const |
| { |
| // The minimum number of tiles high required to fill the viewport + 1 tile extra to allow scrolling. |
| return static_cast<int>(ceilf(m_client->transformedViewportSize().height() / static_cast<float>(tileHeight()))) + 1; |
| } |
| |
| Platform::IntSize BackingStorePrivate::expandedContentsSize() const |
| { |
| return m_client->transformedContentsSize().expandedTo(m_client->transformedViewportSize()); |
| } |
| |
| int BackingStorePrivate::tileWidth() |
| { |
| static int tileWidth = BlackBerry::Platform::Graphics::Screen::primaryScreen()->landscapeWidth(); |
| return tileWidth; |
| } |
| |
| int BackingStorePrivate::tileHeight() |
| { |
| static int tileHeight = BlackBerry::Platform::Graphics::Screen::primaryScreen()->landscapeHeight(); |
| return tileHeight; |
| } |
| |
| Platform::IntSize BackingStorePrivate::tileSize() |
| { |
| return Platform::IntSize(tileWidth(), tileHeight()); |
| } |
| |
| Platform::IntRect BackingStorePrivate::tileRect() |
| { |
| return Platform::IntRect(0, 0, tileWidth(), tileHeight()); |
| } |
| |
| void BackingStorePrivate::renderContents(Platform::Graphics::Drawable* drawable, |
| const Platform::IntRect& contentsRect, |
| const Platform::IntSize& destinationSize) const |
| { |
| if (!drawable || contentsRect.isEmpty()) |
| return; |
| |
| requestLayoutIfNeeded(); |
| |
| PlatformGraphicsContext* platformGraphicsContext = SurfacePool::globalSurfacePool()->createPlatformGraphicsContext(drawable); |
| GraphicsContext graphicsContext(platformGraphicsContext); |
| |
| graphicsContext.translate(-contentsRect.x(), -contentsRect.y()); |
| |
| WebCore::IntRect transformedContentsRect(contentsRect.x(), contentsRect.y(), contentsRect.width(), contentsRect.height()); |
| |
| float widthScale = static_cast<float>(destinationSize.width()) / contentsRect.width(); |
| float heightScale = static_cast<float>(destinationSize.height()) / contentsRect.height(); |
| |
| if (widthScale != 1.0 && heightScale != 1.0) { |
| TransformationMatrix matrix; |
| matrix.scaleNonUniform(1.0 / widthScale, 1.0 / heightScale); |
| transformedContentsRect = matrix.mapRect(transformedContentsRect); |
| |
| // We extract from the contentsRect but draw a slightly larger region than |
| // we were told to, in order to avoid pixels being rendered only partially. |
| const int atLeastOneDevicePixel = static_cast<int>(ceilf(std::max(1.0 / widthScale, 1.0 / heightScale))); |
| transformedContentsRect.inflate(atLeastOneDevicePixel); |
| graphicsContext.scale(FloatSize(widthScale, heightScale)); |
| } |
| |
| graphicsContext.clip(transformedContentsRect); |
| m_client->frame()->view()->paintContents(&graphicsContext, transformedContentsRect); |
| |
| delete platformGraphicsContext; |
| } |
| |
| void BackingStorePrivate::renderContents(BlackBerry::Platform::Graphics::Buffer* tileBuffer, |
| const Platform::IntPoint& surfaceOffset, |
| const Platform::IntRect& contentsRect) const |
| { |
| // If tileBuffer == 0, we render directly to the window. |
| if (!m_webPage->isVisible() && tileBuffer) |
| return; |
| |
| #if DEBUG_BACKINGSTORE |
| BBLOG(BlackBerry::Platform::LogLevelCritical, |
| "BackingStorePrivate::renderContents tileBuffer=0x%x surfaceOffset=(%d,%d) contentsRect=(%d,%d %dx%d)", |
| tileBuffer, surfaceOffset.x(), surfaceOffset.y(), |
| contentsRect.x(), contentsRect.y(), contentsRect.width(), contentsRect.height()); |
| #endif |
| |
| // It is up to callers of this method to perform layout themselves! |
| ASSERT(!m_webPage->d->mainFrame()->view()->needsLayout()); |
| |
| Platform::IntSize contentsSize = m_client->contentsSize(); |
| Color backgroundColor(m_webPage->settings()->backgroundColor()); |
| |
| BlackBerry::Platform::Graphics::Buffer* targetBuffer = tileBuffer |
| ? tileBuffer |
| : buffer(); |
| |
| if (contentsSize.isEmpty() |
| || !Platform::IntRect(Platform::IntPoint(0, 0), m_client->transformedContentsSize()).contains(contentsRect) |
| || backgroundColor.hasAlpha()) { |
| // Clear the area if it's not fully covered by (opaque) contents. |
| BlackBerry::Platform::IntRect clearRect = BlackBerry::Platform::IntRect( |
| contentsRect.x() - surfaceOffset.x(), contentsRect.y() - surfaceOffset.y(), |
| contentsRect.width(), contentsRect.height()); |
| |
| BlackBerry::Platform::Graphics::clearBuffer(targetBuffer, clearRect, |
| backgroundColor.red(), backgroundColor.green(), |
| backgroundColor.blue(), backgroundColor.alpha()); |
| } |
| |
| if (contentsSize.isEmpty()) |
| return; |
| |
| BlackBerry::Platform::Graphics::Drawable* bufferDrawable = |
| BlackBerry::Platform::Graphics::lockBufferDrawable(targetBuffer); |
| |
| PlatformGraphicsContext* bufferPlatformGraphicsContext = bufferDrawable |
| ? SurfacePool::globalSurfacePool()->createPlatformGraphicsContext(bufferDrawable) |
| : 0; |
| PlatformGraphicsContext* targetPlatformGraphicsContext = bufferPlatformGraphicsContext |
| ? bufferPlatformGraphicsContext |
| : SurfacePool::globalSurfacePool()->lockTileRenderingSurface(); |
| |
| ASSERT(targetPlatformGraphicsContext); |
| |
| { |
| GraphicsContext graphicsContext(targetPlatformGraphicsContext); |
| |
| // Believe it or not this is important since the WebKit Skia backend |
| // doesn't store the original state unless you call save first :P |
| graphicsContext.save(); |
| |
| // Translate context according to offset. |
| graphicsContext.translate(-surfaceOffset.x(), -surfaceOffset.y()); |
| |
| // Add our transformation matrix as the global transform. |
| AffineTransform affineTransform( |
| m_webPage->d->transformationMatrix()->a(), |
| m_webPage->d->transformationMatrix()->b(), |
| m_webPage->d->transformationMatrix()->c(), |
| m_webPage->d->transformationMatrix()->d(), |
| m_webPage->d->transformationMatrix()->e(), |
| m_webPage->d->transformationMatrix()->f()); |
| graphicsContext.concatCTM(affineTransform); |
| |
| // Now that the matrix is applied we need untranformed contents coordinates. |
| Platform::IntRect untransformedContentsRect = m_webPage->d->mapFromTransformed(contentsRect); |
| |
| // We extract from the contentsRect but draw a slightly larger region than |
| // we were told to, in order to avoid pixels being rendered only partially. |
| const int atLeastOneDevicePixel = |
| static_cast<int>(ceilf(1.0 / m_webPage->d->transformationMatrix()->a())); |
| untransformedContentsRect.inflate(atLeastOneDevicePixel, atLeastOneDevicePixel); |
| |
| // Make sure the untransformed rectangle for the (slightly larger than |
| // initially requested) repainted region is within the bounds of the page. |
| untransformedContentsRect.intersect(Platform::IntRect(Platform::IntPoint(0, 0), contentsSize)); |
| |
| // Some WebKit painting backends *cough* Skia *cough* don't set this automatically |
| // to the dirtyRect so do so here explicitly. |
| graphicsContext.clip(untransformedContentsRect); |
| |
| // Take care of possible left overflow on RTL page. |
| if (int leftOverFlow = m_client->frame()->view()->minimumScrollPosition().x()) { |
| untransformedContentsRect.move(leftOverFlow, 0); |
| graphicsContext.translate(-leftOverFlow, 0); |
| } |
| |
| // Let WebCore render the page contents into the drawing surface. |
| m_client->frame()->view()->paintContents(&graphicsContext, untransformedContentsRect); |
| |
| graphicsContext.restore(); |
| } |
| |
| // Grab the requested region from the drawing surface into the tile image. |
| |
| delete bufferPlatformGraphicsContext; |
| |
| if (bufferDrawable) |
| releaseBufferDrawable(targetBuffer); |
| else { |
| const Platform::IntPoint dstPoint(contentsRect.x() - surfaceOffset.x(), |
| contentsRect.y() - surfaceOffset.y()); |
| const Platform::IntRect dstRect(dstPoint, contentsRect.size()); |
| const Platform::IntRect srcRect = dstRect; |
| |
| // If we couldn't directly draw to the buffer, copy from the drawing surface. |
| SurfacePool::globalSurfacePool()->releaseTileRenderingSurface(targetPlatformGraphicsContext); |
| BlackBerry::Platform::Graphics::blitToBuffer(targetBuffer, dstRect, BlackBerry::Platform::Graphics::drawingSurface(), srcRect); |
| } |
| } |
| |
| #if DEBUG_FAT_FINGERS |
| static void drawDebugRect(BlackBerry::Platform::Graphics::Buffer* dstBuffer, const Platform::IntRect& dstRect, const Platform::IntRect& srcRect, unsigned char red, unsigned char green, unsigned char blue) |
| { |
| Platform::IntRect drawRect(srcRect); |
| drawRect.intersect(dstRect); |
| if (!drawRect.isEmpty()) |
| BlackBerry::Platform::Graphics::clearBuffer(dstBuffer, drawRect, red, green, blue, 128); |
| } |
| #endif |
| |
| void BackingStorePrivate::blitToWindow(const Platform::IntRect& dstRect, |
| const BlackBerry::Platform::Graphics::Buffer* srcBuffer, |
| const Platform::IntRect& srcRect, |
| bool blend, |
| unsigned char globalAlpha) |
| { |
| ASSERT(BlackBerry::Platform::userInterfaceThreadMessageClient()->isCurrentThread()); |
| |
| windowFrontBufferState()->clearBlittedRegion(dstRect); |
| windowBackBufferState()->addBlittedRegion(dstRect); |
| |
| BlackBerry::Platform::Graphics::Buffer* dstBuffer = buffer(); |
| ASSERT(dstBuffer); |
| ASSERT(srcBuffer); |
| if (!dstBuffer) { |
| BBLOG(BlackBerry::Platform::LogLevelWarn, |
| "Empty window buffer, couldn't blitToWindow"); |
| } |
| |
| BlackBerry::Platform::Graphics::BlendMode blendMode = blend |
| ? BlackBerry::Platform::Graphics::SourceOver |
| : BlackBerry::Platform::Graphics::SourceCopy; |
| |
| BlackBerry::Platform::Graphics::blitToBuffer(dstBuffer, dstRect, srcBuffer, srcRect, blendMode, globalAlpha); |
| |
| #if DEBUG_FAT_FINGERS |
| drawDebugRect(dstBuffer, dstRect, FatFingers::m_debugFatFingerRect, 210, 210, 250); |
| drawDebugRect(dstBuffer, dstRect, Platform::IntRect(FatFingers::m_debugFatFingerClickPosition, Platform::IntSize(3, 3)), 0, 0, 0); |
| drawDebugRect(dstBuffer, dstRect, Platform::IntRect(FatFingers::m_debugFatFingerAdjustedPosition, Platform::IntSize(5, 5)), 100, 100, 100); |
| #endif |
| |
| } |
| |
| void BackingStorePrivate::fillWindow(Platform::Graphics::FillPattern pattern, |
| const Platform::IntRect& dstRect, |
| const Platform::IntPoint& contentsOrigin, |
| double contentsScale) |
| { |
| ASSERT(BlackBerry::Platform::userInterfaceThreadMessageClient()->isCurrentThread()); |
| |
| windowFrontBufferState()->clearBlittedRegion(dstRect); |
| windowBackBufferState()->addBlittedRegion(dstRect); |
| |
| BlackBerry::Platform::Graphics::Buffer* dstBuffer = buffer(); |
| ASSERT(dstBuffer); |
| if (!dstBuffer) { |
| BBLOG(BlackBerry::Platform::LogLevelWarn, |
| "Empty window buffer, couldn't fillWindow"); |
| } |
| |
| if (pattern == BlackBerry::Platform::Graphics::CheckerboardPattern && BlackBerry::Platform::Settings::isPublicBuild()) { |
| // For public builds, convey the impression of less checkerboard. |
| // For developer builds, keep the checkerboard to get it fixed better. |
| BlackBerry::Platform::Graphics::clearBuffer(dstBuffer, dstRect, |
| m_webPageBackgroundColor.red(), m_webPageBackgroundColor.green(), |
| m_webPageBackgroundColor.blue(), m_webPageBackgroundColor.alpha()); |
| return; |
| } |
| |
| BlackBerry::Platform::Graphics::fillBuffer(dstBuffer, pattern, dstRect, contentsOrigin, contentsScale); |
| } |
| |
| WebCore::Color BackingStorePrivate::webPageBackgroundColorUserInterfaceThread() const |
| { |
| ASSERT(BlackBerry::Platform::userInterfaceThreadMessageClient()->isCurrentThread()); |
| return m_webPageBackgroundColor; |
| } |
| |
| void BackingStorePrivate::setWebPageBackgroundColor(const WebCore::Color& color) |
| { |
| if (!BlackBerry::Platform::userInterfaceThreadMessageClient()->isCurrentThread()) { |
| typedef void (BlackBerry::WebKit::BackingStorePrivate::*FunctionType)(const WebCore::Color&); |
| |
| BlackBerry::Platform::userInterfaceThreadMessageClient()->dispatchMessage( |
| BlackBerry::Platform::createMethodCallMessage<FunctionType, BackingStorePrivate, WebCore::Color>( |
| &BackingStorePrivate::setWebPageBackgroundColor, this, color)); |
| return; |
| } |
| |
| m_webPageBackgroundColor = color; |
| } |
| |
| void BackingStorePrivate::invalidateWindow() |
| { |
| // Grab a rect appropriate for the current thread. |
| if (BlackBerry::Platform::userInterfaceThreadMessageClient()->isCurrentThread()) |
| invalidateWindow(m_webPage->client()->userInterfaceViewportAccessor()->destinationSurfaceRect()); |
| else |
| invalidateWindow(Platform::IntRect(Platform::IntPoint(0, 0), m_client->transformedViewportSize())); |
| } |
| |
| void BackingStorePrivate::invalidateWindow(const Platform::IntRect& dst) |
| { |
| if (dst.isEmpty()) |
| return; |
| |
| if (!BlackBerry::Platform::userInterfaceThreadMessageClient()->isCurrentThread() && !shouldDirectRenderingToWindow()) { |
| // This needs to be sync in order to swap the recently drawn thing... |
| // This will only be called from WebKit thread during direct rendering. |
| typedef void (BlackBerry::WebKit::BackingStorePrivate::*FunctionType)(const Platform::IntRect&); |
| BlackBerry::Platform::userInterfaceThreadMessageClient()->dispatchSyncMessage( |
| BlackBerry::Platform::createMethodCallMessage<FunctionType, BackingStorePrivate, Platform::IntRect>( |
| &BackingStorePrivate::invalidateWindow, this, dst)); |
| return; |
| } |
| |
| #if DEBUG_BACKINGSTORE |
| BBLOG(BlackBerry::Platform::LogLevelCritical, "BackingStorePrivate::invalidateWindow dst = %s", dst.toString().c_str()); |
| #endif |
| |
| // Since our window may also be double buffered, we need to also copy the |
| // front buffer's contents to the back buffer before we swap them. It is |
| // analogous to what we do with our double buffered tiles by calling |
| // copyPreviousContentsToBackingSurfaceOfTile(). It only affects partial |
| // screen updates since when we are scrolling or zooming, the whole window |
| // is invalidated anyways and no copying is needed. |
| copyPreviousContentsToBackSurfaceOfWindow(); |
| |
| Platform::IntRect dstRect = dst; |
| |
| Platform::IntRect viewportRect(Platform::IntPoint(0, 0), m_client->transformedViewportSize()); |
| dstRect.intersect(viewportRect); |
| |
| if (dstRect.width() <= 0 || dstRect.height() <= 0) |
| return; |
| |
| #if DEBUG_BACKINGSTORE |
| BBLOG(BlackBerry::Platform::LogLevelCritical, "BackingStorePrivate::invalidateWindow posting = %s", dstRect.toString().c_str()); |
| #endif |
| |
| m_currentWindowBackBuffer = (m_currentWindowBackBuffer + 1) % 2; |
| if (Window* window = m_webPage->client()->window()) |
| window->post(dstRect); |
| } |
| |
| void BackingStorePrivate::clearWindow(const Platform::IntRect& rect, |
| unsigned char red, |
| unsigned char green, |
| unsigned char blue, |
| unsigned char alpha) |
| { |
| if (!BlackBerry::Platform::userInterfaceThreadMessageClient()->isCurrentThread() && !shouldDirectRenderingToWindow()) { |
| typedef void (BlackBerry::WebKit::BackingStorePrivate::*FunctionType)(const Platform::IntRect&, |
| unsigned char, |
| unsigned char, |
| unsigned char, |
| unsigned char); |
| BlackBerry::Platform::userInterfaceThreadMessageClient()->dispatchMessage( |
| BlackBerry::Platform::createMethodCallMessage<FunctionType, |
| BackingStorePrivate, |
| Platform::IntRect, |
| unsigned char, |
| unsigned char, |
| unsigned char, |
| unsigned char>( |
| &BackingStorePrivate::clearWindow, this, rect, red, green, blue, alpha)); |
| return; |
| } |
| |
| BlackBerry::Platform::Graphics::Buffer* dstBuffer = buffer(); |
| ASSERT(dstBuffer); |
| if (!dstBuffer) { |
| BBLOG(BlackBerry::Platform::LogLevelWarn, |
| "Empty window buffer, couldn't clearWindow"); |
| } |
| |
| windowFrontBufferState()->clearBlittedRegion(rect); |
| windowBackBufferState()->addBlittedRegion(rect); |
| |
| BlackBerry::Platform::Graphics::clearBuffer(dstBuffer, rect, red, green, blue, alpha); |
| } |
| |
| bool BackingStorePrivate::isScrollingOrZooming() const |
| { |
| BackingStoreMutexLocker locker(const_cast<BackingStorePrivate*>(this)); |
| return m_isScrollingOrZooming; |
| } |
| |
| void BackingStorePrivate::setScrollingOrZooming(bool scrollingOrZooming, bool shouldBlit) |
| { |
| { |
| BackingStoreMutexLocker locker(this); |
| m_isScrollingOrZooming = scrollingOrZooming; |
| } |
| |
| #if !ENABLE_REPAINTONSCROLL |
| m_suspendRenderJobs = scrollingOrZooming; // Suspend the rendering of everything. |
| #endif |
| |
| if (!m_webPage->settings()->shouldRenderAnimationsOnScrollOrZoom()) |
| m_suspendRegularRenderJobs = scrollingOrZooming; // Suspend the rendering of animations. |
| |
| m_webPage->d->m_mainFrame->view()->setConstrainsScrollingToContentEdge(!scrollingOrZooming); |
| |
| // Clear this flag since we don't care if the render queue is under pressure |
| // or not since we are scrolling and it is more important to not lag than |
| // it is to ensure animations achieve better framerates! |
| if (scrollingOrZooming) |
| m_renderQueue->setCurrentRegularRenderJobBatchUnderPressure(false); |
| #if ENABLE_SCROLLBARS |
| else if (shouldBlit && !shouldDirectRenderingToWindow()) |
| blitVisibleContents(); |
| #endif |
| } |
| |
| void BackingStorePrivate::lockBackingStore() |
| { |
| pthread_mutex_lock(&m_mutex); |
| } |
| |
| void BackingStorePrivate::unlockBackingStore() |
| { |
| pthread_mutex_unlock(&m_mutex); |
| } |
| |
| BackingStoreGeometry* BackingStorePrivate::frontState() const |
| { |
| return reinterpret_cast<BackingStoreGeometry*>(m_frontState); |
| } |
| |
| BackingStoreGeometry* BackingStorePrivate::backState() const |
| { |
| return reinterpret_cast<BackingStoreGeometry*>(m_backState); |
| } |
| |
| void BackingStorePrivate::swapState() |
| { |
| unsigned front = reinterpret_cast<unsigned>(frontState()); |
| unsigned back = reinterpret_cast<unsigned>(backState()); |
| |
| // Atomic change. |
| _smp_xchg(&m_frontState, back); |
| _smp_xchg(&m_backState, front); |
| BlackBerry::Platform::userInterfaceThreadMessageClient()->syncToCurrentMessage(); |
| } |
| |
| BackingStoreWindowBufferState* BackingStorePrivate::windowFrontBufferState() const |
| { |
| return &m_windowBufferState[(m_currentWindowBackBuffer + 1) % 2]; |
| } |
| |
| BackingStoreWindowBufferState* BackingStorePrivate::windowBackBufferState() const |
| { |
| return &m_windowBufferState[m_currentWindowBackBuffer]; |
| } |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| void BackingStorePrivate::drawAndBlendLayersForDirectRendering(const Platform::IntRect& dirtyRect) |
| { |
| ASSERT(BlackBerry::Platform::userInterfaceThreadMessageClient()->isCurrentThread()); |
| if (!BlackBerry::Platform::userInterfaceThreadMessageClient()->isCurrentThread()) |
| return; |
| |
| // Because we're being called sync from the WebKit thread, we can use |
| // regular WebPage size and transformation functions without concerns. |
| WebCore::IntRect contentsRect = visibleContentsRect(); |
| WebCore::FloatRect untransformedContentsRect = m_webPage->d->mapFromTransformedFloatRect(WebCore::FloatRect(contentsRect)); |
| WebCore::IntRect contentsScreenRect = m_client->mapFromTransformedContentsToTransformedViewport(contentsRect); |
| WebCore::IntRect dstRect = intersection(contentsScreenRect, |
| WebCore::IntRect(WebCore::IntPoint(0, 0), m_webPage->d->transformedViewportSize())); |
| |
| // Check if rendering caused a commit and we need to redraw the layers. |
| if (WebPageCompositorPrivate* compositor = m_webPage->d->compositor()) |
| compositor->drawLayers(dstRect, untransformedContentsRect); |
| } |
| #endif |
| |
| bool BackingStorePrivate::isActive() const |
| { |
| return BackingStorePrivate::s_currentBackingStoreOwner == m_webPage && SurfacePool::globalSurfacePool()->isActive(); |
| } |
| |
| void BackingStorePrivate::didRenderContent(const Platform::IntRect& renderedRect) |
| { |
| if (isScrollingOrZooming()) |
| return; |
| |
| if (!shouldDirectRenderingToWindow()) { |
| if (!m_webPage->d->needsOneShotDrawingSynchronization()) |
| blitVisibleContents(); |
| } else |
| invalidateWindow(); |
| |
| m_webPage->client()->notifyPixelContentRendered(renderedRect); |
| } |
| |
| BackingStore::BackingStore(WebPage* webPage, BackingStoreClient* client) |
| : d(new BackingStorePrivate) |
| { |
| d->m_webPage = webPage; |
| d->m_client = client; |
| } |
| |
| BackingStore::~BackingStore() |
| { |
| deleteGuardedObject(d); |
| d = 0; |
| } |
| |
| void BackingStore::createSurface() |
| { |
| static bool initialized = false; |
| if (!initialized) { |
| BlackBerry::Platform::Graphics::initialize(); |
| initialized = true; |
| } |
| |
| // Triggers creation of surfaces in backingstore. |
| d->createSurfaces(); |
| |
| // Focusing the WebPage triggers a repaint, so while we want it to be |
| // focused initially this has to happen after creation of the surface. |
| d->m_webPage->setFocused(true); |
| } |
| |
| void BackingStore::suspendScreenAndBackingStoreUpdates() |
| { |
| d->suspendScreenAndBackingStoreUpdates(); |
| } |
| |
| void BackingStore::resumeScreenAndBackingStoreUpdates(ResumeUpdateOperation op) |
| { |
| d->resumeScreenAndBackingStoreUpdates(op); |
| } |
| |
| bool BackingStore::isScrollingOrZooming() const |
| { |
| return d->isScrollingOrZooming(); |
| } |
| |
| void BackingStore::setScrollingOrZooming(bool scrollingOrZooming) |
| { |
| d->setScrollingOrZooming(scrollingOrZooming); |
| } |
| |
| void BackingStore::blitVisibleContents() |
| { |
| d->blitVisibleContents(false /*force*/); |
| } |
| |
| void BackingStore::repaint(int x, int y, int width, int height, |
| bool contentChanged, bool immediate) |
| { |
| d->repaint(Platform::IntRect(x, y, width, height), contentChanged, immediate); |
| } |
| |
| bool BackingStore::isDirectRenderingToWindow() const |
| { |
| BackingStoreMutexLocker locker(d); |
| return d->shouldDirectRenderingToWindow(); |
| } |
| |
| void BackingStore::createBackingStoreMemory() |
| { |
| if (BackingStorePrivate::s_currentBackingStoreOwner == d->m_webPage) |
| SurfacePool::globalSurfacePool()->createBuffers(); |
| } |
| |
| void BackingStore::releaseBackingStoreMemory() |
| { |
| if (BackingStorePrivate::s_currentBackingStoreOwner == d->m_webPage) |
| SurfacePool::globalSurfacePool()->releaseBuffers(); |
| } |
| |
| bool BackingStore::defersBlit() const |
| { |
| return d->m_defersBlit; |
| } |
| |
| void BackingStore::setDefersBlit(bool b) |
| { |
| d->m_defersBlit = b; |
| } |
| |
| bool BackingStore::hasBlitJobs() const |
| { |
| #if USE(ACCELERATED_COMPOSITING) |
| // If there's a WebPageCompositorClient, let it schedule the blit. |
| WebPageCompositorPrivate* compositor = d->m_webPage->d->compositor(); |
| if (compositor && compositor->client()) |
| return false; |
| #endif |
| |
| // Normally, this would be called from the compositing thread, |
| // and the flag is set on the compositing thread, so no need for |
| // synchronization. |
| return d->m_hasBlitJobs; |
| } |
| |
| void BackingStore::blitOnIdle() |
| { |
| #if USE(ACCELERATED_COMPOSITING) |
| // If there's a WebPageCompositorClient, let it schedule the blit. |
| WebPageCompositorPrivate* compositor = d->m_webPage->d->compositor(); |
| if (compositor && compositor->client()) |
| return; |
| #endif |
| |
| d->blitVisibleContents(true /*force*/); |
| } |
| |
| Platform::IntSize BackingStorePrivate::surfaceSize() const |
| { |
| if (Window* window = m_webPage->client()->window()) |
| return window->surfaceSize(); |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| if (WebPageCompositorPrivate* compositor = m_webPage->d->compositor()) |
| return compositor->context()->surfaceSize(); |
| #endif |
| |
| return Platform::IntSize(); |
| } |
| |
| Platform::Graphics::Buffer* BackingStorePrivate::buffer() const |
| { |
| if (Window* window = m_webPage->client()->window()) |
| return window->buffer(); |
| |
| #if USE(ACCELERATED_COMPOSITING) |
| if (WebPageCompositorPrivate* compositor = m_webPage->d->compositor()) |
| return compositor->context() ? compositor->context()->buffer() : 0; |
| #endif |
| |
| return 0; |
| } |
| |
| void BackingStore::drawContents(Platform::Graphics::Drawable* drawable, const Platform::IntRect& contentsRect, const Platform::IntSize& destinationSize) |
| { |
| d->renderContents(drawable, contentsRect, destinationSize); |
| } |
| |
| } |
| } |