/* -*- 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 <math.h> // for ceil, ceilf, floor #include <algorithm> #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 "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 <sstream> 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") , mTiledBuffer(aPaintedLayer, *this, aManager, &mSharedFrameMetricsHelper) , mLowPrecisionTiledBuffer(aPaintedLayer, *this, aManager, &mSharedFrameMetricsHelper) { MOZ_COUNT_CTOR(MultiTiledContentClient); 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); 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) mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; #else if (!mPaintedLayer.GetParent() || !mPaintedLayer.GetParent()->SupportsComponentAlphaChildren()) { mode = SurfaceMode::SURFACE_SINGLE_CHANNEL_ALPHA; } else { content = gfxContentType::COLOR; } #endif } else if (mode == SurfaceMode::SURFACE_OPAQUE) { #if defined(MOZ_GFX_OPTIMIZE_MOBILE) 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; } class TileExpiry final : public nsExpirationTracker<TileClient, 3> { public: TileExpiry() : nsExpirationTracker<TileClient, 3>(1000, "TileExpiry") {} static void AddTile(TileClient* aTile) { if (!sTileExpiry) { sTileExpiry = MakeUnique<TileExpiry>(); } 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<TileExpiry> sTileExpiry; }; UniquePtr<TileExpiry> TileExpiry::sTileExpiry; void ShutdownTileCache() { TileExpiry::Shutdown(); } void TileClient::PrivateProtector::Set(TileClient * const aContainer, RefPtr<TextureClient> 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<TextureClient>(aNewValue)); } // Placeholder TileClient::TileClient() : mWasPlaceholder(false) { } 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; mWasPlaceholder = o.mWasPlaceholder; mUpdateRect = o.mUpdateRect; #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY mLastUpdate = o.mLastUpdate; #endif 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; mWasPlaceholder = o.mWasPlaceholder; mUpdateRect = o.mUpdateRect; #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY mLastUpdate = o.mLastUpdate; #endif 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() { RefPtr<TextureClient> frontBuffer = mFrontBuffer; RefPtr<TextureClient> frontBufferOnWhite = mFrontBufferOnWhite; mFrontBuffer = mBackBuffer; mFrontBufferOnWhite = mBackBufferOnWhite; mBackBuffer.Set(this, frontBuffer); mBackBufferOnWhite = frontBufferOnWhite; 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(mFrontBuffer->GetReadLock()); MOZ_ASSERT(mAllocator); if (mAllocator) { mAllocator->ReturnTextureClientDeferred(mFrontBuffer); if (mFrontBufferOnWhite) { mAllocator->ReturnTextureClientDeferred(mFrontBufferOnWhite); } } if (mFrontBuffer->IsLocked()) { mFrontBuffer->Unlock(); } if (mFrontBufferOnWhite && mFrontBufferOnWhite->IsLocked()) { mFrontBufferOnWhite->Unlock(); } mFrontBuffer = nullptr; mFrontBufferOnWhite = nullptr; } } static void DiscardTexture(TextureClient* aTexture, TextureClientAllocator* aAllocator) { MOZ_ASSERT(aAllocator); if (aTexture && aAllocator) { MOZ_ASSERT(aTexture->GetReadLock()); if (!aTexture->HasSynchronization() && aTexture->IsReadLocked()) { // 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. aAllocator->ReportClientLost(); } else { aAllocator->ReturnTextureClientDeferred(aTexture); } if (aTexture->IsLocked()) { aTexture->Unlock(); } } } void TileClient::DiscardBackBuffer() { if (mBackBuffer) { DiscardTexture(mBackBuffer, mAllocator); mBackBuffer.Set(this, nullptr); DiscardTexture(mBackBufferOnWhite, mAllocator); mBackBufferOnWhite = nullptr; } } static already_AddRefed<TextureClient> CreateBackBufferTexture(TextureClient* aCurrentTexture, CompositableClient& aCompositable, TextureClientAllocator* aAllocator) { if (aCurrentTexture) { // 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. aAllocator->ReportClientLost(); } RefPtr<TextureClient> texture = aAllocator->GetTextureClient(); if (!texture) { gfxCriticalError() << "[Tiling:Client] Failed to allocate a TextureClient"; return nullptr; } texture->EnableReadLock(); if (!aCompositable.AddTextureClient(texture)) { gfxCriticalError() << "[Tiling:Client] Failed to connect a TextureClient"; return nullptr; } return texture.forget(); } TextureClient* TileClient::GetBackBuffer(CompositableClient& aCompositable, const nsIntRegion& aDirtyRegion, gfxContentType aContent, SurfaceMode aMode, nsIntRegion& aAddPaintedRegion, RefPtr<TextureClient>* aBackBufferOnWhite) { if (!mAllocator) { gfxCriticalError() << "[TileClient] Missing TextureClientAllocator."; return nullptr; } if (aMode != SurfaceMode::SURFACE_COMPONENT_ALPHA) { // It can happen that a component-alpha layer stops being on component alpha // on the next frame, just drop the buffers on white if that happens. if (mBackBufferOnWhite) { mAllocator->ReportClientLost(); mBackBufferOnWhite = nullptr; } if (mFrontBufferOnWhite) { mAllocator->ReportClientLost(); mFrontBufferOnWhite = nullptr; } } // Try to re-use the front-buffer if possible if (mFrontBuffer && mFrontBuffer->HasIntermediateBuffer() && !mFrontBuffer->IsReadLocked() && (aMode != SurfaceMode::SURFACE_COMPONENT_ALPHA || ( mFrontBufferOnWhite && !mFrontBufferOnWhite->IsReadLocked()))) { // If we had a backbuffer we no longer care about it since we'll // re-use the front buffer. DiscardBackBuffer(); Flip(); } else { if (!mBackBuffer || mBackBuffer->IsReadLocked()) { mBackBuffer.Set(this, CreateBackBufferTexture(mBackBuffer, aCompositable, mAllocator) ); if (!mBackBuffer) { DiscardBackBuffer(); DiscardFrontBuffer(); return nullptr; } mInvalidBack = IntRect(IntPoint(), mBackBuffer->GetSize()); } if (aMode == SurfaceMode::SURFACE_COMPONENT_ALPHA && (!mBackBufferOnWhite || mBackBufferOnWhite->IsReadLocked())) { mBackBufferOnWhite = CreateBackBufferTexture( mBackBufferOnWhite, aCompositable, mAllocator ); if (!mBackBufferOnWhite) { DiscardBackBuffer(); DiscardFrontBuffer(); return nullptr; } mInvalidBack = IntRect(IntPoint(), mBackBufferOnWhite->GetSize()); } 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; } } *aBackBufferOnWhite = mBackBufferOnWhite; return mBackBuffer; } TileDescriptor TileClient::GetTileDescriptor() { if (IsPlaceholderTile()) { mWasPlaceholder = true; return PlaceholderTileDescriptor(); } bool wasPlaceholder = mWasPlaceholder; mWasPlaceholder = false; ReadLockDescriptor lock; mFrontBuffer->SerializeReadLock(lock); ReadLockDescriptor lockOnWhite = null_t(); if (mFrontBufferOnWhite) { mFrontBufferOnWhite->SerializeReadLock(lockOnWhite); } return TexturedTileDescriptor(nullptr, mFrontBuffer->GetIPDLActor(), mFrontBufferOnWhite ? MaybeTexture(mFrontBufferOnWhite->GetIPDLActor()) : MaybeTexture(null_t()), mUpdateRect, lock, lockOnWhite, wasPlaceholder); } void ClientMultiTiledLayerBuffer::DiscardBuffers() { for (TileClient& tile : mRetainedTiles) { tile.DiscardBuffers(); } } SurfaceDescriptorTiles ClientMultiTiledLayerBuffer::GetSurfaceDescriptorTiles() { InfallibleTArray<TileDescriptor> tiles; for (TileClient& tile : mRetainedTiles) { TileDescriptor 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, mWasLastPaintProgressive); } void ClientMultiTiledLayerBuffer::PaintThebes(const nsIntRegion& aNewValidRegion, const nsIntRegion& aPaintRegion, const nsIntRegion& aDirtyRegion, LayerManager::DrawPaintedLayerCallback aCallback, void* aCallbackData, bool aIsProgressive) { 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; mWasLastPaintProgressive = aIsProgressive; #ifdef GFX_TILEDLAYER_PREF_WARNINGS long start = PR_IntervalNow(); #endif #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; } void PadDrawTargetOutFromRegion(RefPtr<DrawTarget> 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<LockedBits*>(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<TileClient> 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 (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> drawTarget = gfx::Factory::CreateTiledDrawTarget(tileset); if (!drawTarget || !drawTarget->IsValid()) { gfxDevCrash(LogReason::InvalidContext) << "Invalid tiled draw target"; return; } drawTarget->SetTransform(Matrix()); RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(drawTarget); MOZ_ASSERT(ctx); // already checked the draw target above 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<int32_t>::max(), std::numeric_limits<int32_t>::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> 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.mAllocator) { aTile.SetTextureAllocator(mManager->GetCompositorBridgeChild()->GetTexturePool( mManager->AsShadowForwarder(), gfxPlatform::GetPlatform()->Optimal2DFormatForContent(content), TextureFlags::DISALLOW_BIGIMAGE | TextureFlags::IMMEDIATE_UPLOAD)); MOZ_ASSERT(aTile.mAllocator); } nsIntRegion offsetScaledDirtyRegion = aDirtyRegion.MovedBy(-aTileOrigin); offsetScaledDirtyRegion.ScaleRoundOut(mResolution, mResolution); nsIntRegion extraPainted; RefPtr<TextureClient> backBufferOnWhite; RefPtr<TextureClient> backBuffer = aTile.GetBackBuffer(mCompositableClient, 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; } gfx::Tile moz2DTile; RefPtr<DrawTarget> dt = backBuffer->BorrowDrawTarget(); RefPtr<DrawTarget> 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::RoundOut(drawRect)); 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; } /** * 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<LayerRect> 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; MOZ_ASSERT(mSharedFrameMetricsHelper); bool abortPaint = mSharedFrameMetricsHelper->UpdateFromCompositorFrameMetrics( scrollAncestor, !staleRegion.Contains(aInvalidRegion), drawingLowPrecision, viewTransform); 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<LayerRect> transformedCompositionBounds = GetCompositorSideCompositionBounds(scrollAncestor, aPaintData->mTransformToCompBounds, viewTransform, ViewAs<LayerPixel>(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, true); 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; mHasTransformAnimation = false; mCompositionBounds.SetEmpty(); mCriticalDisplayPort = Nothing(); } } // namespace layers } // namespace mozilla