already_AddRefed<gfx::DrawTarget>
PersistentBufferProviderShared::BorrowDrawTarget(const gfx::IntRect& aPersistedRect)
{
  if (!mFwd->GetTextureForwarder()->IPCOpen()) {
    return nullptr;
  }

  MOZ_ASSERT(!mSnapshot);

  if (IsActivityTracked()) {
    mFwd->GetActiveResourceTracker().MarkUsed(this);
  } else {
    mFwd->GetActiveResourceTracker().AddObject(this);
  }

  if (mDrawTarget) {
    RefPtr<gfx::DrawTarget> dt(mDrawTarget);
    return dt.forget();
  }

  mFront = Nothing();

  auto previousBackBuffer = mBack;

  TextureClient* tex = GetTexture(mBack);

  // First try to reuse the current back buffer. If we can do that it means
  // we can skip copying its content to the new back buffer.
  if (tex && tex->IsReadLocked()) {
    // The back buffer is currently used by the compositor, we can't draw
    // into it.
    tex = nullptr;
  }

  if (!tex) {
    // Try to grab an already allocated texture if any is available.
    for (uint32_t i = 0; i < mTextures.length(); ++i) {
      if (!mTextures[i]->IsReadLocked()) {
        mBack = Some(i);
        tex = mTextures[i];
        break;
      }
    }
  }

  if (!tex) {
    // We have to allocate a new texture.
    if (mTextures.length() >= 4) {
      // We should never need to buffer that many textures, something's wrong.
      MOZ_ASSERT(false);
      // In theory we throttle the main thread when the compositor can't keep up,
      // so we shoud never get in a situation where we sent 4 textures to the
      // compositor and the latter as not released any of them.
      // This seems to happen, however, in some edge cases such as just after a
      // device reset (cf. Bug 1291163).
      // It would be pretty bad to keep piling textures up at this point so we
      // call NotifyInactive to remove some of our textures.
      NotifyInactive();
      // Give up now. The caller can fall-back to a non-shared buffer provider.
      return nullptr;
    }

    RefPtr<TextureClient> newTexture = TextureClient::CreateForDrawing(
      mFwd, mFormat, mSize,
      BackendSelector::Canvas,
      TextureFlags::DEFAULT,
      TextureAllocationFlags::ALLOC_DEFAULT
    );

    MOZ_ASSERT(newTexture);
    if (newTexture) {
      if (mTextures.append(newTexture)) {
        tex = newTexture;
        mBack = Some<uint32_t>(mTextures.length() - 1);
      }
    }
  }

  if (!tex || !tex->Lock(OpenMode::OPEN_READ_WRITE)) {
    return nullptr;
  }

  if (mBack != previousBackBuffer && !aPersistedRect.IsEmpty()) {
    TextureClient* previous = GetTexture(previousBackBuffer);
    if (previous && previous->Lock(OpenMode::OPEN_READ)) {
      DebugOnly<bool> success = previous->CopyToTextureClient(tex, &aPersistedRect, nullptr);
      MOZ_ASSERT(success);

      previous->Unlock();
    }
  }

  mDrawTarget = tex->BorrowDrawTarget();

  RefPtr<gfx::DrawTarget> dt(mDrawTarget);
  return dt.forget();
}