/* -*- Mode: C++; tab-width: 2; 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/. */ #ifndef mozilla_layers_NativeLayerCA_h #define mozilla_layers_NativeLayerCA_h #include #include #include #include #include "mozilla/Mutex.h" #include "mozilla/TimeStamp.h" #include "mozilla/gfx/MacIOSurface.h" #include "mozilla/layers/NativeLayer.h" #include "mozilla/layers/NativeLayerMacSurfaceHandler.h" #include "mozilla/webrender/RenderMacIOSurfaceTextureHost.h" #include "CFTypeRefPtr.h" #include "nsRegion.h" #include "nsISupportsImpl.h" #ifdef __OBJC__ @class CALayer; @class CARenderer; #else typedef void CALayer; typedef void CARenderer; #endif namespace mozilla { namespace gl { class GLContextCGL; class MozFramebuffer; } // namespace gl namespace wr { class RenderMacIOSurfaceTextureHost; } // namespace wr namespace layers { class NativeLayerRootSnapshotterCA; enum class VideoLowPowerType { // These must be kept synchronized with the telemetry histogram enums. NotVideo, // Never emitted as telemetry. No video is visible. LowPower, // As best we can tell, we are in the "detached", // low-power compositing mode. We don't use "Success" // because of name collision with telemetry generation. FailMultipleVideo, // There is more than one video visible. FailWindowed, // The window is not fullscreen. FailOverlaid, // Something is on top of the video (likely captions). FailBacking, // The layer behind the video is not full-coverage black. FailMacOSVersion, // macOS version does not meet requirements. FailPref, // Pref is not set. FailSurface, // Surface is not eligible. FailEnqueue, // Enqueueing the video didn't work. }; // The type of update needed to apply the pending changes to a NativeLayerCA // representation. // // Order is important. Each enum must fully encompass the work implied by the // previous enums. enum class NativeLayerCAUpdateType { None, OnlyVideo, All, }; class SnapshotterCADelegate { public: virtual ~SnapshotterCADelegate(); virtual float BackingScale() const { return 1.0f; } virtual bool DoCustomReadbackForReftestsIfDesired( const gfx::IntSize& aReadbackSize, gfx::SurfaceFormat aReadbackFormat, const Range& aReadbackBuffer) { return false; } virtual void UpdateSnapshotterLayers(CALayer* aRootCALayer) = 0; virtual void OnSnapshotterDestroyed( NativeLayerRootSnapshotterCA* aSnapshotter) {} }; // NativeLayerRootCA is the CoreAnimation implementation of the NativeLayerRoot // interface. A NativeLayerRootCA is created by the widget around an existing // CALayer with a call to CreateForCALayer - this CALayer is the root of the // "onscreen" representation of this layer tree. // All methods can be called from any thread, there is internal locking. // All effects from mutating methods are buffered locally and don't modify the // underlying CoreAnimation layers until CommitToScreen() is called. This // ensures that the modifications happen on the right thread. // // More specifically: During normal operation, screen updates are driven from a // compositing thread. On this thread, the layers are created / destroyed, their // contents are painted, and the result is committed to the screen. However, // there are some scenarios that need to involve the main thread, most notably // window resizing: During a window resize, we still need the drawing part to // happen on the compositing thread, but the modifications to the underlying // CALayers need to happen on the main thread, once compositing is done. // // NativeLayerRootCA + NativeLayerCA create and maintain *two* CALayer tree // representations: An "onscreen" representation and an "offscreen" // representation. These representations are updated via calls to // CommitToScreen() and CommitOffscreen(), respectively. The reason for having // two representations is the following: Our implementation of the snapshotter // API uses CARenderer, which lets us render the composited result of our layer // tree into a GPU buffer. But CARenderer requires "ownership" of the rendered // CALayers in the sense that it associates the CALayers with a local // "CAContext". A CALayer can only be associated with one CAContext at any time. // If we wanted te render our *onscreen* CALayers with CARenderer, we would need // to remove them from the window, reparent them to the CARenderer, render them, // and then put them back into the window. This would lead to a visible flashing // effect. To solve this problem, we build two CALayer representations, so that // one representation can stay inside the window and the other can stay attached // to the CARenderer. class NativeLayerRootCA final : public NativeLayerRoot { public: static already_AddRefed CreateForCALayer(CALayer* aLayer); virtual NativeLayerRootCA* AsNativeLayerRootCA() override { return this; } // Can be called on any thread at any point. Returns whether comitting was // successful. Will return false if called off the main thread while // off-main-thread commits are suspended. bool CommitToScreen() override; void CommitOffscreen(CALayer* aRootCALayer); void OnNativeLayerRootSnapshotterDestroyed( NativeLayerRootSnapshotterCA* aNativeLayerRootSnapshotter); // Enters a mode during which CommitToScreen(), when called on a non-main // thread, will not apply any updates to the CALayer tree. void SuspendOffMainThreadCommits(); // Exits the mode entered by SuspendOffMainThreadCommits(). // Returns true if the last CommitToScreen() was canceled due to suspension, // indicating that another call to CommitToScreen() is needed. bool UnsuspendOffMainThreadCommits(); bool AreOffMainThreadCommitsSuspended(); void DumpLayerTreeToFile(const char* aPath, const MutexAutoLock& aProofOfLock); enum class WhichRepresentation : uint8_t { ONSCREEN, OFFSCREEN }; // Overridden methods already_AddRefed CreateLayer( const gfx::IntSize& aSize, bool aIsOpaque, SurfacePoolHandle* aSurfacePoolHandle) override; void AppendLayer(NativeLayer* aLayer) override; void RemoveLayer(NativeLayer* aLayer) override; void SetLayers(const nsTArray>& aLayers) override; UniquePtr CreateSnapshotter() override; void SetBackingScale(float aBackingScale); float BackingScale(); already_AddRefed CreateLayerForExternalTexture( bool aIsOpaque) override; already_AddRefed CreateLayerForColor( gfx::DeviceColor aColor) override; // A macOS-specific layer creation method, which uses no // SurfacePoolHandle, because it will be handed surfaces directly. already_AddRefed CreateLayerForSurfacePresentation( const gfx::IntSize& aSize, bool aIsOpaque); void SetWindowIsFullscreen(bool aFullscreen); VideoLowPowerType CheckVideoLowPower(const MutexAutoLock& aProofOfLock); protected: explicit NativeLayerRootCA(CALayer* aLayer); ~NativeLayerRootCA() override; // Do a commit of the pending changes to the CALayers. This is used for both // onscreen commits (modifying the CALayers attached to the NSView), and for // "offscreen" commits. // "Offscreen" commits are updating a separate copy of the CALayer tree for // use with CARenderer by the snapshotter. void CommitRepresentation(WhichRepresentation aRepresentation, CALayer* aRootCALayer, const nsTArray>& aSublayers, bool aMutatedLayerStructure, bool aWindowIsFullscreen); void SetMutatedLayerStructure(); using UpdateType = NativeLayerCAUpdateType; UpdateType GetMaxUpdateRequired( WhichRepresentation aRepresentation, const nsTArray>& aSublayers, bool aMutatedLayerStructure) const; // An implementation of SnapshotterCADelegate, backed by a NativeLayerRootCA. struct SnapshotterDelegate final : public SnapshotterCADelegate { explicit SnapshotterDelegate(NativeLayerRootCA* aLayerRoot); virtual ~SnapshotterDelegate() override; virtual float BackingScale() const override { return mLayerRoot->BackingScale(); } virtual void UpdateSnapshotterLayers(CALayer* aRootCALayer) override { mLayerRoot->CommitOffscreen(aRootCALayer); } virtual void OnSnapshotterDestroyed( NativeLayerRootSnapshotterCA* aSnapshotter) override { mLayerRoot->OnNativeLayerRootSnapshotterDestroyed(aSnapshotter); } RefPtr mLayerRoot; }; Mutex mMutex MOZ_UNANNOTATED; // protects all other fields CALayer* mOnscreenRootCALayer = nullptr; // strong CALayer* mOffscreenRootCALayer = nullptr; // strong NativeLayerRootSnapshotterCA* mWeakSnapshotter = nullptr; nsTArray> mSublayers; // in z-order float mBackingScale = 1.0f; bool mMutated = false; // While mOffMainThreadCommitsSuspended is true, no commits // should happen on a non-main thread, because they might race with // main-thread driven updates such as window shape changes, and cause // glitches. bool mOffMainThreadCommitsSuspended = false; // Set to true if CommitToScreen() was aborted because of commit suspension. // Set to false when CommitToScreen() completes successfully. When true, // indicates that CommitToScreen() needs to be called at the next available // opportunity. bool mCommitPending = false; // Updated by the layer's view's window to match the fullscreen state // of that window. bool mWindowIsFullscreen = false; // Whether mSublayers has changed since the last onscreen / offscreen commit. bool mMutatedOnscreenLayerStructure = false; bool mMutatedOffscreenLayerStructure = false; // How many times have we committed since the last time we emitted // telemetry? unsigned int mTelemetryCommitCount = 0; }; class RenderSourceNLRS; #ifdef XP_MACOSX class NativeLayerRootSnapshotterCA final : public NativeLayerRootSnapshotter { public: static UniquePtr Create( UniquePtr&& aDelegate); virtual ~NativeLayerRootSnapshotterCA(); bool ReadbackPixels(const gfx::IntSize& aReadbackSize, gfx::SurfaceFormat aReadbackFormat, const Range& aReadbackBuffer) override; already_AddRefed GetWindowContents( const gfx::IntSize& aWindowSize) override; already_AddRefed CreateDownscaleTarget( const gfx::IntSize& aSize) override; already_AddRefed CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) override; protected: NativeLayerRootSnapshotterCA(UniquePtr&& aDelegate, RefPtr&& aGL); void UpdateSnapshot(const gfx::IntSize& aSize); UniquePtr mDelegate; RefPtr mGL; // Can be null. Created and updated in UpdateSnapshot. RefPtr mSnapshot; CARenderer* mRenderer = nullptr; // strong }; #endif // Wraps one CALayer representation of NativeLayerCA. struct NativeLayerCARepresentation { using UpdateType = NativeLayerCAUpdateType; NativeLayerCARepresentation(); ~NativeLayerCARepresentation(); // Returns null if the layer is currently completely clipped out. CALayer* UnderlyingCALayer() { return mWrappingCALayerHasExtent ? mWrappingCALayer : nullptr; } bool EnqueueSurface(IOSurfaceRef aSurfaceRef); // Applies buffered changes to the native CALayers. The contract with the // caller is as follows: If any of these values have changed since the last // call to ApplyChanges, mMutated[Field] needs to have been set to true // before the call. If aUpdate is not All, then a partial update will be // applied. In such a case, ApplyChanges may not make any changes that // require a CATransacation, because no transaction will be created. In a // a partial update, the return value will indicate if all the needed // changes were able to be applied under these restrictions. A false return // value indicates an All update is necessary. bool ApplyChanges(NativeLayerCAUpdateType aUpdate, const gfx::IntSize& aSize, bool aIsOpaque, const gfx::IntPoint& aPosition, const gfx::Matrix4x4& aTransform, const gfx::IntRect& aDisplayRect, const Maybe& aClipRect, const Maybe& aRoundedClip, float aBackingScale, bool aSurfaceIsFlipped, gfx::SamplingFilter aSamplingFilter, bool aSpecializeVideo, const CFTypeRefPtr& aFrontSurface, const Maybe& aColor, bool aIsDRM, bool aIsVideo); // Return whether any aspects of this layer representation have been mutated // since the last call to ApplyChanges, i.e. whether ApplyChanges needs to // be called. // This is used to optimize away a CATransaction commit if no layers have // changed. NativeLayerCAUpdateType HasUpdate(bool aIsVideo); // Lazily initialized by first call to ApplyChanges. mWrappingLayer is the // layer that applies the intersection of mDisplayRect and mClipRect (if // set), and mContentCALayer is the layer that hosts the IOSurface. We do // not share clip layers between consecutive NativeLayerCA objects with the // same clip rect. CALayer* mWrappingCALayer = nullptr; // strong CALayer* mRoundedClipCALayer = nullptr; // strong CALayer* mContentCALayer = nullptr; // strong CALayer* mOpaquenessTintLayer = nullptr; // strong #ifdef NIGHTLY_BUILD bool mLogNextVideoSurface = false; #endif bool mWrappingCALayerHasExtent : 1; // These are all initialized to true by the constructor. bool mMutatedPosition : 1; bool mMutatedTransform : 1; bool mMutatedDisplayRect : 1; bool mMutatedClipRect : 1; bool mMutatedRoundedClipRect : 1; bool mMutatedBackingScale : 1; bool mMutatedSize : 1; bool mMutatedSurfaceIsFlipped : 1; bool mMutatedFrontSurface : 1; bool mMutatedSamplingFilter : 1; bool mMutatedSpecializeVideo : 1; bool mMutatedIsDRM : 1; // Don't forget to update the constructor when you add a field here. }; // NativeLayerCA wraps a CALayer and lets you draw to it. It ensures that only // fully-drawn frames make their way to the screen, by maintaining a swap chain // of IOSurfaces. // All calls to mutating methods are buffered, and don't take effect on the // underlying CoreAnimation layers until ApplyChanges() is called. // The two most important methods are NextSurface and NotifySurfaceReady: // NextSurface takes an available surface from the swap chain or creates a new // surface if necessary. This surface can then be drawn to. Once drawing is // finished, NotifySurfaceReady marks the surface as ready. This surface is // committed to the layer during the next call to ApplyChanges(). // The swap chain keeps track of invalid areas within the surfaces. class NativeLayerCA : public NativeLayer { public: virtual NativeLayerCA* AsNativeLayerCA() override { return this; } // Overridden methods gfx::IntSize GetSize() override; void SetPosition(const gfx::IntPoint& aPosition) override; gfx::IntPoint GetPosition() override; void SetTransform(const gfx::Matrix4x4& aTransform) override; gfx::Matrix4x4 GetTransform() override; gfx::IntRect GetRect() override; void SetSamplingFilter(gfx::SamplingFilter aSamplingFilter) override; RefPtr NextSurfaceAsDrawTarget( const gfx::IntRect& aDisplayRect, const gfx::IntRegion& aUpdateRegion, gfx::BackendType aBackendType) override; Maybe NextSurfaceAsFramebuffer(const gfx::IntRect& aDisplayRect, const gfx::IntRegion& aUpdateRegion, bool aNeedsDepth) override; void NotifySurfaceReady() override; void DiscardBackbuffers() override; bool IsOpaque() override; void SetClipRect(const Maybe& aClipRect) override; Maybe ClipRect() override; void SetRoundedClipRect(const Maybe& aClip) override; Maybe RoundedClipRect() override; gfx::IntRect CurrentSurfaceDisplayRect() override; void SetDisplayRect(const gfx::IntRect& aDisplayRect); void SetSurfaceIsFlipped(bool aIsFlipped) override; bool SurfaceIsFlipped() override; // Used to force a specific IOSurfaceRef to be used. void SetSurfaceToPresent(CFTypeRefPtr aSurfaceRef, gfx::IntSize& aSize, bool aIsDRM, bool aIsHDR); void DumpLayer(std::ostream& aOutputStream); void AttachExternalImage(wr::RenderTextureHost* aExternalImage) override; GpuFence* GetGpuFence() override; void SetRootWindowIsFullscreen(bool aFullscreen); protected: friend class NativeLayerRootCA; friend struct NativeLayerCARepresentation; using UpdateType = NativeLayerCAUpdateType; using WhichRepresentation = NativeLayerRootCA::WhichRepresentation; using Representation = NativeLayerCARepresentation; NativeLayerCA(const gfx::IntSize& aSize, bool aIsOpaque, SurfacePoolHandleCA* aSurfacePoolHandle); explicit NativeLayerCA(bool aIsOpaque); explicit NativeLayerCA(gfx::DeviceColor aColor); // This constructor is used for surfaces being directly supplied // to the layer. explicit NativeLayerCA(const gfx::IntSize& aSize, bool aIsOpaque); ~NativeLayerCA() override; // To be called by NativeLayerRootCA: CALayer* UnderlyingCALayer(WhichRepresentation aRepresentation); NativeLayerCAUpdateType HasUpdate(WhichRepresentation aRepresentation); // Apply pending updates to the underlaying CALayer. Sets *aMustRebuild to // true if the update requires changing which set of CALayers should be in the // parent. bool ApplyChanges(WhichRepresentation aRepresentation, UpdateType aUpdate, bool* aMustRebuild); void SetBackingScale(float aBackingScale); // Invalidate aUpdateRegion and make sure that mInProgressSurface retains any // valid content from the previous surface outside of aUpdateRegion, so that // only aUpdateRegion needs to be drawn. If content needs to be copied, // aCopyFn is called to do the copying. // aCopyFn: Fn(CFTypeRefPtr aValidSourceIOSurface, // const gfx::IntRegion& aCopyRegion) -> void template void HandlePartialUpdate(const MutexAutoLock& aProofOfLock, const gfx::IntRect& aDisplayRect, const gfx::IntRegion& aUpdateRegion, F&& aCopyFn); bool IsVideo(const MutexAutoLock& aProofOfLock); bool ShouldSpecializeVideo(const MutexAutoLock& aProofOfLock); // This function returns a CGRect if a clip should be applied to the layer. // If set, the CGRect has the scaled position of the clip relative to the // surface origin and the scaled size of the clip rect. static Maybe CalculateClipGeometry( const gfx::IntSize& aSize, const gfx::IntPoint& aPosition, const gfx::Matrix4x4& aTransform, const gfx::IntRect& aDisplayRect, const Maybe& aClipRect, float aBackingScale); Representation& GetRepresentation(WhichRepresentation aRepresentation); template void ForAllRepresentations(F aFn); // Controls access to all fields of this class. Mutex mMutex MOZ_UNANNOTATED; // The IOSurface to use for this layer. Optional. // If not present, we get use a surface from mSurfaceHandler or mTextureHost // instead. We mark the surface as "in use" as long as it is in // mSurfaceToPresent, to prevent other processes from recycling it. CFTypeRefPtr mSurfaceToPresent; Maybe mSurfaceHandler; RefPtr mTextureHost; bool mTextureHostIsVideo = false; Representation mOnscreenRepresentation; Representation mOffscreenRepresentation; gfx::IntPoint mPosition; gfx::Matrix4x4 mTransform; gfx::IntRect mDisplayRect; gfx::IntSize mSize; Maybe mClipRect; Maybe mRoundedClipRect; Maybe mColor; gfx::SamplingFilter mSamplingFilter = gfx::SamplingFilter::POINT; float mBackingScale = 1.0f; bool mSurfaceIsFlipped = false; const bool mIsOpaque = false; bool mRootWindowIsFullscreen = false; bool mSpecializeVideo = false; bool mIsDRM = false; bool mIsHDR = false; #ifdef NIGHTLY_BUILD // Track the consistency of our caller's API usage. Layers that are drawn // should only ever be called with NotifySurfaceReady. Layers that are // external should only ever be called with AttachExternalImage. bool mHasEverAttachExternalImage = false; bool mHasEverNotifySurfaceReady = false; #endif }; } // namespace layers } // namespace mozilla #endif // mozilla_layers_NativeLayerCA_h