gfx/layers/client/SingleTiledContentClient.cpp
author Jamie Nicol <jnicol@mozilla.com>
Thu, 02 Nov 2017 11:46:08 +0000
changeset 443180 6f7cae36d92d36304c05df539135de6e913ab45c
parent 442323 2d2922ab35d243657108303c7262f4c2793cbe86
child 449895 6a8d5147a4164fcf67107403fe01f7050206bd2a
permissions -rw-r--r--
Bug 1413693 - Ensure data copied from discarded front buffer isn't also painted. r=mstange Bug 1092294 introduced a regression in to the code to copy from the discarded front buffers to the new backbuffers in SingleTiledContentClient. The aim was to ensure that if locking the the frontbuffers failed, meaning the region could not be copied, that it would be painted instead. However due to incorrect logic the region would both be copied and painted in cases where there was no onWhite buffers. To fix this we take both locks (frontLock, and frontOnWhiteLock if required) up front, so that we either copy both buffers or neither. MozReview-Commit-ID: 3iepOuweruk

/* -*- 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/SingleTiledContentClient.h"

#include "ClientTiledPaintedLayer.h"
#include "mozilla/Maybe.h"

namespace mozilla {
namespace layers {


SingleTiledContentClient::SingleTiledContentClient(ClientTiledPaintedLayer& aPaintedLayer,
                                                   ClientLayerManager* aManager)
  : TiledContentClient(aManager, "Single")
{
  MOZ_COUNT_CTOR(SingleTiledContentClient);

  mTiledBuffer = new ClientSingleTiledLayerBuffer(aPaintedLayer, *this, aManager);
}

void
SingleTiledContentClient::ClearCachedResources()
{
  CompositableClient::ClearCachedResources();
  mTiledBuffer->DiscardBuffers();
}

void
SingleTiledContentClient::UpdatedBuffer(TiledBufferType aType)
{
  mForwarder->UseTiledLayerBuffer(this, mTiledBuffer->GetSurfaceDescriptorTiles());
  mTiledBuffer->ClearPaintedRegion();
}

/* static */ bool
SingleTiledContentClient::ClientSupportsLayerSize(const gfx::IntSize& aSize, ClientLayerManager* aManager)
{
  int32_t maxTextureSize = aManager->GetMaxTextureSize();
  return aSize.width <= maxTextureSize && aSize.height <= maxTextureSize;
}

ClientSingleTiledLayerBuffer::ClientSingleTiledLayerBuffer(ClientTiledPaintedLayer& aPaintedLayer,
                                                           CompositableClient& aCompositableClient,
                                                           ClientLayerManager* aManager)
  : ClientTiledLayerBuffer(aPaintedLayer, aCompositableClient)
  , mWasLastPaintProgressive(false)
  , mFormat(gfx::SurfaceFormat::UNKNOWN)
{
}

void
ClientSingleTiledLayerBuffer::ReleaseTiles()
{
  if (!mTile.IsPlaceholderTile()) {
    mTile.DiscardBuffers();
  }
  mTile.SetTextureAllocator(nullptr);
}

void
ClientSingleTiledLayerBuffer::DiscardBuffers()
{
  if (!mTile.IsPlaceholderTile()) {
    mTile.DiscardFrontBuffer();
    mTile.DiscardBackBuffer();
  }
}

SurfaceDescriptorTiles
ClientSingleTiledLayerBuffer::GetSurfaceDescriptorTiles()
{
  InfallibleTArray<TileDescriptor> tiles;

  TileDescriptor tileDesc = mTile.GetTileDescriptor();
  tiles.AppendElement(tileDesc);
  mTile.mUpdateRect = gfx::IntRect();

  return SurfaceDescriptorTiles(mValidRegion,
                                tiles,
                                mTilingOrigin,
                                mSize,
                                0, 0, 1, 1,
                                1.0,
                                mFrameResolution.xScale,
                                mFrameResolution.yScale,
                                mWasLastPaintProgressive);
}

