bool
ClientLayerManager::ProgressiveUpdateCallback(bool aHasPendingNewThebesContent,
                                              FrameMetrics& aMetrics,
                                              bool aDrawingCritical)
{
#ifdef MOZ_WIDGET_ANDROID
  MOZ_ASSERT(aMetrics.IsScrollable());
  // This is derived from the code in
  // gfx/layers/ipc/CompositorParent.cpp::TransformShadowTree.
  CSSToLayerScale paintScale = aMetrics.LayersPixelsPerCSSPixel().ToScaleFactor();
  const CSSRect& metricsDisplayPort =
    (aDrawingCritical && !aMetrics.GetCriticalDisplayPort().IsEmpty()) ?
      aMetrics.GetCriticalDisplayPort() : aMetrics.GetDisplayPort();
  LayerRect displayPort = (metricsDisplayPort + aMetrics.GetScrollOffset()) * paintScale;

  ParentLayerPoint scrollOffset;
  CSSToParentLayerScale zoom;
  bool ret = AndroidBridge::Bridge()->ProgressiveUpdateCallback(
    aHasPendingNewThebesContent, displayPort, paintScale.scale, aDrawingCritical,
    scrollOffset, zoom);
  aMetrics.SetScrollOffset(scrollOffset / zoom);
  aMetrics.SetZoom(CSSToParentLayerScale2D(zoom));
  return ret;
#else
  return false;
#endif
}
Example #2
0
 FrameMetrics GetPinchableFrameMetrics()
 {
   FrameMetrics fm;
   fm.SetCompositionBounds(ParentLayerRect(200, 200, 100, 200));
   fm.SetScrollableRect(CSSRect(0, 0, 980, 1000));
   fm.SetScrollOffset(CSSPoint(300, 300));
   fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
   // APZC only allows zooming on the root scrollable frame.
   fm.SetIsRootContent(true);
   // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
   return fm;
 }
Example #3
0
TEST_F(APZCBasicTester, Overzoom) {
  // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100
  FrameMetrics fm;
  fm.SetCompositionBounds(ParentLayerRect(0, 0, 100, 100));
  fm.SetScrollableRect(CSSRect(0, 0, 125, 150));
  fm.SetScrollOffset(CSSPoint(10, 0));
  fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0));
  fm.SetIsRootContent(true);
  apzc->SetFrameMetrics(fm);

  MakeApzcZoomable();

  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);

  PinchWithPinchInputAndCheckStatus(apzc, ScreenIntPoint(50, 50), 0.5, true);

  fm = apzc->GetFrameMetrics();
  EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale);
  // bug 936721 - PGO builds introduce rounding error so
  // use a fuzzy match instead
  EXPECT_LT(std::abs(fm.GetScrollOffset().x), 1e-5);
  EXPECT_LT(std::abs(fm.GetScrollOffset().y), 1e-5);
}
Example #4
0
  void DoPinchTest(bool aShouldTriggerPinch,
                   nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr)
  {
    apzc->SetFrameMetrics(GetPinchableFrameMetrics());
    MakeApzcZoomable();

    if (aShouldTriggerPinch) {
      // One repaint request for each gesture.
      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(2);
    } else {
      EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
    }

    int touchInputId = 0;
    if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
      PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
    } else {
      PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch);
    }

    FrameMetrics fm = apzc->GetFrameMetrics();

    if (aShouldTriggerPinch) {
      // the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80
      EXPECT_EQ(2.5f, fm.GetZoom().ToScaleFactor().scale);
      EXPECT_EQ(305, fm.GetScrollOffset().x);
      EXPECT_EQ(310, fm.GetScrollOffset().y);
    } else {
      // The frame metrics should stay the same since touch-action:none makes
      // apzc ignore pinch gestures.
      EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale);
      EXPECT_EQ(300, fm.GetScrollOffset().x);
      EXPECT_EQ(300, fm.GetScrollOffset().y);
    }

    // part 2 of the test, move to the top-right corner of the page and pinch and
    // make sure we stay in the correct spot
    fm.SetZoom(CSSToParentLayerScale2D(2.0, 2.0));
    fm.SetScrollOffset(CSSPoint(930, 5));
    apzc->SetFrameMetrics(fm);
    // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100

    if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
      PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 0.5, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
    } else {
      PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 0.5, aShouldTriggerPinch);
    }

    fm = apzc->GetFrameMetrics();

    if (aShouldTriggerPinch) {
      // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200
      EXPECT_EQ(1.0f, fm.GetZoom().ToScaleFactor().scale);
      EXPECT_EQ(880, fm.GetScrollOffset().x);
      EXPECT_EQ(0, fm.GetScrollOffset().y);
    } else {
      EXPECT_EQ(2.0f, fm.GetZoom().ToScaleFactor().scale);
      EXPECT_EQ(930, fm.GetScrollOffset().x);
      EXPECT_EQ(5, fm.GetScrollOffset().y);
    }
  }
