/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 AUTO_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 "mozilla/layers/PaintThread.h"  // for PaintThread
#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 {

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;
}

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,
                TilePaintFlags aFlags,
                std::vector<CapturedTiledPaintState::Copy>* aCopies,
                std::vector<RefPtr<TextureClient>>* aClients)
{
  bool asyncPaint = !!(aFlags & TilePaintFlags::Async);
  OpenMode asyncFlags = asyncPaint ? OpenMode::OPEN_ASYNC : OpenMode::OPEN_NONE;

  TextureClientAutoLock frontLock(aFront, OpenMode::OPEN_READ | asyncFlags);
  if (!frontLock.Succeeded()) {
    return false;
  }

  if (!aBack->Lock(OpenMode::OPEN_READ_WRITE | asyncFlags)) {
    gfxCriticalError() << "[Tiling:Client] Failed to lock the tile's back buffer";
    return false;
  }

  RefPtr<gfx::DrawTarget> backBuffer = aBack->BorrowDrawTarget();
  if (!backBuffer) {
    gfxWarning() << "[Tiling:Client] Failed to aquire the back buffer's draw target";
    return false;
  }

  RefPtr<gfx::DrawTarget> frontBuffer = aFront->BorrowDrawTarget();
  if (!frontBuffer) {
    gfxWarning() << "[Tiling:Client] Failed to aquire the front buffer's draw target";
    return false;
  }

  auto copy = CapturedTiledPaintState::Copy{
    frontBuffer, backBuffer, aRectToCopy, aRectToCopy.TopLeft()
  };

  if (asyncPaint && aCopies) {
    aClients->push_back(aFront);
    aCopies->push_back(copy);
  } else {
    copy.CopyBuffer();
  }
  return true;
}

void
TileClient::ValidateBackBufferFromFront(const nsIntRegion& aDirtyRegion,
                                        const nsIntRegion& aVisibleRegion,
                                        nsIntRegion& aAddPaintedRegion,
                                        TilePaintFlags aFlags,
                                        std::vector<CapturedTiledPaintState::Copy>* aCopies,
                                        std::vector<RefPtr<TextureClient>>* aClients)
{
  if (!mBackBuffer || !mFrontBuffer) {
    return;
  }

  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();
    return;
  }

  // Region that needs copying.
  nsIntRegion regionToCopy = mInvalidBack;

  regionToCopy.Sub(regionToCopy, aDirtyRegion);
  regionToCopy.And(regionToCopy, aVisibleRegion);

  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();
  if (!CopyFrontToBack(mFrontBuffer, mBackBuffer, rectToCopy, aFlags, aCopies, aClients)) {
    return;
  }

  if (mBackBufferOnWhite) {
    MOZ_ASSERT(mFrontBufferOnWhite);
    if (!CopyFrontToBack(mFrontBufferOnWhite, mBackBufferOnWhite, rectToCopy, aFlags, aCopies, aClients)) {
      return;
    }
  }

  mInvalidBack.Sub(mInvalidBack, aVisibleRegion);
}

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;
  }
}

bool
TileClient::CopyFromBuffer(RefPtr<TextureClient> aBuffer,
                           RefPtr<TextureClient> aBufferOnWhite,
                           nsIntPoint aBufferOrigin,
                           nsIntPoint aTileOrigin,
                           const nsIntRegion& aRegion,
                           TilePaintFlags aFlags,
                           std::vector<CapturedTiledPaintState::Copy>* aCopies)
{
  if (aRegion.IsEmpty()) {
    return true;
  }

  bool asyncPaint = !!(aFlags & TilePaintFlags::Async);
  auto CopyBuffer = [&aRegion, asyncPaint, &aCopies] (auto aSrc, auto aSrcOrigin, auto aDest, auto aDestOrigin) {
    MOZ_ASSERT(aDest->IsLocked());

    OpenMode asyncFlags = asyncPaint ? OpenMode::OPEN_ASYNC
                                     : OpenMode::OPEN_NONE;
    TextureClientAutoLock lock(aSrc, OpenMode::OPEN_READ | asyncFlags);
    if (!lock.Succeeded()) {
      return false;
    }

    RefPtr<gfx::DrawTarget> srcTarget = aSrc->BorrowDrawTarget();
    RefPtr<gfx::DrawTarget> destTarget = aDest->BorrowDrawTarget();
    if (!srcTarget || !destTarget) {
      return false;
    }

    for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
      const gfx::IntRect src = iter.Get() - aSrcOrigin;
      const gfx::IntPoint dest = iter.Get().TopLeft() - aDestOrigin;

      auto copy = CapturedTiledPaintState::Copy{
        srcTarget, destTarget, src, dest
      };

      if (asyncPaint) {
        aCopies->push_back(copy);
      } else {
        copy.CopyBuffer();
      }
    }
    return true;
  };

  return CopyBuffer(aBuffer, aBufferOrigin, mBackBuffer, aTileOrigin) &&
    (!aBufferOnWhite ||
     !mBackBufferOnWhite ||
     CopyBuffer(aBufferOnWhite, aBufferOrigin, mBackBufferOnWhite, aTileOrigin));
}

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;
  }

  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,
                          const nsIntRegion& aVisibleRegion,
                          gfxContentType aContent,
                          SurfaceMode aMode,
                          nsIntRegion& aAddPaintedRegion,
                          TilePaintFlags aFlags,
                          RefPtr<TextureClient>* aBackBufferOnWhite,
                          std::vector<CapturedTiledPaintState::Copy>* aCopies,
                          std::vector<RefPtr<TextureClient>>* aClients)
{
  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 need it since we can re-use the
    // front buffer here. It can be worth it to hold on to the back buffer
    // so we don't need to pay the cost of initializing a new back buffer
    // later (copying pixels and texture upload). But this could increase
    // our memory usage and lead to OOM more frequently from spikes in usage,
    // so we have this behavior behind a pref.
    if (!gfxPrefs::LayersTileRetainBackBuffer()) {
      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, aVisibleRegion, aAddPaintedRegion, aFlags, aCopies, aClients);
  }

  OpenMode lockMode = aFlags & TilePaintFlags::Async ? OpenMode::OPEN_READ_WRITE_ASYNC
                                                     : OpenMode::OPEN_READ_WRITE;

  if (!mBackBuffer->IsLocked()) {
    if (!mBackBuffer->Lock(lockMode)) {
      gfxCriticalError() << "[Tiling:Client] Failed to lock a tile (B)";
      DiscardBackBuffer();
      DiscardFrontBuffer();
      return nullptr;
    }
  }

  if (mBackBufferOnWhite && !mBackBufferOnWhite->IsLocked()) {
    if (!mBackBufferOnWhite->Lock(lockMode)) {
      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;

  bool readLocked = mFrontBuffer->OnForwardedToHost();
  bool readLockedOnWhite = false;

  if (mFrontBufferOnWhite) {
    readLockedOnWhite = mFrontBufferOnWhite->OnForwardedToHost();
  }

  return TexturedTileDescriptor(nullptr, mFrontBuffer->GetIPDLActor(),
                                mFrontBufferOnWhite ? MaybeTexture(mFrontBufferOnWhite->GetIPDLActor()) : MaybeTexture(null_t()),
                                mUpdateRect,
                                readLocked, readLockedOnWhite,
                                wasPlaceholder);
}

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
TiledContentClient::PrintInfo(std::stringstream& aStream, const char* aPrefix)
{
  aStream << aPrefix;
  aStream << nsPrintfCString("%sTiledContentClient (0x%p)", mName, this).get();
}

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