already_AddRefed<TextureClient>
ClientSingleTiledLayerBuffer::GetTextureClient()
{
  MOZ_ASSERT(mFormat != gfx::SurfaceFormat::UNKNOWN);
  return mCompositableClient.CreateTextureClientForDrawing(
    gfx::ImageFormatToSurfaceFormat(mFormat), mSize, BackendSelector::Content,
    TextureFlags::DISALLOW_BIGIMAGE | TextureFlags::IMMEDIATE_UPLOAD);
}

void
ClientSingleTiledLayerBuffer::PaintThebes(const nsIntRegion& aNewValidRegion,
                                          const nsIntRegion& aPaintRegion,
                                          const nsIntRegion& aDirtyRegion,
                                          LayerManager::DrawPaintedLayerCallback aCallback,
                                          void* aCallbackData,
                                          bool aIsProgressive)
{
  mWasLastPaintProgressive = aIsProgressive;

  // Compare layer valid region size to current backbuffer size, discard if not matching.
  gfx::IntSize size = aNewValidRegion.GetBounds().Size();
  gfx::IntPoint origin = aNewValidRegion.GetBounds().TopLeft();
  nsIntRegion paintRegion = aPaintRegion;

  RefPtr<TextureClient> discardedFrontBuffer = nullptr;
  RefPtr<TextureClient> discardedFrontBufferOnWhite = nullptr;
  nsIntRegion discardedValidRegion;

  if (mSize != size ||
      mTilingOrigin != origin) {
    discardedFrontBuffer = mTile.mFrontBuffer;
    discardedFrontBufferOnWhite = mTile.mFrontBufferOnWhite;
    discardedValidRegion = mValidRegion;

    TILING_LOG("TILING %p: Single-tile valid region changed. Discarding buffers.\n", &mPaintedLayer)
;
    ResetPaintedAndValidState();
    mSize = size;
    mTilingOrigin = origin;
    paintRegion = aNewValidRegion;
  }

  SurfaceMode mode;
  gfxContentType content = GetContentType(&mode);
  mFormat = gfxPlatform::GetPlatform()->OptimalFormatForContent(content);

  if (mTile.IsPlaceholderTile()) {
    mTile.SetTextureAllocator(this);
  }

  // The dirty region relative to the top-left of the tile.
  nsIntRegion tileDirtyRegion = paintRegion.MovedBy(-mTilingOrigin);

  nsIntRegion extraPainted;
  RefPtr<TextureClient> backBufferOnWhite;
  RefPtr<TextureClient> backBuffer =
    mTile.GetBackBuffer(mCompositableClient,
                        tileDirtyRegion,
                        content, mode,
                        extraPainted,
                        &backBufferOnWhite);

  // Mark the area we need to paint in the back buffer as invalid in the
  // front buffer as they will become out of sync.
  mTile.mInvalidFront.OrWith(tileDirtyRegion);

  // Add backbuffer's invalid region to the dirty region to be painted.
  // This will be empty if we were able to copy from the front in to the back.
  paintRegion.OrWith(mTile.mInvalidBack.MovedBy(mTilingOrigin));
  tileDirtyRegion.OrWith(mTile.mInvalidBack);

  mTile.mUpdateRect = tileDirtyRegion.GetBounds().Union(extraPainted.GetBounds());

  extraPainted.MoveBy(mTilingOrigin);
  extraPainted.And(extraPainted, aNewValidRegion);
  mPaintedRegion.OrWith(paintRegion);
  mPaintedRegion.OrWith(extraPainted);

  if (!backBuffer) {
    return;
  }

  RefPtr<gfx::DrawTarget> dt = backBuffer->BorrowDrawTarget();
  RefPtr<gfx::DrawTarget> dtOnWhite;
  if (backBufferOnWhite) {
    dtOnWhite = backBufferOnWhite->BorrowDrawTarget();
  }

  if (mode != SurfaceMode::SURFACE_OPAQUE) {
    for (auto iter = tileDirtyRegion.RectIter(); !iter.Done(); iter.Next()) {
      const gfx::IntRect& rect = iter.Get();
      if (dtOnWhite) {
        dt->FillRect(gfx::Rect(rect.x, rect.y, rect.width, rect.height),
                     gfx::ColorPattern(gfx::Color(0.0, 0.0, 0.0, 1.0)));
        dtOnWhite->FillRect(gfx::Rect(rect.x, rect.y, rect.width, rect.height),
                            gfx::ColorPattern(gfx::Color(1.0, 1.0, 1.0, 1.0)));
      } else {
        dt->ClearRect(gfx::Rect(rect.x, rect.y, rect.width, rect.height));
      }
    }
  }

  // If the old frontbuffer was discarded then attempt to copy what we
  // can from it to the new backbuffer.
  if (discardedFrontBuffer) {
    nsIntRegion copyableRegion;
    copyableRegion.And(aNewValidRegion, discardedValidRegion);
    copyableRegion.SubOut(aDirtyRegion);

    if (!copyableRegion.IsEmpty()) {
      TextureClientAutoLock frontLock(discardedFrontBuffer,
                                      OpenMode::OPEN_READ);
      Maybe<TextureClientAutoLock> frontOnWhiteLock;
      if (discardedFrontBufferOnWhite && backBufferOnWhite) {
        frontOnWhiteLock.emplace(discardedFrontBufferOnWhite, OpenMode::OPEN_READ);
      }

      // Copy to both backBuffer and backBufferOnWhite if required, or copy to neither.
      if (frontLock.Succeeded() && (!frontOnWhiteLock || frontOnWhiteLock->Succeeded())) {
        for (auto iter = copyableRegion.RectIter(); !iter.Done(); iter.Next()) {
          const gfx::IntRect rect = iter.Get() - discardedValidRegion.GetBounds().TopLeft();
          const gfx::IntPoint dest = iter.Get().TopLeft() - mTilingOrigin;
          discardedFrontBuffer->CopyToTextureClient(backBuffer, &rect, &dest);
        }

        if (frontOnWhiteLock) {
          for (auto iter = copyableRegion.RectIter(); !iter.Done(); iter.Next()) {
            const gfx::IntRect rect = iter.Get() - discardedValidRegion.GetBounds().TopLeft();
            const gfx::IntPoint dest = iter.Get().TopLeft() - mTilingOrigin;
            discardedFrontBufferOnWhite->CopyToTextureClient(backBufferOnWhite,
                                                             &rect, &dest);
          }
        }

        TILING_LOG("TILING %p: Region copied from discarded frontbuffer %s\n", &mPaintedLayer, Stringify(copyableRegion).c_str());

        // We don't need to repaint valid content that was just copied.
        paintRegion.SubOut(copyableRegion);
      }
    }
  }

  if (dtOnWhite) {
    dt = gfx::Factory::CreateDualDrawTarget(dt, dtOnWhite);
    dtOnWhite = nullptr;
  }

  {
    RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
    if (!ctx) {
      gfxDevCrash(gfx::LogReason::InvalidContext) << "SingleTiledContextClient context problem " << gfx::hexa(dt);
      return;
    }
    ctx->SetMatrix(ctx->CurrentMatrix().PreTranslate(-mTilingOrigin.x, -mTilingOrigin.y));

    aCallback(&mPaintedLayer, ctx, paintRegion, paintRegion, DrawRegionClip::DRAW, nsIntRegion(), aCallbackData);
  }

  // The new buffer is now validated, remove the dirty region from it.
  mTile.mInvalidBack.SubOut(tileDirtyRegion);

  dt = nullptr;

  mTile.Flip();
  UnlockTile(mTile);

  if (backBuffer->HasIntermediateBuffer()) {
    // If our new buffer has an internal buffer, we don't want to keep another
    // TextureClient around unnecessarily, so discard the back-buffer.
    mTile.DiscardBackBuffer();
  }

  mValidRegion = aNewValidRegion;
  mLastPaintSurfaceMode = mode;
  mLastPaintContentType = content;
}

} // namespace layers
} // namespace mozilla