Example #5
0
TEST_F(APZCBasicTester, ComplexTransform) {
  // This test assumes there is a page that gets rendered to
  // two layers. In CSS pixels, the first layer is 50x50 and
  // the second layer is 25x50. The widget scale factor is 3.0
  // and the presShell resolution is 2.0. Therefore, these layers
  // end up being 300x300 and 150x300 in layer pixels.
  //
  // The second (child) layer has an additional CSS transform that
  // stretches it by 2.0 on the x-axis. Therefore, after applying
  // CSS transforms, the two layers are the same size in screen
  // pixels.
  //
  // The screen itself is 24x24 in screen pixels (therefore 4x4 in
  // CSS pixels). The displayport is 1 extra CSS pixel on all
  // sides.

  RefPtr<TestAsyncPanZoomController> childApzc =
      new TestAsyncPanZoomController(0, mcc, tm);

  const char* layerTreeSyntax = "c(c)";
  // LayerID                     0 1
  nsIntRegion layerVisibleRegion[] = {
    nsIntRegion(IntRect(0, 0, 300, 300)),
    nsIntRegion(IntRect(0, 0, 150, 300)),
  };
  Matrix4x4 transforms[] = {
    Matrix4x4(),
    Matrix4x4(),
  };
  transforms[0].PostScale(0.5f, 0.5f, 1.0f); // this results from the 2.0 resolution on the root layer
  transforms[1].PostScale(2.0f, 1.0f, 1.0f); // this is the 2.0 x-axis CSS transform on the child layer

  nsTArray<RefPtr<Layer> > layers;
  RefPtr<LayerManager> lm;
  RefPtr<Layer> root = CreateLayerTree(layerTreeSyntax, layerVisibleRegion, transforms, lm, layers);

  ScrollMetadata metadata;
  FrameMetrics& metrics = metadata.GetMetrics();
  metrics.SetCompositionBounds(ParentLayerRect(0, 0, 24, 24));
  metrics.SetDisplayPort(CSSRect(-1, -1, 6, 6));
  metrics.SetScrollOffset(CSSPoint(10, 10));
  metrics.SetScrollableRect(CSSRect(0, 0, 50, 50));
  metrics.SetCumulativeResolution(LayoutDeviceToLayerScale2D(2, 2));
  metrics.SetPresShellResolution(2.0f);
  metrics.SetZoom(CSSToParentLayerScale2D(6, 6));
  metrics.SetDevPixelsPerCSSPixel(CSSToLayoutDeviceScale(3));
  metrics.SetScrollId(FrameMetrics::START_SCROLL_ID);

  ScrollMetadata childMetadata = metadata;
  FrameMetrics& childMetrics = childMetadata.GetMetrics();
  childMetrics.SetScrollId(FrameMetrics::START_SCROLL_ID + 1);

  layers[0]->SetScrollMetadata(metadata);
  layers[1]->SetScrollMetadata(childMetadata);

  ParentLayerPoint pointOut;
  AsyncTransform viewTransformOut;

  // Both the parent and child layer should behave exactly the same here, because
  // the CSS transform on the child layer does not affect the SampleContentTransformForFrame code

  // initial transform
  apzc->SetFrameMetrics(metrics);
  apzc->NotifyLayersUpdated(metadata, true, true);
  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
  EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);

  childApzc->SetFrameMetrics(childMetrics);
  childApzc->NotifyLayersUpdated(childMetadata, true, true);
  childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint()), viewTransformOut);
  EXPECT_EQ(ParentLayerPoint(60, 60), pointOut);

  // do an async scroll by 5 pixels and check the transform
  metrics.ScrollBy(CSSPoint(5, 0));
  apzc->SetFrameMetrics(metrics);
  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut);
  EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);

  childMetrics.ScrollBy(CSSPoint(5, 0));
  childApzc->SetFrameMetrics(childMetrics);
  childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1), ParentLayerPoint(-30, 0)), viewTransformOut);
  EXPECT_EQ(ParentLayerPoint(90, 60), pointOut);

  // do an async zoom of 1.5x and check the transform
  metrics.ZoomBy(1.5f);
  apzc->SetFrameMetrics(metrics);
  apzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
  EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);

  childMetrics.ZoomBy(1.5f);
  childApzc->SetFrameMetrics(childMetrics);
  childApzc->SampleContentTransformForFrame(&viewTransformOut, pointOut);
  EXPECT_EQ(AsyncTransform(LayerToParentLayerScale(1.5), ParentLayerPoint(-45, 0)), viewTransformOut);
  EXPECT_EQ(ParentLayerPoint(135, 90), pointOut);

  childApzc->Destroy();
}
Example #6
0
bool
TiledLayerBufferComposite::UseTiles(const SurfaceDescriptorTiles& aTiles,
                                    Compositor* aCompositor,
                                    ISurfaceAllocator* aAllocator)
{
  if (mResolution != aTiles.resolution() ||
      aTiles.tileSize() != mTileSize) {
    Clear();
  }
  MOZ_ASSERT(aAllocator);
  MOZ_ASSERT(aCompositor);
  if (!aAllocator || !aCompositor) {
    return false;
  }

  if (aTiles.resolution() == 0 || IsNaN(aTiles.resolution())) {
    // There are divisions by mResolution so this protects the compositor process
    // against malicious content processes and fuzzing.
    return false;
  }

  TilesPlacement newTiles(aTiles.firstTileX(), aTiles.firstTileY(),
                          aTiles.retainedWidth(), aTiles.retainedHeight());

  const InfallibleTArray<TileDescriptor>& tileDescriptors = aTiles.tiles();

  // Step 1, unlock all the old tiles that haven't been unlocked yet. Any tiles that
  // exist in both the old and new sets will have been locked again by content, so this
  // doesn't result in the surface being writeable again.
  MarkTilesForUnlock();

  TextureSourceRecycler oldRetainedTiles(Move(mRetainedTiles));
  mRetainedTiles.SetLength(tileDescriptors.Length());

  // Step 2, deserialize the incoming set of tiles into mRetainedTiles, and attempt
  // to recycle the TextureSource for any repeated tiles.
  //
  // Since we don't have any retained 'tile' object, we have to search for instances
  // of the same TextureHost in the old tile set. The cost of binding a TextureHost
  // to a TextureSource for gralloc (binding EGLImage to GL texture) can be really
  // high, so we avoid this whenever possible.
  for (size_t i = 0; i < tileDescriptors.Length(); i++) {
    const TileDescriptor& tileDesc = tileDescriptors[i];

    TileHost& tile = mRetainedTiles[i];

    if (tileDesc.type() != TileDescriptor::TTexturedTileDescriptor) {
      NS_WARN_IF_FALSE(tileDesc.type() == TileDescriptor::TPlaceholderTileDescriptor,
                       "Unrecognised tile descriptor type");
      continue;
    }

    const TexturedTileDescriptor& texturedDesc = tileDesc.get_TexturedTileDescriptor();

    const TileLock& ipcLock = texturedDesc.sharedLock();
    if (!GetCopyOnWriteLock(ipcLock, tile, aAllocator)) {
      return false;
    }

    tile.mTextureHost = TextureHost::AsTextureHost(texturedDesc.textureParent());
    tile.mTextureHost->SetCompositor(aCompositor);

    if (texturedDesc.textureOnWhite().type() == MaybeTexture::TPTextureParent) {
      tile.mTextureHostOnWhite =
        TextureHost::AsTextureHost(texturedDesc.textureOnWhite().get_PTextureParent());
    }

    tile.mTilePosition = newTiles.TilePosition(i);

    // If this same tile texture existed in the old tile set then this will move the texture
    // source into our new tile.
    oldRetainedTiles.RecycleTextureSourceForTile(tile);
  }

  // Step 3, attempt to recycle unused texture sources from the old tile set into new tiles.
  //
  // For gralloc, binding a new TextureHost to the existing TextureSource is the fastest way
  // to ensure that any implicit locking on the old gralloc image is released.
  for (TileHost& tile : mRetainedTiles) {
    if (!tile.mTextureHost || tile.mTextureSource) {
      continue;
    }
    oldRetainedTiles.RecycleTextureSource(tile);
  }

  // Step 4, handle the texture uploads, texture source binding and release the
  // copy-on-write locks for textures with an internal buffer.
  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
    TileHost& tile = mRetainedTiles[i];
    if (!tile.mTextureHost) {
      continue;
    }

    const TileDescriptor& tileDesc = tileDescriptors[i];
    const TexturedTileDescriptor& texturedDesc = tileDesc.get_TexturedTileDescriptor();

    UseTileTexture(tile.mTextureHost,
                   tile.mTextureSource,
                   texturedDesc.updateRect(),
                   aCompositor);

    if (tile.mTextureHostOnWhite) {
      UseTileTexture(tile.mTextureHostOnWhite,
                     tile.mTextureSourceOnWhite,
                     texturedDesc.updateRect(),
                     aCompositor);
    }

    if (tile.mTextureHost->HasInternalBuffer()) {
      // Now that we did the texture upload (in UseTileTexture), we can release
      // the lock.
      tile.ReadUnlock();
    }
  }

  mTiles = newTiles;
  mTileSize = aTiles.tileSize();
  mTileOrigin = aTiles.tileOrigin();
  mValidRegion = aTiles.validRegion();
  mResolution = aTiles.resolution();
  mFrameResolution = CSSToParentLayerScale2D(aTiles.frameXResolution(),
                                             aTiles.frameYResolution());

  return true;
}
Example #7
0
bool
TiledLayerBufferComposite::UseTiles(const SurfaceDescriptorTiles& aTiles,
                                    Compositor* aCompositor,
                                    ISurfaceAllocator* aAllocator)
{
  if (mResolution != aTiles.resolution()) {
    Clear();
  }
  MOZ_ASSERT(aAllocator);
  MOZ_ASSERT(aCompositor);
  if (!aAllocator || !aCompositor) {
    return false;
  }

  if (aTiles.resolution() == 0 || IsNaN(aTiles.resolution())) {
    // There are divisions by mResolution so this protects the compositor process
    // against malicious content processes and fuzzing.
    return false;
  }

  TilesPlacement oldTiles = mTiles;
  TilesPlacement newTiles(aTiles.firstTileX(), aTiles.firstTileY(),
                          aTiles.retainedWidth(), aTiles.retainedHeight());

  const InfallibleTArray<TileDescriptor>& tileDescriptors = aTiles.tiles();

  nsTArray<TileHost> oldRetainedTiles;
  mRetainedTiles.SwapElements(oldRetainedTiles);
  mRetainedTiles.SetLength(tileDescriptors.Length());

  // Step 1, we need to unlock tiles that don't have an internal buffer after the
  // next frame where they are replaced.
  // Since we are about to replace the tiles' textures, we need to keep their locks
  // somewhere (in mPreviousSharedLock) until we composite the layer.
  for (size_t i = 0; i < oldRetainedTiles.Length(); ++i) {
    TileHost& tile = oldRetainedTiles[i];
    // It can happen that we still have a previous lock at this point,
    // if we changed a tile's front buffer (causing mSharedLock to
    // go into mPreviousSharedLock, and then did not composite that tile until
    // the next transaction, either because the tile is offscreen or because the
    // two transactions happened with no composition in between (over-production).
    tile.ReadUnlockPrevious();

    if (tile.mTextureHost && !tile.mTextureHost->HasInternalBuffer()) {
      MOZ_ASSERT(tile.mSharedLock);
      const TileIntPoint tilePosition = oldTiles.TilePosition(i);
      if (newTiles.HasTile(tilePosition)) {
        // This tile still exist in the new buffer
        tile.mPreviousSharedLock = tile.mSharedLock;
        tile.mSharedLock = nullptr;
      } else {
        // This tile does not exist anymore in the new buffer because the size
        // changed.
        tile.ReadUnlock();
      }
    }

    // By now we should not have anything in mSharedLock.
    MOZ_ASSERT(!tile.mSharedLock);
  }

  // Step 2, move the tiles in mRetainedTiles at places that correspond to where
  // they should be with the new retained with and height rather than the
  // old one.
  for (size_t i = 0; i < tileDescriptors.Length(); i++) {
    const TileIntPoint tilePosition = newTiles.TilePosition(i);
    // First, get the already existing tiles to the right place in the array,
    // and use placeholders where there was no tiles.
    if (!oldTiles.HasTile(tilePosition)) {
      mRetainedTiles[i] = GetPlaceholderTile();
    } else {
      mRetainedTiles[i] = oldRetainedTiles[oldTiles.TileIndex(tilePosition)];
      // If we hit this assertion it means we probably mixed something up in the
      // logic that tries to reuse tiles on the compositor side. It is most likely
      // benign, but we are missing some fast paths so let's try to make it not happen.
      MOZ_ASSERT(tilePosition.x == mRetainedTiles[i].x &&
                 tilePosition.y == mRetainedTiles[i].y);
    }
  }

  // It is important to remove the duplicated reference to tiles before calling
  // TextureHost::PrepareTextureSource, etc. because depending on the textures
  // ref counts we may or may not get some of the fast paths.
  oldRetainedTiles.Clear();

  // Step 3, handle the texture updates and release the copy-on-write locks.
  for (size_t i = 0; i < mRetainedTiles.Length(); i++) {
    const TileDescriptor& tileDesc = tileDescriptors[i];

    TileHost& tile = mRetainedTiles[i];

    switch (tileDesc.type()) {
      case TileDescriptor::TTexturedTileDescriptor: {
        const TexturedTileDescriptor& texturedDesc = tileDesc.get_TexturedTileDescriptor();

        const TileLock& ipcLock = texturedDesc.sharedLock();
        if (!GetCopyOnWriteLock(ipcLock, tile, aAllocator)) {
          return false;
        }

        RefPtr<TextureHost> textureHost = TextureHost::AsTextureHost(
          texturedDesc.textureParent()
        );

        RefPtr<TextureHost> textureOnWhite = nullptr;
        if (texturedDesc.textureOnWhite().type() == MaybeTexture::TPTextureParent) {
          textureOnWhite = TextureHost::AsTextureHost(
            texturedDesc.textureOnWhite().get_PTextureParent()
          );
        }

        UseTileTexture(tile.mTextureHost,
                       tile.mTextureSource,
                       texturedDesc.updateRect(),
                       textureHost,
                       aCompositor);

        if (textureOnWhite) {
          UseTileTexture(tile.mTextureHostOnWhite,
                         tile.mTextureSourceOnWhite,
                         texturedDesc.updateRect(),
                         textureOnWhite,
                         aCompositor);
        } else {
          // We could still have component alpha textures from a previous frame.
          tile.mTextureSourceOnWhite = nullptr;
          tile.mTextureHostOnWhite = nullptr;
        }

        if (textureHost->HasInternalBuffer()) {
          // Now that we did the texture upload (in UseTileTexture), we can release
          // the lock.
          tile.ReadUnlock();
        }

        break;
      }
      default:
        NS_WARNING("Unrecognised tile descriptor type");
      case TileDescriptor::TPlaceholderTileDescriptor: {

        if (tile.mTextureHost) {
          tile.mTextureHost->UnbindTextureSource();
          tile.mTextureSource = nullptr;
        }
        if (tile.mTextureHostOnWhite) {
          tile.mTextureHostOnWhite->UnbindTextureSource();
          tile.mTextureSourceOnWhite = nullptr;
        }
        // we may have a previous lock, and are about to loose our reference to it.
        // It is okay to unlock it because we just destroyed the texture source.
        tile.ReadUnlockPrevious();
        tile = GetPlaceholderTile();

        break;
      }
    }
    TileIntPoint tilePosition = newTiles.TilePosition(i);
    tile.x = tilePosition.x;
    tile.y = tilePosition.y;
  }

  mTiles = newTiles;
  mValidRegion = aTiles.validRegion();
  mResolution = aTiles.resolution();
  mFrameResolution = CSSToParentLayerScale2D(aTiles.frameXResolution(),
                                             aTiles.frameYResolution());

  return true;
}