/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/layers/TiledContentClient.h" #include // for ceil, ceilf, floor #include #include "ClientTiledPaintedLayer.h" // for ClientTiledPaintedLayer #include "GeckoProfiler.h" // for PROFILER_LABEL #include "ClientLayerManager.h" // for ClientLayerManager #include "gfxContext.h" // for gfxContext, etc #include "gfxPlatform.h" // for gfxPlatform #include "gfxPrefs.h" // for gfxPrefs #include "gfxRect.h" // for gfxRect #include "mozilla/MathAlgorithms.h" // for Abs #include "mozilla/gfx/Point.h" // for IntSize #include "mozilla/gfx/Rect.h" // for Rect #include "mozilla/gfx/Tools.h" // for BytesPerPixel #include "mozilla/layers/CompositableForwarder.h" #include "mozilla/layers/CompositorBridgeChild.h" // for CompositorBridgeChild #include "mozilla/layers/LayerMetricsWrapper.h" #include "mozilla/layers/ShadowLayers.h" // for ShadowLayerForwarder #include "TextureClientPool.h" #include "nsDebug.h" // for NS_ASSERTION #include "nsISupportsImpl.h" // for gfxContext::AddRef, etc #include "gfxReusableSharedImageSurfaceWrapper.h" #include "nsExpirationTracker.h" // for nsExpirationTracker #include "nsMathUtils.h" // for NS_lroundf #include "LayersLogging.h" #include "UnitTransforms.h" // for TransformTo #include "mozilla/UniquePtr.h" // This is the minimum area that we deem reasonable to copy from the front buffer to the // back buffer on tile updates. If the valid region is smaller than this, we just // redraw it and save on the copy (and requisite surface-locking involved). #define MINIMUM_TILE_COPY_AREA (1.f/16.f) #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY #include "cairo.h" #include using mozilla::layers::Layer; static void DrawDebugOverlay(mozilla::gfx::DrawTarget* dt, int x, int y, int width, int height) { gfxContext c(dt); // Draw border c.NewPath(); c.SetDeviceColor(Color(0.f, 0.f, 0.f)); c.Rectangle(gfxRect(0, 0, width, height)); c.Stroke(); // Build tile description std::stringstream ss; ss << x << ", " << y; // Draw text using cairo toy text API // XXX: this drawing will silently fail if |dt| doesn't have a Cairo backend cairo_t* cr = gfxFont::RefCairo(dt); cairo_set_font_size(cr, 25); cairo_text_extents_t extents; cairo_text_extents(cr, ss.str().c_str(), &extents); int textWidth = extents.width + 6; c.NewPath(); c.SetDeviceColor(Color(0.f, 0.f, 0.f)); c.Rectangle(gfxRect(gfxPoint(2,2),gfxSize(textWidth, 30))); c.Fill(); c.NewPath(); c.SetDeviceColor(Color(1.0, 0.0, 0.0)); c.Rectangle(gfxRect(gfxPoint(2,2),gfxSize(textWidth, 30))); c.Stroke(); c.NewPath(); cairo_move_to(cr, 4, 28); cairo_show_text(cr, ss.str().c_str()); } #endif namespace mozilla { using namespace gfx; namespace layers { MultiTiledContentClient::MultiTiledContentClient(ClientTiledPaintedLayer* aPaintedLayer, ClientLayerManager* aManager) : TiledContentClient(aManager, "Multi") { MOZ_COUNT_CTOR(MultiTiledContentClient); mTiledBuffer = ClientMultiTiledLayerBuffer(aPaintedLayer, this, aManager, &mSharedFrameMetricsHelper); mLowPrecisionTiledBuffer = ClientMultiTiledLayerBuffer(aPaintedLayer, this, aManager, &mSharedFrameMetricsHelper); mLowPrecisionTiledBuffer.SetResolution(gfxPrefs::LowPrecisionResolution()); mHasLowPrecision = gfxPrefs::UseLowPrecisionBuffer(); } void MultiTiledContentClient::ClearCachedResources() { CompositableClient::ClearCachedResources(); mTiledBuffer.DiscardBuffers(); mLowPrecisionTiledBuffer.DiscardBuffers(); } void MultiTiledContentClient::UpdatedBuffer(TiledBufferType aType) { ClientMultiTiledLayerBuffer* buffer = aType == LOW_PRECISION_TILED_BUFFER ? &mLowPrecisionTiledBuffer : &mTiledBuffer; MOZ_ASSERT(aType != LOW_PRECISION_TILED_BUFFER || mHasLowPrecision); // Take a ReadLock on behalf of the TiledContentHost. This // reference will be adopted when the descriptor is opened in // TiledLayerBufferComposite. buffer->ReadLock(); mForwarder->UseTiledLayerBuffer(this, buffer->GetSurfaceDescriptorTiles()); buffer->ClearPaintedRegion(); } SharedFrameMetricsHelper::SharedFrameMetricsHelper() : mLastProgressiveUpdateWasLowPrecision(false) , mProgressiveUpdateWasInDanger(false) { MOZ_COUNT_CTOR(SharedFrameMetricsHelper); } SharedFrameMetricsHelper::~SharedFrameMetricsHelper() { MOZ_COUNT_DTOR(SharedFrameMetricsHelper); } static inline bool FuzzyEquals(float a, float b) { return (fabsf(a - b) < 1e-6); } static AsyncTransform ComputeViewTransform(const FrameMetrics& aContentMetrics, const FrameMetrics& aCompositorMetrics) { // This is basically the same code as AsyncPanZoomController::GetCurrentAsyncTransform // but with aContentMetrics used in place of mLastContentPaintMetrics, because they // should be equivalent, modulo race conditions while transactions are inflight. ParentLayerPoint translation = (aCompositorMetrics.GetScrollOffset() - aContentMetrics.GetScrollOffset()) * aCompositorMetrics.GetZoom(); return AsyncTransform(aCompositorMetrics.GetAsyncZoom(), -translation); } bool SharedFrameMetricsHelper::UpdateFromCompositorFrameMetrics( const LayerMetricsWrapper& aLayer, bool aHasPendingNewThebesContent, bool aLowPrecision, AsyncTransform& aViewTransform) { MOZ_ASSERT(aLayer); CompositorBridgeChild* compositor = nullptr; if (aLayer.Manager() && aLayer.Manager()->AsClientLayerManager()) { compositor = aLayer.Manager()->AsClientLayerManager()->GetCompositorBridgeChild(); } if (!compositor) { return false; } const FrameMetrics& contentMetrics = aLayer.Metrics(); FrameMetrics compositorMetrics; if (!compositor->LookupCompositorFrameMetrics(contentMetrics.GetScrollId(), compositorMetrics)) { return false; } aViewTransform = ComputeViewTransform(contentMetrics, compositorMetrics); // Reset the checkerboard risk flag when switching to low precision // rendering. if (aLowPrecision && !mLastProgressiveUpdateWasLowPrecision) { // Skip low precision rendering until we're at risk of checkerboarding. if (!mProgressiveUpdateWasInDanger) { TILING_LOG("TILING: Aborting low-precision rendering because not at risk of checkerboarding\n"); return true; } mProgressiveUpdateWasInDanger = false; } mLastProgressiveUpdateWasLowPrecision = aLowPrecision; // Always abort updates if the resolution has changed. There's no use // in drawing at the incorrect resolution. if (!FuzzyEquals(compositorMetrics.GetZoom().xScale, contentMetrics.GetZoom().xScale) || !FuzzyEquals(compositorMetrics.GetZoom().yScale, contentMetrics.GetZoom().yScale)) { TILING_LOG("TILING: Aborting because resolution changed from %s to %s\n", ToString(contentMetrics.GetZoom()).c_str(), ToString(compositorMetrics.GetZoom()).c_str()); return true; } // Never abort drawing if we can't be sure we've sent a more recent // display-port. If we abort updating when we shouldn't, we can end up // with blank regions on the screen and we open up the risk of entering // an endless updating cycle. if (fabsf(contentMetrics.GetScrollOffset().x - compositorMetrics.GetScrollOffset().x) <= 2 && fabsf(contentMetrics.GetScrollOffset().y - compositorMetrics.GetScrollOffset().y) <= 2 && fabsf(contentMetrics.GetDisplayPort().x - compositorMetrics.GetDisplayPort().x) <= 2 && fabsf(contentMetrics.GetDisplayPort().y - compositorMetrics.GetDisplayPort().y) <= 2 && fabsf(contentMetrics.GetDisplayPort().width - compositorMetrics.GetDisplayPort().width) <= 2 && fabsf(contentMetrics.GetDisplayPort().height - compositorMetrics.GetDisplayPort().height) <= 2) { return false; } // When not a low precision pass and the page is in danger of checker boarding // abort update. if (!aLowPrecision && !mProgressiveUpdateWasInDanger) { bool scrollUpdatePending = contentMetrics.GetScrollOffsetUpdated() && contentMetrics.GetScrollGeneration() != compositorMetrics.GetScrollGeneration(); // If scrollUpdatePending is true, then that means the content-side // metrics has a new scroll offset that is going to be forced into the // compositor but it hasn't gotten there yet. // Even though right now comparing the metrics might indicate we're // about to checkerboard (and that's true), the checkerboarding will // disappear as soon as the new scroll offset update is processed // on the compositor side. To avoid leaving things in a low-precision // paint, we need to detect and handle this case (bug 1026756). if (!scrollUpdatePending && AboutToCheckerboard(contentMetrics, compositorMetrics)) { mProgressiveUpdateWasInDanger = true; return true; } } // Abort drawing stale low-precision content if there's a more recent // display-port in the pipeline. if (aLowPrecision && !aHasPendingNewThebesContent) { TILING_LOG("TILING: Aborting low-precision because of new pending content\n"); return true; } return false; } bool SharedFrameMetricsHelper::AboutToCheckerboard(const FrameMetrics& aContentMetrics, const FrameMetrics& aCompositorMetrics) { // The size of the painted area is originally computed in layer pixels in layout, but then // converted to app units and then back to CSS pixels before being put in the FrameMetrics. // This process can introduce some rounding error, so we inflate the rect by one app unit // to account for that. CSSRect painted = (aContentMetrics.GetCriticalDisplayPort().IsEmpty() ? aContentMetrics.GetDisplayPort() : aContentMetrics.GetCriticalDisplayPort()) + aContentMetrics.GetScrollOffset(); painted.Inflate(CSSMargin::FromAppUnits(nsMargin(1, 1, 1, 1))); // Inflate the rect by the danger zone. See the description of the danger zone prefs // in AsyncPanZoomController.cpp for an explanation of this. CSSRect showing = CSSRect(aCompositorMetrics.GetScrollOffset(), aCompositorMetrics.CalculateBoundedCompositedSizeInCssPixels()); showing.Inflate(LayerSize(gfxPrefs::APZDangerZoneX(), gfxPrefs::APZDangerZoneY()) / aCompositorMetrics.LayersPixelsPerCSSPixel()); // Clamp both rects to the scrollable rect, because having either of those // exceed the scrollable rect doesn't make sense, and could lead to false // positives. painted = painted.Intersect(aContentMetrics.GetScrollableRect()); showing = showing.Intersect(aContentMetrics.GetScrollableRect()); if (!painted.Contains(showing)) { TILING_LOG("TILING: About to checkerboard; content %s\n", Stringify(aContentMetrics).c_str()); TILING_LOG("TILING: About to checkerboard; painted %s\n", Stringify(painted).c_str()); TILING_LOG("TILING: About to checkerboard; compositor %s\n", Stringify(aCompositorMetrics).c_str()); TILING_LOG("TILING: About to checkerboard; showing %s\n", Stringify(showing).c_str()); return true; } return false; } ClientMultiTiledLayerBuffer::ClientMultiTiledLayerBuffer(ClientTiledPaintedLayer* aPaintedLayer, CompositableClient* aCompositableClient, ClientLayerManager* aManager, SharedFrameMetricsHelper* aHelper) : ClientTiledLayerBuffer(aPaintedLayer, aCompositableClient) , mManager(aManager) , mCallback(nullptr) , mCallbackData(nullptr) , mSharedFrameMetricsHelper(aHelper) { } bool ClientTiledLayerBuffer::HasFormatChanged() const { SurfaceMode mode; gfxContentType content = GetContentType(&mode); return content != mLastPaintContentType || mode != mLastPaintSurfaceMode; } gfxContentType ClientTiledLayerBuffer::GetContentType(SurfaceMode* aMode) const { gfxContentType content = mPaintedLayer->CanUseOpaqueSurface() ? gfxContentType::COLOR : gfxContentType::COLOR_ALPHA; SurfaceMode mode = mPaintedLayer->GetSurfaceMode(); if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { #if defined(MOZ_GFX_OPTIMIZE_MOBILE) || defined(MOZ_WIDGET_GONK) mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; #else if (!mPaintedLayer->GetParent() || !mPaintedLayer->GetParent()->SupportsComponentAlphaChildren() || !gfxPrefs::TiledDrawTargetEnabled()) { mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; } else { content = gfxContentType::COLOR; } #endif } else if (mode == SurfaceMode::SURFACE_OPAQUE) { #if defined(MOZ_GFX_OPTIMIZE_MOBILE) || defined(MOZ_WIDGET_GONK) if (IsLowPrecision()) { // If we're in low-res mode, drawing can sample from outside the visible // region. Make sure that we only sample transparency if that happens. mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; content = gfxContentType::COLOR_ALPHA; } #else if (mPaintedLayer->MayResample()) { mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; content = gfxContentType::COLOR_ALPHA; } #endif } if (aMode) { *aMode = mode; } return content; } gfxMemorySharedReadLock::gfxMemorySharedReadLock() : mReadCount(1) { MOZ_COUNT_CTOR(gfxMemorySharedReadLock); } gfxMemorySharedReadLock::~gfxMemorySharedReadLock() { MOZ_ASSERT(mReadCount == 0); MOZ_COUNT_DTOR(gfxMemorySharedReadLock); } int32_t gfxMemorySharedReadLock::ReadLock() { NS_ASSERT_OWNINGTHREAD(gfxMemorySharedReadLock); return PR_ATOMIC_INCREMENT(&mReadCount); } int32_t gfxMemorySharedReadLock::ReadUnlock() { int32_t readCount = PR_ATOMIC_DECREMENT(&mReadCount); MOZ_ASSERT(readCount >= 0); return readCount; } int32_t gfxMemorySharedReadLock::GetReadCount() { NS_ASSERT_OWNINGTHREAD(gfxMemorySharedReadLock); return mReadCount; } gfxShmSharedReadLock::gfxShmSharedReadLock(ClientIPCAllocator* aAllocator) : mAllocator(aAllocator) , mAllocSuccess(false) { MOZ_COUNT_CTOR(gfxShmSharedReadLock); MOZ_ASSERT(mAllocator); if (mAllocator) { #define MOZ_ALIGN_WORD(x) (((x) + 3) & ~3) if (mAllocator->AsLayerForwarder()->GetTileLockAllocator()->AllocShmemSection( MOZ_ALIGN_WORD(sizeof(ShmReadLockInfo)), &mShmemSection)) { ShmReadLockInfo* info = GetShmReadLockInfoPtr(); info->readCount = 1; mAllocSuccess = true; } } } gfxShmSharedReadLock::~gfxShmSharedReadLock() { MOZ_COUNT_DTOR(gfxShmSharedReadLock); } int32_t gfxShmSharedReadLock::ReadLock() { NS_ASSERT_OWNINGTHREAD(gfxShmSharedReadLock); if (!mAllocSuccess) { return 0; } ShmReadLockInfo* info = GetShmReadLockInfoPtr(); return PR_ATOMIC_INCREMENT(&info->readCount); } int32_t gfxShmSharedReadLock::ReadUnlock() { if (!mAllocSuccess) { return 0; } ShmReadLockInfo* info = GetShmReadLockInfoPtr(); int32_t readCount = PR_ATOMIC_DECREMENT(&info->readCount); MOZ_ASSERT(readCount >= 0); if (readCount <= 0) { auto fwd = mAllocator->AsLayerForwarder(); if (fwd) { fwd->GetTileLockAllocator()->DeallocShmemSection(mShmemSection); } else { // we are on the compositor process FixedSizeSmallShmemSectionAllocator::FreeShmemSection(mShmemSection); } } return readCount; } int32_t gfxShmSharedReadLock::GetReadCount() { NS_ASSERT_OWNINGTHREAD(gfxShmSharedReadLock); if (!mAllocSuccess) { return 0; } ShmReadLockInfo* info = GetShmReadLockInfoPtr(); return info->readCount; } class TileExpiry final : public nsExpirationTracker { public: TileExpiry() : nsExpirationTracker(1000, "TileExpiry") {} static void AddTile(TileClient* aTile) { if (!sTileExpiry) { sTileExpiry = MakeUnique(); } sTileExpiry->AddObject(aTile); } static void RemoveTile(TileClient* aTile) { MOZ_ASSERT(sTileExpiry); sTileExpiry->RemoveObject(aTile); } static void Shutdown() { sTileExpiry = nullptr; } private: virtual void NotifyExpired(TileClient* aTile) override { aTile->DiscardBackBuffer(); } static UniquePtr sTileExpiry; }; UniquePtr TileExpiry::sTileExpiry; void ShutdownTileCache() { TileExpiry::Shutdown(); } void TileClient::PrivateProtector::Set(TileClient * const aContainer, RefPtr aNewValue) { if (mBuffer) { TileExpiry::RemoveTile(aContainer); } mBuffer = aNewValue; if (mBuffer) { TileExpiry::AddTile(aContainer); } } void TileClient::PrivateProtector::Set(TileClient * const aContainer, TextureClient* aNewValue) { Set(aContainer, RefPtr(aNewValue)); } // Placeholder TileClient::TileClient() : mCompositableClient(nullptr) { } TileClient::~TileClient() { if (mExpirationState.IsTracked()) { MOZ_ASSERT(mBackBuffer); TileExpiry::RemoveTile(this); } } TileClient::TileClient(const TileClient& o) { mBackBuffer.Set(this, o.mBackBuffer); mBackBufferOnWhite = o.mBackBufferOnWhite; mFrontBuffer = o.mFrontBuffer; mFrontBufferOnWhite = o.mFrontBufferOnWhite; mBackLock = o.mBackLock; mFrontLock = o.mFrontLock; mCompositableClient = o.mCompositableClient; mUpdateRect = o.mUpdateRect; #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY mLastUpdate = o.mLastUpdate; #endif mManager = o.mManager; mAllocator = o.mAllocator; mInvalidFront = o.mInvalidFront; mInvalidBack = o.mInvalidBack; } TileClient& TileClient::operator=(const TileClient& o) { if (this == &o) return *this; mBackBuffer.Set(this, o.mBackBuffer); mBackBufferOnWhite = o.mBackBufferOnWhite; mFrontBuffer = o.mFrontBuffer; mFrontBufferOnWhite = o.mFrontBufferOnWhite; mBackLock = o.mBackLock; mFrontLock = o.mFrontLock; mCompositableClient = o.mCompositableClient; mUpdateRect = o.mUpdateRect; #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY mLastUpdate = o.mLastUpdate; #endif mManager = o.mManager; mAllocator = o.mAllocator; mInvalidFront = o.mInvalidFront; mInvalidBack = o.mInvalidBack; return *this; } void TileClient::Dump(std::stringstream& aStream) { aStream << "TileClient(bb=" << (TextureClient*)mBackBuffer << " fb=" << mFrontBuffer.get(); if (mBackBufferOnWhite) { aStream << " bbow=" << mBackBufferOnWhite.get(); } if (mFrontBufferOnWhite) { aStream << " fbow=" << mFrontBufferOnWhite.get(); } aStream << ")"; } void TileClient::Flip() { if (mCompositableClient) { if (mFrontBuffer) { mFrontBuffer->RemoveFromCompositable(mCompositableClient); } if (mFrontBufferOnWhite) { mFrontBufferOnWhite->RemoveFromCompositable(mCompositableClient); } } RefPtr frontBuffer = mFrontBuffer; RefPtr frontBufferOnWhite = mFrontBufferOnWhite; mFrontBuffer = mBackBuffer; mFrontBufferOnWhite = mBackBufferOnWhite; mBackBuffer.Set(this, frontBuffer); mBackBufferOnWhite = frontBufferOnWhite; RefPtr frontLock = mFrontLock; mFrontLock = mBackLock; mBackLock = frontLock; nsIntRegion invalidFront = mInvalidFront; mInvalidFront = mInvalidBack; mInvalidBack = invalidFront; } static bool CopyFrontToBack(TextureClient* aFront, TextureClient* aBack, const gfx::IntRect& aRectToCopy) { TextureClientAutoLock frontLock(aFront, OpenMode::OPEN_READ); if (!frontLock.Succeeded()) { gfxCriticalError() << "[Tiling:Client] Failed to lock the tile's front buffer"; return false; } if (!aBack->Lock(OpenMode::OPEN_READ_WRITE)) { gfxCriticalError() << "[Tiling:Client] Failed to lock the tile's back buffer"; return false; } gfx::IntPoint rectToCopyTopLeft = aRectToCopy.TopLeft(); aFront->CopyToTextureClient(aBack, &aRectToCopy, &rectToCopyTopLeft); return true; } void TileClient::ValidateBackBufferFromFront(const nsIntRegion& aDirtyRegion, nsIntRegion& aAddPaintedRegion) { if (mBackBuffer && mFrontBuffer) { gfx::IntSize tileSize = mFrontBuffer->GetSize(); const IntRect tileRect = IntRect(0, 0, tileSize.width, tileSize.height); if (aDirtyRegion.Contains(tileRect)) { // The dirty region means that we no longer need the front buffer, so // discard it. DiscardFrontBuffer(); } else { // Region that needs copying. nsIntRegion regionToCopy = mInvalidBack; regionToCopy.Sub(regionToCopy, aDirtyRegion); aAddPaintedRegion = regionToCopy; if (regionToCopy.IsEmpty()) { // Just redraw it all. return; } // Copy the bounding rect of regionToCopy. As tiles are quite small, it // is unlikely that we'd save much by copying each individual rect of the // region, but we can reevaluate this if it becomes an issue. const IntRect rectToCopy = regionToCopy.GetBounds(); gfx::IntRect gfxRectToCopy(rectToCopy.x, rectToCopy.y, rectToCopy.width, rectToCopy.height); CopyFrontToBack(mFrontBuffer, mBackBuffer, gfxRectToCopy); if (mBackBufferOnWhite) { MOZ_ASSERT(mFrontBufferOnWhite); CopyFrontToBack(mFrontBufferOnWhite, mBackBufferOnWhite, gfxRectToCopy); } mInvalidBack.SetEmpty(); } } } void TileClient::DiscardFrontBuffer() { if (mFrontBuffer) { MOZ_ASSERT(mFrontLock); if (mCompositableClient) { mFrontBuffer->RemoveFromCompositable(mCompositableClient); } mAllocator->ReturnTextureClientDeferred(mFrontBuffer); if (mFrontBufferOnWhite) { mFrontBufferOnWhite->RemoveFromCompositable(mCompositableClient); mAllocator->ReturnTextureClientDeferred(mFrontBufferOnWhite); } mFrontLock->ReadUnlock(); if (mFrontBuffer->IsLocked()) { mFrontBuffer->Unlock(); } if (mFrontBufferOnWhite && mFrontBufferOnWhite->IsLocked()) { mFrontBufferOnWhite->Unlock(); } mFrontBuffer = nullptr; mFrontBufferOnWhite = nullptr; mFrontLock = nullptr; } } void TileClient::DiscardBackBuffer() { if (mBackBuffer) { MOZ_ASSERT(mBackLock); if (!mBackBuffer->HasSynchronization() && mBackLock->GetReadCount() > 1) { // Our current back-buffer is still locked by the compositor. This can occur // when the client is producing faster than the compositor can consume. In // this case we just want to drop it and not return it to the pool. mAllocator->ReportClientLost(); if (mBackBufferOnWhite) { mAllocator->ReportClientLost(); } } else { mAllocator->ReturnTextureClientDeferred(mBackBuffer); if (mBackBufferOnWhite) { mAllocator->ReturnTextureClientDeferred(mBackBufferOnWhite); } } mBackLock->ReadUnlock(); if (mBackBuffer->IsLocked()) { mBackBuffer->Unlock(); } if (mBackBufferOnWhite && mBackBufferOnWhite->IsLocked()) { mBackBufferOnWhite->Unlock(); } mBackBuffer.Set(this, nullptr); mBackBufferOnWhite = nullptr; mBackLock = nullptr; } } TextureClient* TileClient::GetBackBuffer(const nsIntRegion& aDirtyRegion, gfxContentType aContent, SurfaceMode aMode, nsIntRegion& aAddPaintedRegion, RefPtr* aBackBufferOnWhite) { // Try to re-use the front-buffer if possible bool createdTextureClient = false; if (mFrontBuffer && mFrontBuffer->HasIntermediateBuffer() && mFrontLock->GetReadCount() == 1 && !(aMode == SurfaceMode::SURFACE_COMPONENT_ALPHA && !mFrontBufferOnWhite)) { // If we had a backbuffer we no longer care about it since we'll // re-use the front buffer. DiscardBackBuffer(); Flip(); } else { if (!mBackBuffer || mBackLock->GetReadCount() > 1) { if (mBackLock) { // Before we Replacing the lock by another one we need to unlock it! mBackLock->ReadUnlock(); } if (mBackBuffer) { // Our current back-buffer is still locked by the compositor. This can occur // when the client is producing faster than the compositor can consume. In // this case we just want to drop it and not return it to the pool. mAllocator->ReportClientLost(); } if (mBackBufferOnWhite) { mAllocator->ReportClientLost(); mBackBufferOnWhite = nullptr; } mBackBuffer.Set(this, mAllocator->GetTextureClient()); if (!mBackBuffer) { gfxCriticalError() << "[Tiling:Client] Failed to allocate a TextureClient (B)"; return nullptr; } if (aMode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { mBackBufferOnWhite = mAllocator->GetTextureClient(); if (!mBackBufferOnWhite) { mBackBuffer.Set(this, nullptr); gfxCriticalError() << "[Tiling:Client] Failed to allocate a TextureClient (W)"; return nullptr; } } // Create a lock for our newly created back-buffer. if (mManager->AsShadowForwarder()->IsSameProcess()) { // If our compositor is in the same process, we can save some cycles by not // using shared memory. mBackLock = new gfxMemorySharedReadLock(); } else { mBackLock = new gfxShmSharedReadLock(mManager->AsShadowForwarder()); } MOZ_ASSERT(mBackLock->IsValid()); createdTextureClient = true; mInvalidBack = IntRect(0, 0, mBackBuffer->GetSize().width, mBackBuffer->GetSize().height); } ValidateBackBufferFromFront(aDirtyRegion, aAddPaintedRegion); } if (!mBackBuffer->IsLocked()) { if (!mBackBuffer->Lock(OpenMode::OPEN_READ_WRITE)) { gfxCriticalError() << "[Tiling:Client] Failed to lock a tile (B)"; DiscardBackBuffer(); DiscardFrontBuffer(); return nullptr; } } if (mBackBufferOnWhite && !mBackBufferOnWhite->IsLocked()) { if (!mBackBufferOnWhite->Lock(OpenMode::OPEN_READ_WRITE)) { gfxCriticalError() << "[Tiling:Client] Failed to lock a tile (W)"; DiscardBackBuffer(); DiscardFrontBuffer(); return nullptr; } } if (createdTextureClient) { if (!mCompositableClient->AddTextureClient(mBackBuffer)) { gfxCriticalError() << "[Tiling:Client] Failed to connect a TextureClient (a)"; DiscardFrontBuffer(); DiscardBackBuffer(); return nullptr; } if (mBackBufferOnWhite && !mCompositableClient->AddTextureClient(mBackBufferOnWhite)) { gfxCriticalError() << "[Tiling:Client] Failed to connect a TextureClient (b)"; DiscardFrontBuffer(); DiscardBackBuffer(); return nullptr; } } *aBackBufferOnWhite = mBackBufferOnWhite; return mBackBuffer; } TileDescriptor TileClient::GetTileDescriptor() { if (IsPlaceholderTile()) { return PlaceholderTileDescriptor(); } MOZ_ASSERT(mFrontLock); if (mFrontLock->GetType() == gfxSharedReadLock::TYPE_MEMORY) { // AddRef here and Release when receiving on the host side to make sure the // reference count doesn't go to zero before the host receives the message. // see TiledLayerBufferComposite::TiledLayerBufferComposite mFrontLock.get()->AddRef(); } if (mFrontLock->GetType() == gfxSharedReadLock::TYPE_MEMORY) { return TexturedTileDescriptor(nullptr, mFrontBuffer->GetIPDLActor(), mFrontBufferOnWhite ? MaybeTexture(mFrontBufferOnWhite->GetIPDLActor()) : MaybeTexture(null_t()), mUpdateRect, TileLock(uintptr_t(mFrontLock.get()))); } else { gfxShmSharedReadLock *lock = static_cast(mFrontLock.get()); return TexturedTileDescriptor(nullptr, mFrontBuffer->GetIPDLActor(), mFrontBufferOnWhite ? MaybeTexture(mFrontBufferOnWhite->GetIPDLActor()) : MaybeTexture(null_t()), mUpdateRect, TileLock(lock->GetShmemSection())); } } void ClientMultiTiledLayerBuffer::ReadLock() { for (TileClient& tile : mRetainedTiles) { if (!tile.IsPlaceholderTile()) { tile.ReadLock(); } } } void ClientMultiTiledLayerBuffer::DiscardBuffers() { for (TileClient& tile : mRetainedTiles) { tile.DiscardBuffers(); } } SurfaceDescriptorTiles ClientMultiTiledLayerBuffer::GetSurfaceDescriptorTiles() { InfallibleTArray tiles; for (TileClient& tile : mRetainedTiles) { TileDescriptor tileDesc; if (tile.IsPlaceholderTile()) { tileDesc = PlaceholderTileDescriptor(); } else { tileDesc = tile.GetTileDescriptor(); } tiles.AppendElement(tileDesc); // Reset the update rect tile.mUpdateRect = IntRect(); } return SurfaceDescriptorTiles(mValidRegion, tiles, mTileOrigin, mTileSize, mTiles.mFirst.x, mTiles.mFirst.y, mTiles.mSize.width, mTiles.mSize.height, mResolution, mFrameResolution.xScale, mFrameResolution.yScale); } void ClientMultiTiledLayerBuffer::PaintThebes(const nsIntRegion& aNewValidRegion, const nsIntRegion& aPaintRegion, const nsIntRegion& aDirtyRegion, LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData) { TILING_LOG("TILING %p: PaintThebes painting region %s\n", mPaintedLayer, Stringify(aPaintRegion).c_str()); TILING_LOG("TILING %p: PaintThebes new valid region %s\n", mPaintedLayer, Stringify(aNewValidRegion).c_str()); mCallback = aCallback; mCallbackData = aCallbackData; #ifdef GFX_TILEDLAYER_PREF_WARNINGS long start = PR_IntervalNow(); #endif if (!gfxPrefs::TiledDrawTargetEnabled()) { if (!aPaintRegion.IsEmpty()) { RefPtr ctxt; const IntRect bounds = aPaintRegion.GetBounds(); { PROFILER_LABEL("ClientMultiTiledLayerBuffer", "PaintThebesSingleBufferAlloc", js::ProfileEntry::Category::GRAPHICS); mSinglePaintDrawTarget = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( gfx::IntSize(ceilf(bounds.width * mResolution), ceilf(bounds.height * mResolution)), gfxPlatform::GetPlatform()->Optimal2DFormatForContent( GetContentType())); if (!mSinglePaintDrawTarget) { return; } ctxt = new gfxContext(mSinglePaintDrawTarget); mSinglePaintBufferOffset = nsIntPoint(bounds.x, bounds.y); } ctxt->NewPath(); ctxt->SetMatrix( ctxt->CurrentMatrix().Scale(mResolution, mResolution). Translate(-bounds.x, -bounds.y)); #ifdef GFX_TILEDLAYER_PREF_WARNINGS if (PR_IntervalNow() - start > 3) { printf_stderr("Slow alloc %i\n", PR_IntervalNow() - start); } start = PR_IntervalNow(); #endif PROFILER_LABEL("ClientMultiTiledLayerBuffer", "PaintThebesSingleBufferDraw", js::ProfileEntry::Category::GRAPHICS); mCallback(mPaintedLayer, ctxt, aPaintRegion, aDirtyRegion, DrawRegionClip::NONE, nsIntRegion(), mCallbackData); } } #ifdef GFX_TILEDLAYER_PREF_WARNINGS if (PR_IntervalNow() - start > 30) { const IntRect bounds = aPaintRegion.GetBounds(); printf_stderr("Time to draw %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, bounds.x, bounds.y, bounds.width, bounds.height); if (aPaintRegion.IsComplex()) { printf_stderr("Complex region\n"); for (auto iter = aPaintRegion.RectIter(); !iter.Done(); iter.Next()) { const IntRect& rect = iter.Get(); printf_stderr(" rect %i, %i, %i, %i\n", rect.x, rect.y, rect.width, rect.height); } } } start = PR_IntervalNow(); #endif PROFILER_LABEL("ClientMultiTiledLayerBuffer", "PaintThebesUpdate", js::ProfileEntry::Category::GRAPHICS); mNewValidRegion = aNewValidRegion; Update(aNewValidRegion, aPaintRegion, aDirtyRegion); #ifdef GFX_TILEDLAYER_PREF_WARNINGS if (PR_IntervalNow() - start > 10) { const IntRect bounds = aPaintRegion.GetBounds(); printf_stderr("Time to tile %i: %i, %i, %i, %i\n", PR_IntervalNow() - start, bounds.x, bounds.y, bounds.width, bounds.height); } #endif mLastPaintContentType = GetContentType(&mLastPaintSurfaceMode); mCallback = nullptr; mCallbackData = nullptr; mSinglePaintDrawTarget = nullptr; } void PadDrawTargetOutFromRegion(RefPtr drawTarget, nsIntRegion ®ion) { struct LockedBits { uint8_t *data; IntSize size; int32_t stride; SurfaceFormat format; static int clamp(int x, int min, int max) { if (x < min) x = min; if (x > max) x = max; return x; } static void ensure_memcpy(uint8_t *dst, uint8_t *src, size_t n, uint8_t *bitmap, int stride, int height) { if (src + n > bitmap + stride*height) { MOZ_CRASH("GFX: long src memcpy"); } if (src < bitmap) { MOZ_CRASH("GFX: short src memcpy"); } if (dst + n > bitmap + stride*height) { MOZ_CRASH("GFX: long dst mempcy"); } if (dst < bitmap) { MOZ_CRASH("GFX: short dst mempcy"); } } static void visitor(void *closure, VisitSide side, int x1, int y1, int x2, int y2) { LockedBits *lb = static_cast(closure); uint8_t *bitmap = lb->data; const int bpp = gfx::BytesPerPixel(lb->format); const int stride = lb->stride; const int width = lb->size.width; const int height = lb->size.height; if (side == VisitSide::TOP) { if (y1 > 0) { x1 = clamp(x1, 0, width - 1); x2 = clamp(x2, 0, width - 1); ensure_memcpy(&bitmap[x1*bpp + (y1-1) * stride], &bitmap[x1*bpp + y1 * stride], (x2 - x1) * bpp, bitmap, stride, height); memcpy(&bitmap[x1*bpp + (y1-1) * stride], &bitmap[x1*bpp + y1 * stride], (x2 - x1) * bpp); } } else if (side == VisitSide::BOTTOM) { if (y1 < height) { x1 = clamp(x1, 0, width - 1); x2 = clamp(x2, 0, width - 1); ensure_memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[x1*bpp + (y1-1) * stride], (x2 - x1) * bpp, bitmap, stride, height); memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[x1*bpp + (y1-1) * stride], (x2 - x1) * bpp); } } else if (side == VisitSide::LEFT) { if (x1 > 0) { while (y1 != y2) { memcpy(&bitmap[(x1-1)*bpp + y1 * stride], &bitmap[x1*bpp + y1*stride], bpp); y1++; } } } else if (side == VisitSide::RIGHT) { if (x1 < width) { while (y1 != y2) { memcpy(&bitmap[x1*bpp + y1 * stride], &bitmap[(x1-1)*bpp + y1*stride], bpp); y1++; } } } } } lb; if (drawTarget->LockBits(&lb.data, &lb.size, &lb.stride, &lb.format)) { // we can only pad software targets so if we can't lock the bits don't pad region.VisitEdges(lb.visitor, &lb); drawTarget->ReleaseBits(lb.data); } } void ClientTiledLayerBuffer::UnlockTile(TileClient& aTile) { // We locked the back buffer, and flipped so we now need to unlock the front if (aTile.mFrontBuffer && aTile.mFrontBuffer->IsLocked()) { aTile.mFrontBuffer->Unlock(); aTile.mFrontBuffer->SyncWithObject(mCompositableClient->GetForwarder()->GetSyncObject()); } if (aTile.mFrontBufferOnWhite && aTile.mFrontBufferOnWhite->IsLocked()) { aTile.mFrontBufferOnWhite->Unlock(); aTile.mFrontBufferOnWhite->SyncWithObject(mCompositableClient->GetForwarder()->GetSyncObject()); } if (aTile.mBackBuffer && aTile.mBackBuffer->IsLocked()) { aTile.mBackBuffer->Unlock(); } if (aTile.mBackBufferOnWhite && aTile.mBackBufferOnWhite->IsLocked()) { aTile.mBackBufferOnWhite->Unlock(); } } void ClientMultiTiledLayerBuffer::Update(const nsIntRegion& newValidRegion, const nsIntRegion& aPaintRegion, const nsIntRegion& aDirtyRegion) { const IntSize scaledTileSize = GetScaledTileSize(); const gfx::IntRect newBounds = newValidRegion.GetBounds(); const TilesPlacement oldTiles = mTiles; const TilesPlacement newTiles(floor_div(newBounds.x, scaledTileSize.width), floor_div(newBounds.y, scaledTileSize.height), floor_div(GetTileStart(newBounds.x, scaledTileSize.width) + newBounds.width, scaledTileSize.width) + 1, floor_div(GetTileStart(newBounds.y, scaledTileSize.height) + newBounds.height, scaledTileSize.height) + 1); const size_t oldTileCount = mRetainedTiles.Length(); const size_t newTileCount = newTiles.mSize.width * newTiles.mSize.height; nsTArray oldRetainedTiles; mRetainedTiles.SwapElements(oldRetainedTiles); mRetainedTiles.SetLength(newTileCount); for (size_t oldIndex = 0; oldIndex < oldTileCount; oldIndex++) { const TileIntPoint tilePosition = oldTiles.TilePosition(oldIndex); const size_t newIndex = newTiles.TileIndex(tilePosition); // First, get the already existing tiles to the right place in the new array. // Leave placeholders (default constructor) where there was no tile. if (newTiles.HasTile(tilePosition)) { mRetainedTiles[newIndex] = oldRetainedTiles[oldIndex]; } else { // release tiles that we are not going to reuse before allocating new ones // to avoid allocating unnecessarily. oldRetainedTiles[oldIndex].DiscardBuffers(); } } oldRetainedTiles.Clear(); if (!aPaintRegion.IsEmpty()) { for (size_t i = 0; i < newTileCount; ++i) { const TileIntPoint tilePosition = newTiles.TilePosition(i); IntPoint tileOffset = GetTileOffset(tilePosition); nsIntRegion tileDrawRegion = IntRect(tileOffset, scaledTileSize); tileDrawRegion.AndWith(aPaintRegion); if (tileDrawRegion.IsEmpty()) { continue; } TileClient& tile = mRetainedTiles[i]; if (!ValidateTile(tile, GetTileOffset(tilePosition), tileDrawRegion)) { gfxCriticalError() << "ValidateTile failed"; } } if (gfxPrefs::TiledDrawTargetEnabled() && mMoz2DTiles.size() > 0) { gfx::TileSet tileset; for (size_t i = 0; i < mMoz2DTiles.size(); ++i) { mMoz2DTiles[i].mTileOrigin -= mTilingOrigin; } tileset.mTiles = &mMoz2DTiles[0]; tileset.mTileCount = mMoz2DTiles.size(); RefPtr drawTarget = gfx::Factory::CreateTiledDrawTarget(tileset); drawTarget->SetTransform(Matrix()); RefPtr ctx = new gfxContext(drawTarget); ctx->SetMatrix( ctx->CurrentMatrix().Scale(mResolution, mResolution).Translate(ThebesPoint(-mTilingOrigin))); mCallback(mPaintedLayer, ctx, aPaintRegion, aDirtyRegion, DrawRegionClip::DRAW, nsIntRegion(), mCallbackData); mMoz2DTiles.clear(); // Reset: mTilingOrigin = IntPoint(std::numeric_limits::max(), std::numeric_limits::max()); } bool edgePaddingEnabled = gfxPrefs::TileEdgePaddingEnabled(); for (uint32_t i = 0; i < mRetainedTiles.Length(); ++i) { TileClient& tile = mRetainedTiles[i]; // Only worry about padding when not doing low-res because it simplifies // the math and the artifacts won't be noticable // Edge padding prevents sampling artifacts when compositing. if (edgePaddingEnabled && mResolution == 1 && tile.mFrontBuffer && tile.mFrontBuffer->IsLocked()) { const TileIntPoint tilePosition = newTiles.TilePosition(i); IntPoint tileOffset = GetTileOffset(tilePosition); // Strictly speakig we want the unscaled rect here, but it doesn't matter // because we only run this code when the resolution is equal to 1. IntRect tileRect = IntRect(tileOffset.x, tileOffset.y, GetTileSize().width, GetTileSize().height); nsIntRegion tileDrawRegion = IntRect(tileOffset, scaledTileSize); tileDrawRegion.AndWith(aPaintRegion); nsIntRegion tileValidRegion = mValidRegion; tileValidRegion.OrWith(tileDrawRegion); // We only need to pad out if the tile has area that's not valid if (!tileValidRegion.Contains(tileRect)) { tileValidRegion = tileValidRegion.Intersect(tileRect); // translate the region into tile space and pad tileValidRegion.MoveBy(-IntPoint(tileOffset.x, tileOffset.y)); RefPtr drawTarget = tile.mFrontBuffer->BorrowDrawTarget(); PadDrawTargetOutFromRegion(drawTarget, tileValidRegion); } } UnlockTile(tile); } } mTiles = newTiles; mValidRegion = newValidRegion; mPaintedRegion.OrWith(aPaintRegion); } bool ClientMultiTiledLayerBuffer::ValidateTile(TileClient& aTile, const nsIntPoint& aTileOrigin, const nsIntRegion& aDirtyRegion) { PROFILER_LABEL("ClientMultiTiledLayerBuffer", "ValidateTile", js::ProfileEntry::Category::GRAPHICS); #ifdef GFX_TILEDLAYER_PREF_WARNINGS if (aDirtyRegion.IsComplex()) { printf_stderr("Complex region\n"); } #endif SurfaceMode mode; gfxContentType content = GetContentType(&mode); if (aTile.IsPlaceholderTile()) { aTile.SetLayerManager(mManager); aTile.SetTextureAllocator(mManager->GetTexturePool( gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content), TextureFlags::DISALLOW_BIGIMAGE | TextureFlags::IMMEDIATE_UPLOAD)); } aTile.SetCompositableClient(mCompositableClient); nsIntRegion offsetScaledDirtyRegion = aDirtyRegion.MovedBy(-aTileOrigin); offsetScaledDirtyRegion.ScaleRoundOut(mResolution, mResolution); bool usingTiledDrawTarget = gfxPrefs::TiledDrawTargetEnabled(); MOZ_ASSERT(usingTiledDrawTarget || !!mSinglePaintDrawTarget); nsIntRegion extraPainted; RefPtr backBufferOnWhite; RefPtr backBuffer = aTile.GetBackBuffer(offsetScaledDirtyRegion, content, mode, extraPainted, &backBufferOnWhite); aTile.mUpdateRect = offsetScaledDirtyRegion.GetBounds().Union(extraPainted.GetBounds()); extraPainted.MoveBy(aTileOrigin); extraPainted.And(extraPainted, mNewValidRegion); mPaintedRegion.Or(mPaintedRegion, extraPainted); if (!backBuffer) { return false; } if (usingTiledDrawTarget) { gfx::Tile moz2DTile; RefPtr dt = backBuffer->BorrowDrawTarget(); RefPtr dtOnWhite; if (backBufferOnWhite) { dtOnWhite = backBufferOnWhite->BorrowDrawTarget(); moz2DTile.mDrawTarget = Factory::CreateDualDrawTarget(dt, dtOnWhite); } else { moz2DTile.mDrawTarget = dt; } moz2DTile.mTileOrigin = gfx::IntPoint(aTileOrigin.x, aTileOrigin.y); if (!dt || (backBufferOnWhite && !dtOnWhite)) { aTile.DiscardBuffers(); return false; } mMoz2DTiles.push_back(moz2DTile); mTilingOrigin.x = std::min(mTilingOrigin.x, moz2DTile.mTileOrigin.x); mTilingOrigin.y = std::min(mTilingOrigin.y, moz2DTile.mTileOrigin.y); for (auto iter = aDirtyRegion.RectIter(); !iter.Done(); iter.Next()) { const IntRect& dirtyRect = iter.Get(); gfx::Rect drawRect(dirtyRect.x - aTileOrigin.x, dirtyRect.y - aTileOrigin.y, dirtyRect.width, dirtyRect.height); drawRect.Scale(mResolution); // Mark the newly updated area as invalid in the front buffer aTile.mInvalidFront.Or(aTile.mInvalidFront, IntRect(NS_lroundf(drawRect.x), NS_lroundf(drawRect.y), drawRect.width, drawRect.height)); if (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) { dt->FillRect(drawRect, ColorPattern(Color(0.0, 0.0, 0.0, 1.0))); dtOnWhite->FillRect(drawRect, ColorPattern(Color(1.0, 1.0, 1.0, 1.0))); } else if (content == gfxContentType::COLOR_ALPHA) { dt->ClearRect(drawRect); } } // The new buffer is now validated, remove the dirty region from it. aTile.mInvalidBack.SubOut(offsetScaledDirtyRegion); aTile.Flip(); return true; } // Single paint buffer case: MOZ_ASSERT(!backBufferOnWhite, "Component alpha only supported with TiledDrawTarget"); // We must not keep a reference to the DrawTarget after it has been unlocked, // make sure these are null'd before unlocking as destruction of the context // may cause the target to be flushed. RefPtr drawTarget = backBuffer->BorrowDrawTarget(); drawTarget->SetTransform(Matrix()); // XXX Perhaps we should just copy the bounding rectangle here? RefPtr source = mSinglePaintDrawTarget->Snapshot(); for (auto iter = aDirtyRegion.RectIter(); !iter.Done(); iter.Next()) { const IntRect& dirtyRect = iter.Get(); #ifdef GFX_TILEDLAYER_PREF_WARNINGS printf_stderr(" break into subdirtyRect %i, %i, %i, %i\n", dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); #endif gfx::Rect drawRect(dirtyRect.x - aTileOrigin.x, dirtyRect.y - aTileOrigin.y, dirtyRect.width, dirtyRect.height); drawRect.Scale(mResolution); gfx::IntRect copyRect(NS_lroundf((dirtyRect.x - mSinglePaintBufferOffset.x) * mResolution), NS_lroundf((dirtyRect.y - mSinglePaintBufferOffset.y) * mResolution), drawRect.width, drawRect.height); gfx::IntPoint copyTarget(NS_lroundf(drawRect.x), NS_lroundf(drawRect.y)); drawTarget->CopySurface(source, copyRect, copyTarget); // Mark the newly updated area as invalid in the front buffer aTile.mInvalidFront.Or(aTile.mInvalidFront, IntRect(copyTarget.x, copyTarget.y, copyRect.width, copyRect.height)); } // The new buffer is now validated, remove the dirty region from it. aTile.mInvalidBack.SubOut(offsetScaledDirtyRegion); #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY DrawDebugOverlay(drawTarget, aTileOrigin.x * mResolution, aTileOrigin.y * GetPresShellResolution(), GetTileLength(), GetTileLength()); #endif drawTarget = nullptr; nsIntRegion tileRegion = IntRect(aTileOrigin.x, aTileOrigin.y, GetScaledTileSize().width, GetScaledTileSize().height); // Intersect this area with the portion that's invalid. tileRegion.SubOut(GetValidRegion()); tileRegion.SubOut(aDirtyRegion); // Has now been validated backBuffer->SetWaste(tileRegion.Area() * mResolution * mResolution); aTile.Flip(); // Note, we don't call UpdatedTexture. The Updated function is called manually // by the TiledContentHost before composition. if (backBuffer->HasIntermediateBuffer()) { // If our new buffer has an internal buffer, we don't want to keep another // TextureClient around unnecessarily, so discard the back-buffer. aTile.DiscardBackBuffer(); } return true; } /** * This function takes the transform stored in aTransformToCompBounds * (which was generated in GetTransformToAncestorsParentLayer), and * modifies it with the ViewTransform from the compositor side so that * it reflects what the compositor is actually rendering. This operation * basically adds in the layer's async transform. * This function then returns the scroll ancestor's composition bounds, * transformed into the painted layer's LayerPixel coordinates, accounting * for the compositor state. */ static Maybe GetCompositorSideCompositionBounds(const LayerMetricsWrapper& aScrollAncestor, const LayerToParentLayerMatrix4x4& aTransformToCompBounds, const AsyncTransform& aAPZTransform, const LayerRect& aClip) { LayerToParentLayerMatrix4x4 transform = aTransformToCompBounds * AsyncTransformComponentMatrix(aAPZTransform); return UntransformBy(transform.Inverse(), aScrollAncestor.Metrics().GetCompositionBounds(), aClip); } bool ClientMultiTiledLayerBuffer::ComputeProgressiveUpdateRegion(const nsIntRegion& aInvalidRegion, const nsIntRegion& aOldValidRegion, nsIntRegion& aRegionToPaint, BasicTiledLayerPaintData* aPaintData, bool aIsRepeated) { aRegionToPaint = aInvalidRegion; // If the composition bounds rect is empty, we can't make any sensible // decision about how to update coherently. In this case, just update // everything in one transaction. if (aPaintData->mCompositionBounds.IsEmpty()) { aPaintData->mPaintFinished = true; return false; } // If this is a low precision buffer, we force progressive updates. The // assumption is that the contents is less important, so visual coherency // is lower priority than speed. bool drawingLowPrecision = IsLowPrecision(); // Find out if we have any non-stale content to update. nsIntRegion staleRegion; staleRegion.And(aInvalidRegion, aOldValidRegion); TILING_LOG("TILING %p: Progressive update stale region %s\n", mPaintedLayer, Stringify(staleRegion).c_str()); LayerMetricsWrapper scrollAncestor; mPaintedLayer->GetAncestorLayers(&scrollAncestor, nullptr, nullptr); // Find out the current view transform to determine which tiles to draw // first, and see if we should just abort this paint. Aborting is usually // caused by there being an incoming, more relevant paint. AsyncTransform viewTransform; #if defined(MOZ_WIDGET_ANDROID) && !defined(MOZ_ANDROID_APZ) FrameMetrics contentMetrics = scrollAncestor.Metrics(); bool abortPaint = false; // On Android, only the primary scrollable layer is async-scrolled, and the only one // that the Java-side code can provide details about. If we're tiling some other layer // then we already have all the information we need about it. if (contentMetrics.GetScrollId() == mManager->GetRootScrollableLayerId()) { FrameMetrics compositorMetrics = contentMetrics; // The ProgressiveUpdateCallback updates the compositorMetrics abortPaint = mManager->ProgressiveUpdateCallback(!staleRegion.Contains(aInvalidRegion), compositorMetrics, !drawingLowPrecision); viewTransform = ComputeViewTransform(contentMetrics, compositorMetrics); } #else MOZ_ASSERT(mSharedFrameMetricsHelper); bool abortPaint = mSharedFrameMetricsHelper->UpdateFromCompositorFrameMetrics( scrollAncestor, !staleRegion.Contains(aInvalidRegion), drawingLowPrecision, viewTransform); #endif TILING_LOG("TILING %p: Progressive update view transform %s zoom %f abort %d\n", mPaintedLayer, ToString(viewTransform.mTranslation).c_str(), viewTransform.mScale.scale, abortPaint); if (abortPaint) { // We ignore if front-end wants to abort if this is the first, // non-low-precision paint, as in that situation, we're about to override // front-end's page/viewport metrics. if (!aPaintData->mFirstPaint || drawingLowPrecision) { PROFILER_LABEL("ClientMultiTiledLayerBuffer", "ComputeProgressiveUpdateRegion", js::ProfileEntry::Category::GRAPHICS); aRegionToPaint.SetEmpty(); return aIsRepeated; } } Maybe transformedCompositionBounds = GetCompositorSideCompositionBounds(scrollAncestor, aPaintData->mTransformToCompBounds, viewTransform, ViewAs(Rect(mPaintedLayer->GetLayerBounds()))); if (!transformedCompositionBounds) { aPaintData->mPaintFinished = true; return false; } TILING_LOG("TILING %p: Progressive update transformed compositor bounds %s\n", mPaintedLayer, Stringify(*transformedCompositionBounds).c_str()); // Compute a "coherent update rect" that we should paint all at once in a // single transaction. This is to avoid rendering glitches on animated // page content, and when layers change size/shape. // On Fennec uploads are more expensive because we're not using gralloc, so // we use a coherent update rect that is intersected with the screen at the // time of issuing the draw command. This will paint faster but also potentially // make the progressive paint more visible to the user while scrolling. // On B2G uploads are cheaper and we value coherency more, especially outside // the browser, so we always use the entire user-visible area. IntRect coherentUpdateRect(RoundedOut( #ifdef MOZ_WIDGET_ANDROID transformedCompositionBounds->Intersect(aPaintData->mCompositionBounds) #else *transformedCompositionBounds #endif ).ToUnknownRect()); TILING_LOG("TILING %p: Progressive update final coherency rect %s\n", mPaintedLayer, Stringify(coherentUpdateRect).c_str()); aRegionToPaint.And(aInvalidRegion, coherentUpdateRect); aRegionToPaint.Or(aRegionToPaint, staleRegion); bool drawingStale = !aRegionToPaint.IsEmpty(); if (!drawingStale) { aRegionToPaint = aInvalidRegion; } // Prioritise tiles that are currently visible on the screen. bool paintingVisible = false; if (aRegionToPaint.Intersects(coherentUpdateRect)) { aRegionToPaint.And(aRegionToPaint, coherentUpdateRect); paintingVisible = true; } TILING_LOG("TILING %p: Progressive update final paint region %s\n", mPaintedLayer, Stringify(aRegionToPaint).c_str()); // Paint area that's visible and overlaps previously valid content to avoid // visible glitches in animated elements, such as gifs. bool paintInSingleTransaction = paintingVisible && (drawingStale || aPaintData->mFirstPaint); TILING_LOG("TILING %p: paintingVisible %d drawingStale %d firstPaint %d singleTransaction %d\n", mPaintedLayer, paintingVisible, drawingStale, aPaintData->mFirstPaint, paintInSingleTransaction); // The following code decides what order to draw tiles in, based on the // current scroll direction of the primary scrollable layer. NS_ASSERTION(!aRegionToPaint.IsEmpty(), "Unexpectedly empty paint region!"); IntRect paintBounds = aRegionToPaint.GetBounds(); int startX, incX, startY, incY; gfx::IntSize scaledTileSize = GetScaledTileSize(); if (aPaintData->mScrollOffset.x >= aPaintData->mLastScrollOffset.x) { startX = RoundDownToTileEdge(paintBounds.x, scaledTileSize.width); incX = scaledTileSize.width; } else { startX = RoundDownToTileEdge(paintBounds.XMost() - 1, scaledTileSize.width); incX = -scaledTileSize.width; } if (aPaintData->mScrollOffset.y >= aPaintData->mLastScrollOffset.y) { startY = RoundDownToTileEdge(paintBounds.y, scaledTileSize.height); incY = scaledTileSize.height; } else { startY = RoundDownToTileEdge(paintBounds.YMost() - 1, scaledTileSize.height); incY = -scaledTileSize.height; } // Find a tile to draw. IntRect tileBounds(startX, startY, scaledTileSize.width, scaledTileSize.height); int32_t scrollDiffX = aPaintData->mScrollOffset.x - aPaintData->mLastScrollOffset.x; int32_t scrollDiffY = aPaintData->mScrollOffset.y - aPaintData->mLastScrollOffset.y; // This loop will always terminate, as there is at least one tile area // along the first/last row/column intersecting with regionToPaint, or its // bounds would have been smaller. while (true) { aRegionToPaint.And(aInvalidRegion, tileBounds); if (!aRegionToPaint.IsEmpty()) { if (mResolution != 1) { // Paint the entire tile for low-res. This is aimed to fixing low-res resampling // and to avoid doing costly region accurate painting for a small area. aRegionToPaint = tileBounds; } break; } if (Abs(scrollDiffY) >= Abs(scrollDiffX)) { tileBounds.x += incX; } else { tileBounds.y += incY; } } if (!aRegionToPaint.Contains(aInvalidRegion)) { // The region needed to paint is larger then our progressive chunk size // therefore update what we want to paint and ask for a new paint transaction. // If we need to draw more than one tile to maintain coherency, make // sure it happens in the same transaction by requesting this work be // repeated immediately. // If this is unnecessary, the remaining work will be done tile-by-tile in // subsequent transactions. The caller code is responsible for scheduling // the subsequent transactions as long as we don't set the mPaintFinished // flag to true. return (!drawingLowPrecision && paintInSingleTransaction); } // We're not repeating painting and we've not requested a repeat transaction, // so the paint is finished. If there's still a separate low precision // paint to do, it will get marked as unfinished later. aPaintData->mPaintFinished = true; return false; } bool ClientMultiTiledLayerBuffer::ProgressiveUpdate(nsIntRegion& aValidRegion, nsIntRegion& aInvalidRegion, const nsIntRegion& aOldValidRegion, BasicTiledLayerPaintData* aPaintData, LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData) { TILING_LOG("TILING %p: Progressive update valid region %s\n", mPaintedLayer, Stringify(aValidRegion).c_str()); TILING_LOG("TILING %p: Progressive update invalid region %s\n", mPaintedLayer, Stringify(aInvalidRegion).c_str()); TILING_LOG("TILING %p: Progressive update old valid region %s\n", mPaintedLayer, Stringify(aOldValidRegion).c_str()); bool repeat = false; bool isBufferChanged = false; do { // Compute the region that should be updated. Repeat as many times as // is required. nsIntRegion regionToPaint; repeat = ComputeProgressiveUpdateRegion(aInvalidRegion, aOldValidRegion, regionToPaint, aPaintData, repeat); TILING_LOG("TILING %p: Progressive update computed paint region %s repeat %d\n", mPaintedLayer, Stringify(regionToPaint).c_str(), repeat); // There's no further work to be done. if (regionToPaint.IsEmpty()) { break; } isBufferChanged = true; // Keep track of what we're about to refresh. aValidRegion.Or(aValidRegion, regionToPaint); // aValidRegion may have been altered by InvalidateRegion, but we still // want to display stale content until it gets progressively updated. // Create a region that includes stale content. nsIntRegion validOrStale; validOrStale.Or(aValidRegion, aOldValidRegion); // Paint the computed region and subtract it from the invalid region. PaintThebes(validOrStale, regionToPaint, aInvalidRegion, aCallback, aCallbackData); aInvalidRegion.Sub(aInvalidRegion, regionToPaint); } while (repeat); TILING_LOG("TILING %p: Progressive update final valid region %s buffer changed %d\n", mPaintedLayer, Stringify(aValidRegion).c_str(), isBufferChanged); TILING_LOG("TILING %p: Progressive update final invalid region %s\n", mPaintedLayer, Stringify(aInvalidRegion).c_str()); // Return false if nothing has been drawn, or give what has been drawn // to the shadow layer to upload. return isBufferChanged; } void TiledContentClient::PrintInfo(std::stringstream& aStream, const char* aPrefix) { aStream << aPrefix; aStream << nsPrintfCString("%sTiledContentClient (0x%p)", mName, this).get(); if (profiler_feature_active("displaylistdump")) { nsAutoCString pfx(aPrefix); pfx += " "; Dump(aStream, pfx.get(), false); } } void TiledContentClient::Dump(std::stringstream& aStream, const char* aPrefix, bool aDumpHtml, TextureDumpMode aCompress) { GetTiledBuffer()->Dump(aStream, aPrefix, aDumpHtml, aCompress); } void BasicTiledLayerPaintData::ResetPaintData() { mLowPrecisionPaintCount = 0; mPaintFinished = false; mCompositionBounds.SetEmpty(); mCriticalDisplayPort = Nothing(); } } // namespace layers } // namespace mozilla