summaryrefslogtreecommitdiffstats
path: root/gfx/layers/client/TiledContentClient.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/layers/client/TiledContentClient.cpp')
-rw-r--r--gfx/layers/client/TiledContentClient.cpp1430
1 files changed, 1430 insertions, 0 deletions
diff --git a/gfx/layers/client/TiledContentClient.cpp b/gfx/layers/client/TiledContentClient.cpp
new file mode 100644
index 000000000..c5677a8a3
--- /dev/null
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -0,0 +1,1430 @@
+/* -*- 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 &region)
+{
+ 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