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