/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "LayerManagerComposite.h" #include // for size_t #include // for uint16_t, uint32_t #include "CanvasLayerComposite.h" // for CanvasLayerComposite #include "ColorLayerComposite.h" // for ColorLayerComposite #include "CompositableHost.h" // for CompositableHost #include "ContainerLayerComposite.h" // for ContainerLayerComposite, etc #include "Diagnostics.h" #include "FPSCounter.h" // for FPSState, FPSCounter #include "FrameMetrics.h" // for FrameMetrics #include "GeckoProfiler.h" // for profiler_* #include "ImageLayerComposite.h" // for ImageLayerComposite #include "Layers.h" // for Layer, ContainerLayer, etc #include "LayerScope.h" // for LayerScope Tool #include "protobuf/LayerScopePacket.pb.h" // for protobuf (LayerScope) #include "PaintedLayerComposite.h" // for PaintedLayerComposite #include "TiledContentHost.h" #include "Units.h" // for ScreenIntRect #include "UnitTransforms.h" // for ViewAs #include "apz/src/AsyncPanZoomController.h" // for AsyncPanZoomController #include "gfxPrefs.h" // for gfxPrefs #ifdef XP_MACOSX #include "gfxPlatformMac.h" #endif #include "gfxRect.h" // for gfxRect #include "gfxUtils.h" // for frame color util #include "mozilla/Assertions.h" // for MOZ_ASSERT, etc #include "mozilla/RefPtr.h" // for RefPtr, already_AddRefed #include "mozilla/gfx/2D.h" // for DrawTarget #include "mozilla/gfx/Matrix.h" // for Matrix4x4 #include "mozilla/gfx/Point.h" // for IntSize, Point #include "mozilla/gfx/Rect.h" // for Rect #include "mozilla/gfx/Types.h" // for Color, SurfaceFormat #include "mozilla/layers/Compositor.h" // for Compositor #include "mozilla/layers/CompositorTypes.h" #include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc #include "mozilla/layers/LayerMetricsWrapper.h" // for LayerMetricsWrapper #include "mozilla/layers/LayersTypes.h" // for etc #include "mozilla/widget/CompositorWidget.h" // for WidgetRenderingContext #include "ipc/CompositorBench.h" // for CompositorBench #include "ipc/ShadowLayerUtils.h" #include "mozilla/mozalloc.h" // for operator new, etc #include "nsAppRunner.h" #include "mozilla/RefPtr.h" // for nsRefPtr #include "nsCOMPtr.h" // for already_AddRefed #include "nsDebug.h" // for NS_WARNING, NS_RUNTIMEABORT, etc #include "nsISupportsImpl.h" // for Layer::AddRef, etc #include "nsPoint.h" // for nsIntPoint #include "nsRect.h" // for mozilla::gfx::IntRect #include "nsRegion.h" // for nsIntRegion, etc #if defined(MOZ_WIDGET_ANDROID) #include #include #include "mozilla/widget/AndroidCompositorWidget.h" #include "opengl/CompositorOGL.h" #include "GLConsts.h" #include "GLContextEGL.h" #include "GLContextProvider.h" #include "mozilla/Unused.h" #include "mozilla/widget/AndroidCompositorWidget.h" #include "ScopedGLHelpers.h" #endif #include "GeckoProfiler.h" #include "TextRenderer.h" // for TextRenderer #include "mozilla/layers/CompositorBridgeParent.h" #include "TreeTraversal.h" // for ForEachNode #ifdef USE_SKIA #include "PaintCounter.h" // For PaintCounter #endif class gfxContext; namespace mozilla { namespace layers { class ImageLayer; using namespace mozilla::gfx; using namespace mozilla::gl; static LayerComposite* ToLayerComposite(Layer* aLayer) { return static_cast(aLayer->ImplData()); } static void ClearSubtree(Layer* aLayer) { ForEachNode( aLayer, [] (Layer* layer) { ToLayerComposite(layer)->CleanupResources(); }); } void LayerManagerComposite::ClearCachedResources(Layer* aSubtree) { MOZ_ASSERT(!aSubtree || aSubtree->Manager() == this); Layer* subtree = aSubtree ? aSubtree : mRoot.get(); if (!subtree) { return; } ClearSubtree(subtree); // FIXME [bjacob] // XXX the old LayerManagerOGL code had a mMaybeInvalidTree that it set to true here. // Do we need that? } HostLayerManager::HostLayerManager() : mDebugOverlayWantsNextFrame(false) , mWarningLevel(0.0f) , mCompositorBridgeID(0) , mWindowOverlayChanged(false) , mLastPaintTime(TimeDuration::Forever()) , mRenderStartTime(TimeStamp::Now()) {} HostLayerManager::~HostLayerManager() {} void HostLayerManager::RecordPaintTimes(const PaintTiming& aTiming) { mDiagnostics->RecordPaintTimes(aTiming); } void HostLayerManager::RecordUpdateTime(float aValue) { mDiagnostics->RecordUpdateTime(aValue); } /** * LayerManagerComposite */ LayerManagerComposite::LayerManagerComposite(Compositor* aCompositor) : mUnusedApzTransformWarning(false) , mDisabledApzWarning(false) , mCompositor(aCompositor) , mInTransaction(false) , mIsCompositorReady(false) #if defined(MOZ_WIDGET_ANDROID) , mScreenPixelsTarget(nullptr) #endif // defined(MOZ_WIDGET_ANDROID) { mTextRenderer = new TextRenderer(); mDiagnostics = MakeUnique(); MOZ_ASSERT(aCompositor); #ifdef USE_SKIA mPaintCounter = nullptr; #endif } LayerManagerComposite::~LayerManagerComposite() { Destroy(); } void LayerManagerComposite::Destroy() { if (!mDestroyed) { mCompositor->GetWidget()->CleanupWindowEffects(); if (mRoot) { RootLayer()->Destroy(); } mCompositor->CancelFrame(); mRoot = nullptr; mClonedLayerTreeProperties = nullptr; mDestroyed = true; #ifdef USE_SKIA mPaintCounter = nullptr; #endif } } void LayerManagerComposite::UpdateRenderBounds(const IntRect& aRect) { mRenderBounds = aRect; } bool LayerManagerComposite::AreComponentAlphaLayersEnabled() { return mCompositor->GetBackendType() != LayersBackend::LAYERS_BASIC && mCompositor->SupportsEffect(EffectTypes::COMPONENT_ALPHA) && LayerManager::AreComponentAlphaLayersEnabled(); } bool LayerManagerComposite::BeginTransaction() { mInTransaction = true; if (!mCompositor->Ready()) { return false; } mIsCompositorReady = true; return true; } void LayerManagerComposite::BeginTransactionWithDrawTarget(DrawTarget* aTarget, const IntRect& aRect) { mInTransaction = true; if (!mCompositor->Ready()) { return; } #ifdef MOZ_LAYERS_HAVE_LOG MOZ_LAYERS_LOG(("[----- BeginTransaction")); Log(); #endif if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return; } mIsCompositorReady = true; mCompositor->SetTargetContext(aTarget, aRect); mTarget = aTarget; mTargetBounds = aRect; } void LayerManagerComposite::PostProcessLayers(nsIntRegion& aOpaqueRegion) { LayerIntRegion visible; LayerComposite* rootComposite = static_cast(mRoot->AsHostLayer()); PostProcessLayers(mRoot, aOpaqueRegion, visible, ViewAs(rootComposite->GetShadowClipRect(), PixelCastJustification::RenderTargetIsParentLayerForRoot), Nothing()); } // We want to skip directly through ContainerLayers that don't have an intermediate // surface. We compute occlusions for leaves and intermediate surfaces against // the layer that they actually composite into so that we can use the final (snapped) // effective transform. bool ShouldProcessLayer(Layer* aLayer) { if (!aLayer->AsContainerLayer()) { return true; } return aLayer->AsContainerLayer()->UseIntermediateSurface(); } /** * Get accumulated transform of from the context creating layer to the * given layer. */ static Matrix4x4 GetAccTransformIn3DContext(Layer* aLayer) { Matrix4x4 transform = aLayer->GetLocalTransform(); for (Layer* layer = aLayer->GetParent(); layer && layer->Extend3DContext(); layer = layer->GetParent()) { transform = transform * layer->GetLocalTransform(); } return transform; } void LayerManagerComposite::PostProcessLayers(Layer* aLayer, nsIntRegion& aOpaqueRegion, LayerIntRegion& aVisibleRegion, const Maybe& aRenderTargetClip, const Maybe& aClipFromAncestors) { // Compute a clip that's the combination of our layer clip with the clip // from our ancestors. LayerComposite* composite = static_cast(aLayer->AsHostLayer()); Maybe layerClip = composite->GetShadowClipRect(); MOZ_ASSERT(!layerClip || !aLayer->Combines3DTransformWithAncestors(), "The layer with a clip should not participate " "a 3D rendering context"); Maybe outsideClip = IntersectMaybeRects(layerClip, aClipFromAncestors); Maybe insideClip; if (aLayer->Extend3DContext()) { // If we're preserve-3d just pass the clip rect down directly, and we'll do the // conversion at the preserve-3d leaf Layer. if (outsideClip) { insideClip = Some(ViewAs(*outsideClip, PixelCastJustification::MovingDownToChildren)); } } else if (outsideClip) { // Convert the combined clip into our pre-transform coordinate space, so // that it can later be intersected with our visible region. // If our transform is a perspective, there's no meaningful insideClip rect // we can compute (it would need to be a cone). Matrix4x4 localTransform = GetAccTransformIn3DContext(aLayer); if (!localTransform.HasPerspectiveComponent() && localTransform.Invert()) { LayerRect insideClipFloat = UntransformBy(ViewAs(localTransform), ParentLayerRect(*outsideClip), LayerRect::MaxIntRect()).valueOr(LayerRect()); insideClipFloat.RoundOut(); LayerIntRect insideClipInt; if (insideClipFloat.ToIntRect(&insideClipInt)) { insideClip = Some(insideClipInt); } } } Maybe ancestorClipForChildren; if (insideClip) { ancestorClipForChildren = Some(ViewAs(*insideClip, PixelCastJustification::MovingDownToChildren)); } if (!ShouldProcessLayer(aLayer)) { MOZ_ASSERT(aLayer->AsContainerLayer() && !aLayer->AsContainerLayer()->UseIntermediateSurface()); // For layers participating 3D rendering context, their visible // region should be empty (invisible), so we pass through them // without doing anything. for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) { LayerComposite* childComposite = static_cast(child->AsHostLayer()); Maybe renderTargetClip = aRenderTargetClip; if (childComposite->GetShadowClipRect()) { RenderTargetIntRect clip = TransformBy(ViewAs( aLayer->GetEffectiveTransform(), PixelCastJustification::RenderTargetIsParentLayerForRoot), *childComposite->GetShadowClipRect()); renderTargetClip = IntersectMaybeRects(renderTargetClip, Some(clip)); } PostProcessLayers(child, aOpaqueRegion, aVisibleRegion, renderTargetClip, ancestorClipForChildren); } return; } nsIntRegion localOpaque; // Treat layers on the path to the root of the 3D rendering context as // a giant layer if it is a leaf. Matrix4x4 transform = aLayer->GetEffectiveTransform(); Matrix transform2d; Maybe integerTranslation; // If aLayer has a simple transform (only an integer translation) then we // can easily convert aOpaqueRegion into pre-transform coordinates and include // that region. if (transform.Is2D(&transform2d)) { if (transform2d.IsIntegerTranslation()) { integerTranslation = Some(IntPoint::Truncate(transform2d.GetTranslation())); localOpaque = aOpaqueRegion; localOpaque.MoveBy(-*integerTranslation); } } // Save the value of localOpaque, which currently stores the region obscured // by siblings (and uncles and such), before our descendants contribute to it. nsIntRegion obscured = localOpaque; // Recurse on our descendants, in front-to-back order. In this process: // - Occlusions are computed for them, and they contribute to localOpaque. // - They recalculate their visible regions, taking ancestorClipForChildren // into account, and accumulate them into descendantsVisibleRegion. LayerIntRegion descendantsVisibleRegion; bool hasPreserve3DChild = false; for (Layer* child = aLayer->GetLastChild(); child; child = child->GetPrevSibling()) { MOZ_ASSERT(aLayer->AsContainerLayer()->UseIntermediateSurface()); LayerComposite* childComposite = static_cast(child->AsHostLayer()); PostProcessLayers(child, localOpaque, descendantsVisibleRegion, ViewAs(childComposite->GetShadowClipRect(), PixelCastJustification::RenderTargetIsParentLayerForRoot), ancestorClipForChildren); if (child->Extend3DContext()) { hasPreserve3DChild = true; } } // Recalculate our visible region. LayerIntRegion visible = composite->GetShadowVisibleRegion(); // If we have descendants, throw away the visible region stored on this // layer, and use the region accumulated by our descendants instead. if (aLayer->GetFirstChild() && !hasPreserve3DChild) { visible = descendantsVisibleRegion; } // Subtract any areas that we know to be opaque. if (!obscured.IsEmpty()) { visible.SubOut(LayerIntRegion::FromUnknownRegion(obscured)); } // Clip the visible region using the combined clip. if (insideClip) { visible.AndWith(*insideClip); } composite->SetShadowVisibleRegion(visible); // Transform the newly calculated visible region into our parent's space, // apply our clip to it (if any), and accumulate it into |aVisibleRegion| // for the caller to use. ParentLayerIntRegion visibleParentSpace = TransformBy( ViewAs(transform), visible); aVisibleRegion.OrWith(ViewAs(visibleParentSpace, PixelCastJustification::MovingDownToChildren)); // If we have a simple transform, then we can add our opaque area into // aOpaqueRegion. if (integerTranslation && !aLayer->HasMaskLayers() && aLayer->IsOpaqueForVisibility()) { if (aLayer->IsOpaque()) { localOpaque.OrWith(composite->GetFullyRenderedRegion()); } localOpaque.MoveBy(*integerTranslation); if (aRenderTargetClip) { localOpaque.AndWith(aRenderTargetClip->ToUnknownRect()); } aOpaqueRegion.OrWith(localOpaque); } } void LayerManagerComposite::EndTransaction(const TimeStamp& aTimeStamp, EndTransactionFlags aFlags) { NS_ASSERTION(mInTransaction, "Didn't call BeginTransaction?"); NS_ASSERTION(!(aFlags & END_NO_COMPOSITE), "Shouldn't get END_NO_COMPOSITE here"); mInTransaction = false; mRenderStartTime = TimeStamp::Now(); if (!mIsCompositorReady) { return; } mIsCompositorReady = false; #ifdef MOZ_LAYERS_HAVE_LOG MOZ_LAYERS_LOG((" ----- (beginning paint)")); Log(); #endif if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return; } // Set composition timestamp here because we need it in // ComputeEffectiveTransforms (so the correct video frame size is picked) and // also to compute invalid regions properly. SetCompositionTime(aTimeStamp); if (mRoot && !(aFlags & END_NO_IMMEDIATE_REDRAW)) { MOZ_ASSERT(!aTimeStamp.IsNull()); UpdateAndRender(); mCompositor->FlushPendingNotifyNotUsed(); } mCompositor->ClearTargetContext(); mTarget = nullptr; #ifdef MOZ_LAYERS_HAVE_LOG Log(); MOZ_LAYERS_LOG(("]----- EndTransaction")); #endif } void LayerManagerComposite::UpdateAndRender() { nsIntRegion invalid; // The results of our drawing always go directly into a pixel buffer, // so we don't need to pass any global transform here. mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4()); nsIntRegion opaque; PostProcessLayers(opaque); if (mClonedLayerTreeProperties) { // We need to compute layer tree differences even if we're not going to // immediately use the resulting damage area, since ComputeDifferences // is also responsible for invalidates intermediate surfaces in // ContainerLayers. nsIntRegion changed = mClonedLayerTreeProperties->ComputeDifferences(mRoot, nullptr); if (mTarget) { // Since we're composing to an external target, we're not going to use // the damage region from layers changes - we want to composite // everything in the target bounds. Instead we accumulate the layers // damage region for the next window composite. mInvalidRegion.Or(mInvalidRegion, changed); } else { invalid = Move(changed); } } if (mTarget) { invalid.Or(invalid, mTargetBounds); } else { // If we didn't have a previous layer tree, invalidate the entire render // area. if (!mClonedLayerTreeProperties) { invalid.Or(invalid, mRenderBounds); } // Add any additional invalid rects from the window manager or previous // damage computed during ComposeToTarget(). invalid.Or(invalid, mInvalidRegion); mInvalidRegion.SetEmpty(); } if (invalid.IsEmpty() && !mWindowOverlayChanged) { // Composition requested, but nothing has changed. Don't do any work. mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); return; } // We don't want our debug overlay to cause more frames to happen // so we will invalidate after we've decided if something changed. InvalidateDebugOverlay(invalid, mRenderBounds); Render(invalid, opaque); #if defined(MOZ_WIDGET_ANDROID) RenderToPresentationSurface(); #endif mWindowOverlayChanged = false; // Update cached layer tree information. mClonedLayerTreeProperties = LayerProperties::CloneFrom(GetRoot()); } already_AddRefed LayerManagerComposite::CreateOptimalMaskDrawTarget(const IntSize &aSize) { MOZ_CRASH("Should only be called on the drawing side"); return nullptr; } LayerComposite* LayerManagerComposite::RootLayer() const { if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return ToLayerComposite(mRoot); } void LayerManagerComposite::InvalidateDebugOverlay(nsIntRegion& aInvalidRegion, const IntRect& aBounds) { bool drawFps = gfxPrefs::LayersDrawFPS(); bool drawFrameColorBars = gfxPrefs::CompositorDrawColorBars(); if (drawFps) { aInvalidRegion.Or(aInvalidRegion, nsIntRect(0, 0, 650, 400)); } if (drawFrameColorBars) { aInvalidRegion.Or(aInvalidRegion, nsIntRect(0, 0, 10, aBounds.height)); } #ifdef USE_SKIA bool drawPaintTimes = gfxPrefs::AlwaysPaint(); if (drawPaintTimes) { aInvalidRegion.Or(aInvalidRegion, nsIntRect(PaintCounter::GetPaintRect())); } #endif } #ifdef USE_SKIA void LayerManagerComposite::DrawPaintTimes(Compositor* aCompositor) { if (!mPaintCounter) { mPaintCounter = new PaintCounter(); } TimeDuration compositeTime = TimeStamp::Now() - mRenderStartTime; mPaintCounter->Draw(aCompositor, mLastPaintTime, compositeTime); } #endif static uint16_t sFrameCount = 0; void LayerManagerComposite::RenderDebugOverlay(const IntRect& aBounds) { bool drawFps = gfxPrefs::LayersDrawFPS(); bool drawFrameColorBars = gfxPrefs::CompositorDrawColorBars(); if (drawFps) { float alpha = 1; #ifdef ANDROID // Draw a translation delay warning overlay int width; int border; TimeStamp now = TimeStamp::Now(); if (!mWarnTime.IsNull() && (now - mWarnTime).ToMilliseconds() < kVisualWarningDuration) { EffectChain effects; // Black blorder border = 4; width = 6; effects.mPrimaryEffect = new EffectSolidColor(gfx::Color(0, 0, 0, 1)); mCompositor->DrawQuad(gfx::Rect(border, border, aBounds.width - 2 * border, width), aBounds, effects, alpha, gfx::Matrix4x4()); mCompositor->DrawQuad(gfx::Rect(border, aBounds.height - border - width, aBounds.width - 2 * border, width), aBounds, effects, alpha, gfx::Matrix4x4()); mCompositor->DrawQuad(gfx::Rect(border, border + width, width, aBounds.height - 2 * border - width * 2), aBounds, effects, alpha, gfx::Matrix4x4()); mCompositor->DrawQuad(gfx::Rect(aBounds.width - border - width, border + width, width, aBounds.height - 2 * border - 2 * width), aBounds, effects, alpha, gfx::Matrix4x4()); // Content border = 5; width = 4; effects.mPrimaryEffect = new EffectSolidColor(gfx::Color(1, 1.f - mWarningLevel, 0, 1)); mCompositor->DrawQuad(gfx::Rect(border, border, aBounds.width - 2 * border, width), aBounds, effects, alpha, gfx::Matrix4x4()); mCompositor->DrawQuad(gfx::Rect(border, aBounds.height - border - width, aBounds.width - 2 * border, width), aBounds, effects, alpha, gfx::Matrix4x4()); mCompositor->DrawQuad(gfx::Rect(border, border + width, width, aBounds.height - 2 * border - width * 2), aBounds, effects, alpha, gfx::Matrix4x4()); mCompositor->DrawQuad(gfx::Rect(aBounds.width - border - width, border + width, width, aBounds.height - 2 * border - 2 * width), aBounds, effects, alpha, gfx::Matrix4x4()); SetDebugOverlayWantsNextFrame(true); } #endif GPUStats stats; stats.mScreenPixels = mRenderBounds.width * mRenderBounds.height; mCompositor->GetFrameStats(&stats); std::string text = mDiagnostics->GetFrameOverlayString(stats); mTextRenderer->RenderText( mCompositor, text, IntPoint(2, 5), Matrix4x4(), 24, 600, TextRenderer::FontType::FixedWidth); if (mUnusedApzTransformWarning) { // If we have an unused APZ transform on this composite, draw a 20x20 red box // in the top-right corner EffectChain effects; effects.mPrimaryEffect = new EffectSolidColor(gfx::Color(1, 0, 0, 1)); mCompositor->DrawQuad(gfx::Rect(aBounds.width - 20, 0, 20, 20), aBounds, effects, alpha, gfx::Matrix4x4()); mUnusedApzTransformWarning = false; SetDebugOverlayWantsNextFrame(true); } if (mDisabledApzWarning) { // If we have a disabled APZ on this composite, draw a 20x20 yellow box // in the top-right corner, to the left of the unused-apz-transform // warning box EffectChain effects; effects.mPrimaryEffect = new EffectSolidColor(gfx::Color(1, 1, 0, 1)); mCompositor->DrawQuad(gfx::Rect(aBounds.width - 40, 0, 20, 20), aBounds, effects, alpha, gfx::Matrix4x4()); mDisabledApzWarning = false; SetDebugOverlayWantsNextFrame(true); } } if (drawFrameColorBars) { gfx::IntRect sideRect(0, 0, 10, aBounds.height); EffectChain effects; effects.mPrimaryEffect = new EffectSolidColor(gfxUtils::GetColorForFrameNumber(sFrameCount)); mCompositor->DrawQuad(Rect(sideRect), sideRect, effects, 1.0, gfx::Matrix4x4()); } if (drawFrameColorBars) { // We intentionally overflow at 2^16. sFrameCount++; } #ifdef USE_SKIA bool drawPaintTimes = gfxPrefs::AlwaysPaint(); if (drawPaintTimes) { DrawPaintTimes(mCompositor); } #endif } RefPtr LayerManagerComposite::PushGroupForLayerEffects() { // This is currently true, so just making sure that any new use of this // method is flagged for investigation MOZ_ASSERT(gfxPrefs::LayersEffectInvert() || gfxPrefs::LayersEffectGrayscale() || gfxPrefs::LayersEffectContrast() != 0.0); RefPtr previousTarget = mCompositor->GetCurrentRenderTarget(); // make our render target the same size as the destination target // so that we don't have to change size if the drawing area changes. IntRect rect(previousTarget->GetOrigin(), previousTarget->GetSize()); // XXX: I'm not sure if this is true or not... MOZ_ASSERT(rect.x == 0 && rect.y == 0); if (!mTwoPassTmpTarget || mTwoPassTmpTarget->GetSize() != previousTarget->GetSize() || mTwoPassTmpTarget->GetOrigin() != previousTarget->GetOrigin()) { mTwoPassTmpTarget = mCompositor->CreateRenderTarget(rect, INIT_MODE_NONE); } MOZ_ASSERT(mTwoPassTmpTarget); mCompositor->SetRenderTarget(mTwoPassTmpTarget); return previousTarget; } void LayerManagerComposite::PopGroupForLayerEffects(RefPtr aPreviousTarget, IntRect aClipRect, bool aGrayscaleEffect, bool aInvertEffect, float aContrastEffect) { MOZ_ASSERT(mTwoPassTmpTarget); // This is currently true, so just making sure that any new use of this // method is flagged for investigation MOZ_ASSERT(aInvertEffect || aGrayscaleEffect || aContrastEffect != 0.0); mCompositor->SetRenderTarget(aPreviousTarget); EffectChain effectChain(RootLayer()); Matrix5x4 effectMatrix; if (aGrayscaleEffect) { // R' = G' = B' = luminance // R' = 0.2126*R + 0.7152*G + 0.0722*B // G' = 0.2126*R + 0.7152*G + 0.0722*B // B' = 0.2126*R + 0.7152*G + 0.0722*B Matrix5x4 grayscaleMatrix(0.2126f, 0.2126f, 0.2126f, 0, 0.7152f, 0.7152f, 0.7152f, 0, 0.0722f, 0.0722f, 0.0722f, 0, 0, 0, 0, 1, 0, 0, 0, 0); effectMatrix = grayscaleMatrix; } if (aInvertEffect) { // R' = 1 - R // G' = 1 - G // B' = 1 - B Matrix5x4 colorInvertMatrix(-1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 1, 1, 1, 0); effectMatrix = effectMatrix * colorInvertMatrix; } if (aContrastEffect != 0.0) { // Multiplying with: // R' = (1 + c) * (R - 0.5) + 0.5 // G' = (1 + c) * (G - 0.5) + 0.5 // B' = (1 + c) * (B - 0.5) + 0.5 float cP1 = aContrastEffect + 1; float hc = 0.5*aContrastEffect; Matrix5x4 contrastMatrix( cP1, 0, 0, 0, 0, cP1, 0, 0, 0, 0, cP1, 0, 0, 0, 0, 1, -hc, -hc, -hc, 0); effectMatrix = effectMatrix * contrastMatrix; } effectChain.mPrimaryEffect = new EffectRenderTarget(mTwoPassTmpTarget); effectChain.mSecondaryEffects[EffectTypes::COLOR_MATRIX] = new EffectColorMatrix(effectMatrix); mCompositor->DrawQuad(Rect(Point(0, 0), Size(mTwoPassTmpTarget->GetSize())), aClipRect, effectChain, 1., Matrix4x4()); } // Used to clear the 'mLayerComposited' flag at the beginning of each Render(). static void ClearLayerFlags(Layer* aLayer) { ForEachNode( aLayer, [] (Layer* layer) { if (layer->AsHostLayer()) { static_cast(layer->AsHostLayer())->SetLayerComposited(false); } }); } #if defined(MOZ_WIDGET_ANDROID) class ScopedCompositorRenderOffset { public: ScopedCompositorRenderOffset(CompositorOGL* aCompositor, const ScreenPoint& aOffset) : mCompositor(aCompositor), mOriginalOffset(mCompositor->GetScreenRenderOffset()), mOriginalProjection(mCompositor->GetProjMatrix()) { ScreenPoint offset(mOriginalOffset.x + aOffset.x, mOriginalOffset.y + aOffset.y); mCompositor->SetScreenRenderOffset(offset); // Calling CompositorOGL::SetScreenRenderOffset does not affect the projection matrix // so adjust that as well. gfx::Matrix4x4 mat = mOriginalProjection; mat.PreTranslate(aOffset.x, aOffset.y, 0.0f); mCompositor->SetProjMatrix(mat); } ~ScopedCompositorRenderOffset() { mCompositor->SetScreenRenderOffset(mOriginalOffset); mCompositor->SetProjMatrix(mOriginalProjection); } private: CompositorOGL* const mCompositor; const ScreenPoint mOriginalOffset; const gfx::Matrix4x4 mOriginalProjection; }; #endif // defined(MOZ_WIDGET_ANDROID) void LayerManagerComposite::Render(const nsIntRegion& aInvalidRegion, const nsIntRegion& aOpaqueRegion) { AUTO_PROFILER_LABEL("LayerManagerComposite::Render", GRAPHICS); if (mDestroyed || !mCompositor || mCompositor->IsDestroyed()) { NS_WARNING("Call on destroyed layer manager"); return; } ClearLayerFlags(mRoot); // At this time, it doesn't really matter if these preferences change // during the execution of the function; we should be safe in all // permutations. However, may as well just get the values onces and // then use them, just in case the consistency becomes important in // the future. bool invertVal = gfxPrefs::LayersEffectInvert(); bool grayscaleVal = gfxPrefs::LayersEffectGrayscale(); float contrastVal = gfxPrefs::LayersEffectContrast(); bool haveLayerEffects = (invertVal || grayscaleVal || contrastVal != 0.0); // Set LayerScope begin/end frame LayerScopeAutoFrame frame(PR_Now()); // Dump to console if (gfxPrefs::LayersDump()) { this->Dump(/* aSorted= */true); } else if (profiler_feature_active(ProfilerFeature::LayersDump)) { std::stringstream ss; Dump(ss); profiler_tracing("log", ss.str().c_str()); } // Dump to LayerScope Viewer if (LayerScope::CheckSendable()) { // Create a LayersPacket, dump Layers into it and transfer the // packet('s ownership) to LayerScope. auto packet = MakeUnique(); layerscope::LayersPacket* layersPacket = packet->mutable_layers(); this->Dump(layersPacket); LayerScope::SendLayerDump(Move(packet)); } mozilla::widget::WidgetRenderingContext widgetContext; #if defined(XP_MACOSX) widgetContext.mLayerManager = this; #elif defined(MOZ_WIDGET_ANDROID) widgetContext.mCompositor = GetCompositor(); #endif { AUTO_PROFILER_LABEL("LayerManagerComposite::Render:Prerender", GRAPHICS); if (!mCompositor->GetWidget()->PreRender(&widgetContext)) { return; } } ParentLayerIntRect clipRect; IntRect bounds(mRenderBounds.x, mRenderBounds.y, mRenderBounds.width, mRenderBounds.height); IntRect actualBounds; CompositorBench(mCompositor, bounds); MOZ_ASSERT(mRoot->GetOpacity() == 1); #if defined(MOZ_WIDGET_ANDROID) LayerMetricsWrapper wrapper = GetRootContentLayer(); if (wrapper) { mCompositor->SetClearColor(wrapper.Metadata().GetBackgroundColor()); } else { mCompositor->SetClearColorToDefault(); } #endif if (mRoot->GetClipRect()) { clipRect = *mRoot->GetClipRect(); IntRect rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height); mCompositor->BeginFrame(aInvalidRegion, &rect, bounds, aOpaqueRegion, nullptr, &actualBounds); } else { gfx::IntRect rect; mCompositor->BeginFrame(aInvalidRegion, nullptr, bounds, aOpaqueRegion, &rect, &actualBounds); clipRect = ParentLayerIntRect(rect.x, rect.y, rect.width, rect.height); } #if defined(MOZ_WIDGET_ANDROID) ScreenCoord offset = GetContentShiftForToolbar(); ScopedCompositorRenderOffset scopedOffset(mCompositor->AsCompositorOGL(), ScreenPoint(0.0f, offset)); #endif if (actualBounds.IsEmpty()) { mCompositor->GetWidget()->PostRender(&widgetContext); return; } // Allow widget to render a custom background. mCompositor->GetWidget()->DrawWindowUnderlay( &widgetContext, LayoutDeviceIntRect::FromUnknownRect(actualBounds)); RefPtr previousTarget; if (haveLayerEffects) { previousTarget = PushGroupForLayerEffects(); } else { mTwoPassTmpTarget = nullptr; } // Render our layers. { Diagnostics::Record record(mRenderStartTime); RootLayer()->Prepare(ViewAs(clipRect, PixelCastJustification::RenderTargetIsParentLayerForRoot)); if (record.Recording()) { mDiagnostics->RecordPrepareTime(record.Duration()); } } // Execute draw commands. { Diagnostics::Record record; RootLayer()->RenderLayer(clipRect.ToUnknownRect(), Nothing()); if (record.Recording()) { mDiagnostics->RecordCompositeTime(record.Duration()); } } RootLayer()->Cleanup(); if (!mRegionToClear.IsEmpty()) { for (auto iter = mRegionToClear.RectIter(); !iter.Done(); iter.Next()) { const IntRect& r = iter.Get(); mCompositor->ClearRect(Rect(r.x, r.y, r.width, r.height)); } } if (mTwoPassTmpTarget) { MOZ_ASSERT(haveLayerEffects); PopGroupForLayerEffects(previousTarget, clipRect.ToUnknownRect(), grayscaleVal, invertVal, contrastVal); } // Allow widget to render a custom foreground. mCompositor->GetWidget()->DrawWindowOverlay( &widgetContext, LayoutDeviceIntRect::FromUnknownRect(actualBounds)); mCompositor->NormalDrawingDone(); #if defined(MOZ_WIDGET_ANDROID) // Depending on the content shift the toolbar may be rendered on top of // some of the content so it must be rendered after the content. RenderToolbar(); HandlePixelsTarget(); #endif // defined(MOZ_WIDGET_ANDROID) // Debugging RenderDebugOverlay(actualBounds); { AUTO_PROFILER_LABEL("LayerManagerComposite::Render:EndFrame", GRAPHICS); mCompositor->EndFrame(); // Call after EndFrame() mCompositor->SetDispAcquireFence(mRoot); } mCompositor->GetWidget()->PostRender(&widgetContext); RecordFrame(); } #if defined(MOZ_WIDGET_ANDROID) class ScopedCompositorProjMatrix { public: ScopedCompositorProjMatrix(CompositorOGL* aCompositor, const Matrix4x4& aProjMatrix): mCompositor(aCompositor), mOriginalProjMatrix(mCompositor->GetProjMatrix()) { mCompositor->SetProjMatrix(aProjMatrix); } ~ScopedCompositorProjMatrix() { mCompositor->SetProjMatrix(mOriginalProjMatrix); } private: CompositorOGL* const mCompositor; const Matrix4x4 mOriginalProjMatrix; }; class ScopedCompostitorSurfaceSize { public: ScopedCompostitorSurfaceSize(CompositorOGL* aCompositor, const gfx::IntSize& aSize) : mCompositor(aCompositor), mOriginalSize(mCompositor->GetDestinationSurfaceSize()) { mCompositor->SetDestinationSurfaceSize(aSize); } ~ScopedCompostitorSurfaceSize() { mCompositor->SetDestinationSurfaceSize(mOriginalSize); } private: CompositorOGL* const mCompositor; const gfx::IntSize mOriginalSize; }; class ScopedContextSurfaceOverride { public: ScopedContextSurfaceOverride(GLContextEGL* aContext, void* aSurface) : mContext(aContext) { MOZ_ASSERT(aSurface); mContext->SetEGLSurfaceOverride(aSurface); mContext->MakeCurrent(true); } ~ScopedContextSurfaceOverride() { mContext->SetEGLSurfaceOverride(EGL_NO_SURFACE); mContext->MakeCurrent(true); } private: GLContextEGL* const mContext; }; void LayerManagerComposite::RenderToPresentationSurface() { widget::CompositorWidget* const widget = mCompositor->GetWidget(); ANativeWindow* window = widget->AsAndroid()->GetPresentationANativeWindow(); if (!window) { return; } EGLSurface surface = widget->AsAndroid()->GetPresentationEGLSurface(); if (!surface) { //create surface; surface = GLContextProviderEGL::CreateEGLSurface(window); if (!surface) { return; } widget->AsAndroid()->SetPresentationEGLSurface(surface); } CompositorOGL* compositor = mCompositor->AsCompositorOGL(); GLContext* gl = compositor->gl(); GLContextEGL* egl = GLContextEGL::Cast(gl); if (!egl) { return; } const IntSize windowSize(ANativeWindow_getWidth(window), ANativeWindow_getHeight(window)); if ((windowSize.width <= 0) || (windowSize.height <= 0)) { return; } ScreenRotation rotation = compositor->GetScreenRotation(); const int actualWidth = windowSize.width; const int actualHeight = windowSize.height; const gfx::IntSize originalSize = compositor->GetDestinationSurfaceSize(); const nsIntRect originalRect = nsIntRect(0, 0, originalSize.width, originalSize.height); int pageWidth = originalSize.width; int pageHeight = originalSize.height; if (rotation == ROTATION_90 || rotation == ROTATION_270) { pageWidth = originalSize.height; pageHeight = originalSize.width; } float scale = 1.0; if ((pageWidth > actualWidth) || (pageHeight > actualHeight)) { const float scaleWidth = (float)actualWidth / (float)pageWidth; const float scaleHeight = (float)actualHeight / (float)pageHeight; scale = scaleWidth <= scaleHeight ? scaleWidth : scaleHeight; } const gfx::IntSize actualSize(actualWidth, actualHeight); ScopedCompostitorSurfaceSize overrideSurfaceSize(compositor, actualSize); const ScreenPoint offset((actualWidth - (int)(scale * pageWidth)) / 2, 0); ScopedContextSurfaceOverride overrideSurface(egl, surface); Matrix viewMatrix = ComputeTransformForRotation(originalRect, rotation); viewMatrix.Invert(); // unrotate viewMatrix.PostScale(scale, scale); viewMatrix.PostTranslate(offset.x, offset.y); Matrix4x4 matrix = Matrix4x4::From2D(viewMatrix); mRoot->ComputeEffectiveTransforms(matrix); nsIntRegion opaque; PostProcessLayers(opaque); nsIntRegion invalid; IntRect bounds = IntRect::Truncate(0, 0, scale * pageWidth, actualHeight); IntRect rect, actualBounds; MOZ_ASSERT(mRoot->GetOpacity() == 1); mCompositor->BeginFrame(invalid, nullptr, bounds, nsIntRegion(), &rect, &actualBounds); // The Java side of Fennec sets a scissor rect that accounts for // chrome such as the URL bar. Override that so that the entire frame buffer // is cleared. ScopedScissorRect scissorRect(egl, 0, 0, actualWidth, actualHeight); egl->fClearColor(0.0, 0.0, 0.0, 0.0); egl->fClear(LOCAL_GL_COLOR_BUFFER_BIT); const IntRect clipRect = IntRect::Truncate(0, 0, actualWidth, actualHeight); RootLayer()->Prepare(RenderTargetIntRect::FromUnknownRect(clipRect)); RootLayer()->RenderLayer(clipRect, Nothing()); mCompositor->EndFrame(); } ScreenCoord LayerManagerComposite::GetContentShiftForToolbar() { ScreenCoord result(0.0f); // If GetTargetContext return is not null we are not drawing to the screen so there will not be any content offset. if (mCompositor->GetTargetContext() != nullptr) { return result; } if (CompositorBridgeParent* bridge = mCompositor->GetCompositorBridgeParent()) { AndroidDynamicToolbarAnimator* animator = bridge->GetAPZCTreeManager()->GetAndroidDynamicToolbarAnimator(); MOZ_RELEASE_ASSERT(animator); result.value = (float)animator->GetCurrentContentOffset().value; } return result; } void LayerManagerComposite::RenderToolbar() { // If GetTargetContext return is not null we are not drawing to the screen so don't draw the toolbar. if (mCompositor->GetTargetContext() != nullptr) { return; } if (CompositorBridgeParent* bridge = mCompositor->GetCompositorBridgeParent()) { AndroidDynamicToolbarAnimator* animator = bridge->GetAPZCTreeManager()->GetAndroidDynamicToolbarAnimator(); MOZ_RELEASE_ASSERT(animator); int32_t toolbarHeight = animator->GetCurrentToolbarHeight(); if (toolbarHeight == 0) { return; } EffectChain effects; effects.mPrimaryEffect = animator->GetToolbarEffect(mCompositor->AsCompositorOGL()); // If GetToolbarEffect returns null, nothing is rendered for the static snapshot of the toolbar. // If the real toolbar chrome is not covering this portion of the surface, the clear color // of the surface will be visible. On Android the clear color is the background color of the page. if (effects.mPrimaryEffect) { ScopedCompositorRenderOffset toolbarOffset(mCompositor->AsCompositorOGL(), ScreenPoint(0.0f, -animator->GetCurrentContentOffset())); mCompositor->DrawQuad(gfx::Rect(0, 0, mRenderBounds.width, toolbarHeight), IntRect(0, 0, mRenderBounds.width, toolbarHeight), effects, 1.0, gfx::Matrix4x4()); } } } // Used by robocop tests to get a snapshot of the frame buffer. void LayerManagerComposite::HandlePixelsTarget() { if (!mScreenPixelsTarget) { return; } int32_t bufferWidth = mRenderBounds.width; int32_t bufferHeight = mRenderBounds.height; ipc::Shmem mem; if (!mScreenPixelsTarget->AllocPixelBuffer(bufferWidth * bufferHeight * sizeof(uint32_t), &mem)) { // Failed to alloc shmem, Just bail out. return; } CompositorOGL* compositor = mCompositor->AsCompositorOGL(); GLContext* gl = compositor->gl(); MOZ_ASSERT(gl); gl->fReadPixels(0, 0, bufferWidth, bufferHeight, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, mem.get()); Unused << mScreenPixelsTarget->SendScreenPixels(mem, ScreenIntSize(bufferWidth, bufferHeight)); mScreenPixelsTarget = nullptr; } #endif class TextLayerComposite : public TextLayer, public LayerComposite { public: explicit TextLayerComposite(LayerManagerComposite *aManager) : TextLayer(aManager, nullptr) , LayerComposite(aManager) { MOZ_COUNT_CTOR(TextLayerComposite); mImplData = static_cast(this); } protected: ~TextLayerComposite() { MOZ_COUNT_DTOR(TextLayerComposite); Destroy(); } public: // LayerComposite Implementation virtual Layer* GetLayer() override { return this; } virtual void SetLayerManager(HostLayerManager* aManager) override { LayerComposite::SetLayerManager(aManager); mManager = aManager; } virtual void Destroy() override { mDestroyed = true; } virtual void RenderLayer(const gfx::IntRect& aClipRect, const Maybe& aGeometry) override {} virtual void CleanupResources() override {}; virtual void GenEffectChain(EffectChain& aEffect) override {} CompositableHost* GetCompositableHost() override { return nullptr; } virtual HostLayer* AsHostLayer() override { return this; } virtual const char* Name() const override { return "TextLayerComposite"; } }; class BorderLayerComposite : public BorderLayer, public LayerComposite { public: explicit BorderLayerComposite(LayerManagerComposite *aManager) : BorderLayer(aManager, nullptr) , LayerComposite(aManager) { MOZ_COUNT_CTOR(BorderLayerComposite); mImplData = static_cast(this); } protected: ~BorderLayerComposite() { MOZ_COUNT_DTOR(BorderLayerComposite); Destroy(); } public: // LayerComposite Implementation virtual Layer* GetLayer() override { return this; } virtual void SetLayerManager(HostLayerManager* aManager) override { LayerComposite::SetLayerManager(aManager); mManager = aManager; } virtual void Destroy() override { mDestroyed = true; } virtual void RenderLayer(const gfx::IntRect& aClipRect, const Maybe& aGeometry) override {} virtual void CleanupResources() override {}; virtual void GenEffectChain(EffectChain& aEffect) override {} CompositableHost* GetCompositableHost() override { return nullptr; } virtual HostLayer* AsHostLayer() override { return this; } virtual const char* Name() const override { return "BorderLayerComposite"; } }; already_AddRefed LayerManagerComposite::CreatePaintedLayer() { if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return RefPtr(new PaintedLayerComposite(this)).forget(); } already_AddRefed LayerManagerComposite::CreateContainerLayer() { if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return RefPtr(new ContainerLayerComposite(this)).forget(); } already_AddRefed LayerManagerComposite::CreateImageLayer() { if (mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return RefPtr(new ImageLayerComposite(this)).forget(); } already_AddRefed LayerManagerComposite::CreateColorLayer() { if (LayerManagerComposite::mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return RefPtr(new ColorLayerComposite(this)).forget(); } already_AddRefed LayerManagerComposite::CreateCanvasLayer() { if (LayerManagerComposite::mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return RefPtr(new CanvasLayerComposite(this)).forget(); } already_AddRefed LayerManagerComposite::CreateRefLayer() { if (LayerManagerComposite::mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return RefPtr(new RefLayerComposite(this)).forget(); } already_AddRefed LayerManagerComposite::CreateTextLayer() { if (LayerManagerComposite::mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return RefPtr(new TextLayerComposite(this)).forget(); } already_AddRefed LayerManagerComposite::CreateBorderLayer() { if (LayerManagerComposite::mDestroyed) { NS_WARNING("Call on destroyed layer manager"); return nullptr; } return RefPtr(new BorderLayerComposite(this)).forget(); } LayerManagerComposite::AutoAddMaskEffect::AutoAddMaskEffect(Layer* aMaskLayer, EffectChain& aEffects) : mCompositable(nullptr), mFailed(false) { if (!aMaskLayer) { return; } mCompositable = ToLayerComposite(aMaskLayer)->GetCompositableHost(); if (!mCompositable) { NS_WARNING("Mask layer with no compositable host"); mFailed = true; return; } if (!mCompositable->AddMaskEffect(aEffects, aMaskLayer->GetEffectiveTransform())) { mCompositable = nullptr; mFailed = true; } } LayerManagerComposite::AutoAddMaskEffect::~AutoAddMaskEffect() { if (!mCompositable) { return; } mCompositable->RemoveMaskEffect(); } bool LayerManagerComposite::IsCompositingToScreen() const { if (!mCompositor) { return true; } return !mCompositor->GetTargetContext(); } LayerComposite::LayerComposite(LayerManagerComposite *aManager) : HostLayer(aManager) , mCompositeManager(aManager) , mCompositor(aManager->GetCompositor()) , mDestroyed(false) , mLayerComposited(false) { } LayerComposite::~LayerComposite() { } void LayerComposite::Destroy() { if (!mDestroyed) { mDestroyed = true; CleanupResources(); } } void LayerComposite::AddBlendModeEffect(EffectChain& aEffectChain) { gfx::CompositionOp blendMode = GetLayer()->GetEffectiveMixBlendMode(); if (blendMode == gfx::CompositionOp::OP_OVER) { return; } aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE] = new EffectBlendMode(blendMode); return; } bool LayerManagerComposite::CanUseCanvasLayerForSize(const IntSize &aSize) { return mCompositor->CanUseCanvasLayerForSize(gfx::IntSize(aSize.width, aSize.height)); } void LayerManagerComposite::NotifyShadowTreeTransaction() { if (gfxPrefs::LayersDrawFPS()) { mDiagnostics->AddTxnFrame(); } } void LayerComposite::SetLayerManager(HostLayerManager* aManager) { HostLayer::SetLayerManager(aManager); mCompositeManager = static_cast(aManager); mCompositor = mCompositeManager->GetCompositor(); } bool LayerManagerComposite::AsyncPanZoomEnabled() const { if (CompositorBridgeParent* bridge = mCompositor->GetCompositorBridgeParent()) { return bridge->GetOptions().UseAPZ(); } return false; } bool LayerManagerComposite::AlwaysScheduleComposite() const { return !!(mCompositor->GetDiagnosticTypes() & DiagnosticTypes::FLASH_BORDERS); } nsIntRegion LayerComposite::GetFullyRenderedRegion() { if (TiledContentHost* tiled = GetCompositableHost() ? GetCompositableHost()->AsTiledContentHost() : nullptr) { nsIntRegion shadowVisibleRegion = GetShadowVisibleRegion().ToUnknownRegion(); // Discard the region which hasn't been drawn yet when doing // progressive drawing. Note that if the shadow visible region // shrunk the tiled valig region may not have discarded this yet. shadowVisibleRegion.And(shadowVisibleRegion, tiled->GetValidRegion()); return shadowVisibleRegion; } else { return GetShadowVisibleRegion().ToUnknownRegion(); } } Matrix4x4 HostLayer::GetShadowTransform() { Matrix4x4 transform = mShadowTransform; Layer* layer = GetLayer(); transform.PostScale(layer->GetPostXScale(), layer->GetPostYScale(), 1.0f); if (const ContainerLayer* c = layer->AsContainerLayer()) { transform.PreScale(c->GetPreXScale(), c->GetPreYScale(), 1.0f); } return transform; } bool LayerComposite::HasStaleCompositor() const { return mCompositeManager->GetCompositor() != mCompositor; } #ifndef MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS /*static*/ bool LayerManagerComposite::SupportsDirectTexturing() { return false; } /*static*/ void LayerManagerComposite::PlatformSyncBeforeReplyUpdate() { } #endif // !defined(MOZ_HAVE_PLATFORM_SPECIFIC_LAYER_BUFFERS) } // namespace layers } // namespace mozilla