forked from mirrors/gecko-dev
		
	 cbf0e5ea15
			
		
	
	
		cbf0e5ea15
		
	
	
	
	
		
			
			By removing the async mode, remote texture implementation becomes simpler. Differential Revision: https://phabricator.services.mozilla.com/D197323
		
			
				
	
	
		
			2110 lines
		
	
	
	
		
			73 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			2110 lines
		
	
	
	
		
			73 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
| /* -*- 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 "mozilla/layers/NativeLayerCA.h"
 | |
| 
 | |
| #import <AppKit/NSAnimationContext.h>
 | |
| #import <AppKit/NSColor.h>
 | |
| #import <AVFoundation/AVFoundation.h>
 | |
| #import <OpenGL/gl.h>
 | |
| #import <QuartzCore/QuartzCore.h>
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <fstream>
 | |
| #include <iostream>
 | |
| #include <sstream>
 | |
| #include <utility>
 | |
| 
 | |
| #include "gfxUtils.h"
 | |
| #include "GLBlitHelper.h"
 | |
| #include "GLContextCGL.h"
 | |
| #include "GLContextProvider.h"
 | |
| #include "MozFramebuffer.h"
 | |
| #include "mozilla/gfx/Swizzle.h"
 | |
| #include "mozilla/layers/ScreenshotGrabber.h"
 | |
| #include "mozilla/layers/SurfacePoolCA.h"
 | |
| #include "mozilla/StaticPrefs_gfx.h"
 | |
| #include "mozilla/Telemetry.h"
 | |
| #include "mozilla/webrender/RenderMacIOSurfaceTextureHost.h"
 | |
| #include "ScopedGLHelpers.h"
 | |
| 
 | |
| @interface CALayer (PrivateSetContentsOpaque)
 | |
| - (void)setContentsOpaque:(BOOL)opaque;
 | |
| @end
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace layers {
 | |
| 
 | |
| using gfx::DataSourceSurface;
 | |
| using gfx::IntPoint;
 | |
| using gfx::IntRect;
 | |
| using gfx::IntRegion;
 | |
| using gfx::IntSize;
 | |
| using gfx::Matrix4x4;
 | |
| using gfx::SurfaceFormat;
 | |
| using gl::GLContext;
 | |
| using gl::GLContextCGL;
 | |
| 
 | |
| static Maybe<Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER>
 | |
| VideoLowPowerTypeToTelemetryType(VideoLowPowerType aVideoLowPower) {
 | |
|   switch (aVideoLowPower) {
 | |
|     case VideoLowPowerType::LowPower:
 | |
|       return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::LowPower);
 | |
| 
 | |
|     case VideoLowPowerType::FailMultipleVideo:
 | |
|       return Some(
 | |
|           Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailMultipleVideo);
 | |
| 
 | |
|     case VideoLowPowerType::FailWindowed:
 | |
|       return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailWindowed);
 | |
| 
 | |
|     case VideoLowPowerType::FailOverlaid:
 | |
|       return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailOverlaid);
 | |
| 
 | |
|     case VideoLowPowerType::FailBacking:
 | |
|       return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailBacking);
 | |
| 
 | |
|     case VideoLowPowerType::FailMacOSVersion:
 | |
|       return Some(
 | |
|           Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailMacOSVersion);
 | |
| 
 | |
|     case VideoLowPowerType::FailPref:
 | |
|       return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailPref);
 | |
| 
 | |
|     case VideoLowPowerType::FailSurface:
 | |
|       return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailSurface);
 | |
| 
 | |
|     case VideoLowPowerType::FailEnqueue:
 | |
|       return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailEnqueue);
 | |
| 
 | |
|     default:
 | |
|       return Nothing();
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void EmitTelemetryForVideoLowPower(VideoLowPowerType aVideoLowPower) {
 | |
|   auto telemetryValue = VideoLowPowerTypeToTelemetryType(aVideoLowPower);
 | |
|   if (telemetryValue.isSome()) {
 | |
|     Telemetry::AccumulateCategorical(telemetryValue.value());
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Utility classes for NativeLayerRootSnapshotter (NLRS) profiler screenshots.
 | |
| 
 | |
| class RenderSourceNLRS : public profiler_screenshots::RenderSource {
 | |
|  public:
 | |
|   explicit RenderSourceNLRS(UniquePtr<gl::MozFramebuffer>&& aFramebuffer)
 | |
|       : RenderSource(aFramebuffer->mSize),
 | |
|         mFramebuffer(std::move(aFramebuffer)) {}
 | |
|   auto& FB() { return *mFramebuffer; }
 | |
| 
 | |
|  protected:
 | |
|   UniquePtr<gl::MozFramebuffer> mFramebuffer;
 | |
| };
 | |
| 
 | |
| class DownscaleTargetNLRS : public profiler_screenshots::DownscaleTarget {
 | |
|  public:
 | |
|   DownscaleTargetNLRS(gl::GLContext* aGL,
 | |
|                       UniquePtr<gl::MozFramebuffer>&& aFramebuffer)
 | |
|       : profiler_screenshots::DownscaleTarget(aFramebuffer->mSize),
 | |
|         mGL(aGL),
 | |
|         mRenderSource(new RenderSourceNLRS(std::move(aFramebuffer))) {}
 | |
|   already_AddRefed<profiler_screenshots::RenderSource> AsRenderSource()
 | |
|       override {
 | |
|     return do_AddRef(mRenderSource);
 | |
|   };
 | |
|   bool DownscaleFrom(profiler_screenshots::RenderSource* aSource,
 | |
|                      const IntRect& aSourceRect,
 | |
|                      const IntRect& aDestRect) override;
 | |
| 
 | |
|  protected:
 | |
|   RefPtr<gl::GLContext> mGL;
 | |
|   RefPtr<RenderSourceNLRS> mRenderSource;
 | |
| };
 | |
| 
 | |
| class AsyncReadbackBufferNLRS
 | |
|     : public profiler_screenshots::AsyncReadbackBuffer {
 | |
|  public:
 | |
|   AsyncReadbackBufferNLRS(gl::GLContext* aGL, const IntSize& aSize,
 | |
|                           GLuint aBufferHandle)
 | |
|       : profiler_screenshots::AsyncReadbackBuffer(aSize),
 | |
|         mGL(aGL),
 | |
|         mBufferHandle(aBufferHandle) {}
 | |
|   void CopyFrom(profiler_screenshots::RenderSource* aSource) override;
 | |
|   bool MapAndCopyInto(DataSourceSurface* aSurface,
 | |
|                       const IntSize& aReadSize) override;
 | |
| 
 | |
|  protected:
 | |
|   virtual ~AsyncReadbackBufferNLRS();
 | |
|   RefPtr<gl::GLContext> mGL;
 | |
|   GLuint mBufferHandle = 0;
 | |
| };
 | |
| 
 | |
| // Needs to be on the stack whenever CALayer mutations are performed.
 | |
| // (Mutating CALayers outside of a transaction can result in permanently stuck
 | |
| // rendering, because such mutations create an implicit transaction which never
 | |
| // auto-commits if the current thread does not have a native runloop.) Uses
 | |
| // NSAnimationContext, which wraps CATransaction with additional off-main-thread
 | |
| // protection, see bug 1585523.
 | |
| struct MOZ_STACK_CLASS AutoCATransaction final {
 | |
|   AutoCATransaction() {
 | |
|     [NSAnimationContext beginGrouping];
 | |
|     // By default, mutating a CALayer property triggers an animation which
 | |
|     // smoothly transitions the property to the new value. We don't need these
 | |
|     // animations, and this call turns them off:
 | |
|     [CATransaction setDisableActions:YES];
 | |
|   }
 | |
|   ~AutoCATransaction() { [NSAnimationContext endGrouping]; }
 | |
| };
 | |
| 
 | |
| /* static */ already_AddRefed<NativeLayerRootCA>
 | |
| NativeLayerRootCA::CreateForCALayer(CALayer* aLayer) {
 | |
|   RefPtr<NativeLayerRootCA> layerRoot = new NativeLayerRootCA(aLayer);
 | |
|   return layerRoot.forget();
 | |
| }
 | |
| 
 | |
| // Returns an autoreleased CALayer* object.
 | |
| static CALayer* MakeOffscreenRootCALayer() {
 | |
|   // This layer should behave similarly to the backing layer of a flipped
 | |
|   // NSView. It will never be rendered on the screen and it will never be
 | |
|   // attached to an NSView's layer; instead, it will be the root layer of a
 | |
|   // "local" CAContext. Setting geometryFlipped to YES causes the orientation of
 | |
|   // descendant CALayers' contents (such as IOSurfaces) to be consistent with
 | |
|   // what happens in a layer subtree that is attached to a flipped NSView.
 | |
|   // Setting it to NO would cause the surfaces in individual leaf layers to
 | |
|   // render upside down (rather than just flipping the entire layer tree upside
 | |
|   // down).
 | |
|   AutoCATransaction transaction;
 | |
|   CALayer* layer = [CALayer layer];
 | |
|   layer.position = NSZeroPoint;
 | |
|   layer.bounds = NSZeroRect;
 | |
|   layer.anchorPoint = NSZeroPoint;
 | |
|   layer.contentsGravity = kCAGravityTopLeft;
 | |
|   layer.masksToBounds = YES;
 | |
|   layer.geometryFlipped = YES;
 | |
|   return layer;
 | |
| }
 | |
| 
 | |
| NativeLayerRootCA::NativeLayerRootCA(CALayer* aLayer)
 | |
|     : mMutex("NativeLayerRootCA"),
 | |
|       mOnscreenRepresentation(aLayer),
 | |
|       mOffscreenRepresentation(MakeOffscreenRootCALayer()) {}
 | |
| 
 | |
| NativeLayerRootCA::~NativeLayerRootCA() {
 | |
|   MOZ_RELEASE_ASSERT(
 | |
|       mSublayers.IsEmpty(),
 | |
|       "Please clear all layers before destroying the layer root.");
 | |
| }
 | |
| 
 | |
| already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayer(
 | |
|     const IntSize& aSize, bool aIsOpaque,
 | |
|     SurfacePoolHandle* aSurfacePoolHandle) {
 | |
|   RefPtr<NativeLayer> layer = new NativeLayerCA(
 | |
|       aSize, aIsOpaque, aSurfacePoolHandle->AsSurfacePoolHandleCA());
 | |
|   return layer.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayerForExternalTexture(
 | |
|     bool aIsOpaque) {
 | |
|   RefPtr<NativeLayer> layer = new NativeLayerCA(aIsOpaque);
 | |
|   return layer.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayerForColor(
 | |
|     gfx::DeviceColor aColor) {
 | |
|   RefPtr<NativeLayer> layer = new NativeLayerCA(aColor);
 | |
|   return layer.forget();
 | |
| }
 | |
| 
 | |
| void NativeLayerRootCA::AppendLayer(NativeLayer* aLayer) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA();
 | |
|   MOZ_RELEASE_ASSERT(layerCA);
 | |
| 
 | |
|   mSublayers.AppendElement(layerCA);
 | |
|   layerCA->SetBackingScale(mBackingScale);
 | |
|   layerCA->SetRootWindowIsFullscreen(mWindowIsFullscreen);
 | |
|   ForAllRepresentations(
 | |
|       [&](Representation& r) { r.mMutatedLayerStructure = true; });
 | |
| }
 | |
| 
 | |
| void NativeLayerRootCA::RemoveLayer(NativeLayer* aLayer) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA();
 | |
|   MOZ_RELEASE_ASSERT(layerCA);
 | |
| 
 | |
|   mSublayers.RemoveElement(layerCA);
 | |
|   ForAllRepresentations(
 | |
|       [&](Representation& r) { r.mMutatedLayerStructure = true; });
 | |
| }
 | |
| 
 | |
| void NativeLayerRootCA::SetLayers(
 | |
|     const nsTArray<RefPtr<NativeLayer>>& aLayers) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   // Ideally, we'd just be able to do mSublayers = std::move(aLayers).
 | |
|   // However, aLayers has a different type: it carries NativeLayer objects,
 | |
|   // whereas mSublayers carries NativeLayerCA objects, so we have to downcast
 | |
|   // all the elements first. There's one other reason to look at all the
 | |
|   // elements in aLayers first: We need to make sure any new layers know about
 | |
|   // our current backing scale.
 | |
| 
 | |
|   nsTArray<RefPtr<NativeLayerCA>> layersCA(aLayers.Length());
 | |
|   for (auto& layer : aLayers) {
 | |
|     RefPtr<NativeLayerCA> layerCA = layer->AsNativeLayerCA();
 | |
|     MOZ_RELEASE_ASSERT(layerCA);
 | |
|     layerCA->SetBackingScale(mBackingScale);
 | |
|     layerCA->SetRootWindowIsFullscreen(mWindowIsFullscreen);
 | |
|     layersCA.AppendElement(std::move(layerCA));
 | |
|   }
 | |
| 
 | |
|   if (layersCA != mSublayers) {
 | |
|     mSublayers = std::move(layersCA);
 | |
|     ForAllRepresentations(
 | |
|         [&](Representation& r) { r.mMutatedLayerStructure = true; });
 | |
|   }
 | |
| }
 | |
| 
 | |
| void NativeLayerRootCA::SetBackingScale(float aBackingScale) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   mBackingScale = aBackingScale;
 | |
|   for (auto layer : mSublayers) {
 | |
|     layer->SetBackingScale(aBackingScale);
 | |
|   }
 | |
| }
 | |
| 
 | |
| float NativeLayerRootCA::BackingScale() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return mBackingScale;
 | |
| }
 | |
| 
 | |
| void NativeLayerRootCA::SuspendOffMainThreadCommits() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   mOffMainThreadCommitsSuspended = true;
 | |
| }
 | |
| 
 | |
| bool NativeLayerRootCA::UnsuspendOffMainThreadCommits() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   mOffMainThreadCommitsSuspended = false;
 | |
|   return mCommitPending;
 | |
| }
 | |
| 
 | |
| bool NativeLayerRootCA::AreOffMainThreadCommitsSuspended() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return mOffMainThreadCommitsSuspended;
 | |
| }
 | |
| 
 | |
| bool NativeLayerRootCA::CommitToScreen() {
 | |
|   {
 | |
|     MutexAutoLock lock(mMutex);
 | |
| 
 | |
|     if (!NS_IsMainThread() && mOffMainThreadCommitsSuspended) {
 | |
|       mCommitPending = true;
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     mOnscreenRepresentation.Commit(WhichRepresentation::ONSCREEN, mSublayers,
 | |
|                                    mWindowIsFullscreen);
 | |
| 
 | |
|     mCommitPending = false;
 | |
|   }
 | |
| 
 | |
|   if (StaticPrefs::gfx_webrender_debug_dump_native_layer_tree_to_file()) {
 | |
|     static uint32_t sFrameID = 0;
 | |
|     uint32_t frameID = sFrameID++;
 | |
| 
 | |
|     NSString* dirPath =
 | |
|         [NSString stringWithFormat:@"%@/Desktop/nativelayerdumps-%d",
 | |
|                                    NSHomeDirectory(), getpid()];
 | |
|     if ([NSFileManager.defaultManager createDirectoryAtPath:dirPath
 | |
|                                 withIntermediateDirectories:YES
 | |
|                                                  attributes:nil
 | |
|                                                       error:nullptr]) {
 | |
|       NSString* filename =
 | |
|           [NSString stringWithFormat:@"frame-%d.html", frameID];
 | |
|       NSString* filePath = [dirPath stringByAppendingPathComponent:filename];
 | |
|       DumpLayerTreeToFile([filePath UTF8String]);
 | |
|     } else {
 | |
|       NSLog(@"Failed to create directory %@", dirPath);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Decide if we are going to emit telemetry about video low power on this
 | |
|   // commit.
 | |
|   static const int32_t TELEMETRY_COMMIT_PERIOD =
 | |
|       StaticPrefs::gfx_core_animation_low_power_telemetry_frames_AtStartup();
 | |
|   mTelemetryCommitCount = (mTelemetryCommitCount + 1) % TELEMETRY_COMMIT_PERIOD;
 | |
|   if (mTelemetryCommitCount == 0) {
 | |
|     // Figure out if we are hitting video low power mode.
 | |
|     VideoLowPowerType videoLowPower = CheckVideoLowPower();
 | |
|     EmitTelemetryForVideoLowPower(videoLowPower);
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| UniquePtr<NativeLayerRootSnapshotter> NativeLayerRootCA::CreateSnapshotter() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_RELEASE_ASSERT(!mWeakSnapshotter,
 | |
|                      "No NativeLayerRootSnapshotter for this NativeLayerRoot "
 | |
|                      "should exist when this is called");
 | |
| 
 | |
|   auto cr = NativeLayerRootSnapshotterCA::Create(
 | |
|       this, mOffscreenRepresentation.mRootCALayer);
 | |
|   if (cr) {
 | |
|     mWeakSnapshotter = cr.get();
 | |
|   }
 | |
|   return cr;
 | |
| }
 | |
| 
 | |
| void NativeLayerRootCA::OnNativeLayerRootSnapshotterDestroyed(
 | |
|     NativeLayerRootSnapshotterCA* aNativeLayerRootSnapshotter) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_RELEASE_ASSERT(mWeakSnapshotter == aNativeLayerRootSnapshotter);
 | |
|   mWeakSnapshotter = nullptr;
 | |
| }
 | |
| 
 | |
| void NativeLayerRootCA::CommitOffscreen() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   mOffscreenRepresentation.Commit(WhichRepresentation::OFFSCREEN, mSublayers,
 | |
|                                   mWindowIsFullscreen);
 | |
| }
 | |
| 
 | |
| template <typename F>
 | |
| void NativeLayerRootCA::ForAllRepresentations(F aFn) {
 | |
|   aFn(mOnscreenRepresentation);
 | |
|   aFn(mOffscreenRepresentation);
 | |
| }
 | |
| 
 | |
| NativeLayerRootCA::Representation::Representation(CALayer* aRootCALayer)
 | |
|     : mRootCALayer([aRootCALayer retain]) {}
 | |
| 
 | |
| NativeLayerRootCA::Representation::~Representation() {
 | |
|   if (mMutatedLayerStructure) {
 | |
|     // Clear the root layer's sublayers. At this point the window is usually
 | |
|     // closed, so this transaction does not cause any screen updates.
 | |
|     AutoCATransaction transaction;
 | |
|     mRootCALayer.sublayers = @[];
 | |
|   }
 | |
| 
 | |
|   [mRootCALayer release];
 | |
| }
 | |
| 
 | |
| void NativeLayerRootCA::Representation::Commit(
 | |
|     WhichRepresentation aRepresentation,
 | |
|     const nsTArray<RefPtr<NativeLayerCA>>& aSublayers,
 | |
|     bool aWindowIsFullscreen) {
 | |
|   bool mustRebuild = mMutatedLayerStructure;
 | |
|   if (!mustRebuild) {
 | |
|     // Check which type of update we need to do, if any.
 | |
|     NativeLayerCA::UpdateType updateRequired = NativeLayerCA::UpdateType::None;
 | |
| 
 | |
|     for (auto layer : aSublayers) {
 | |
|       // Use the ordering of our UpdateType enums to build a maximal update
 | |
|       // type.
 | |
|       updateRequired =
 | |
|           std::max(updateRequired, layer->HasUpdate(aRepresentation));
 | |
|       if (updateRequired == NativeLayerCA::UpdateType::All) {
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (updateRequired == NativeLayerCA::UpdateType::None) {
 | |
|       // Nothing more needed, so early exit.
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (updateRequired == NativeLayerCA::UpdateType::OnlyVideo) {
 | |
|       bool allUpdatesSucceeded = std::all_of(
 | |
|           aSublayers.begin(), aSublayers.end(),
 | |
|           [=](const RefPtr<NativeLayerCA>& layer) {
 | |
|             return layer->ApplyChanges(aRepresentation,
 | |
|                                        NativeLayerCA::UpdateType::OnlyVideo);
 | |
|           });
 | |
| 
 | |
|       if (allUpdatesSucceeded) {
 | |
|         // Nothing more needed, so early exit;
 | |
|         return;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // We're going to do a full update now, which requires a transaction. Update
 | |
|   // all of the sublayers. Afterwards, only continue processing the sublayers
 | |
|   // which have an extent.
 | |
|   AutoCATransaction transaction;
 | |
|   nsTArray<NativeLayerCA*> sublayersWithExtent;
 | |
|   for (auto layer : aSublayers) {
 | |
|     mustRebuild |= layer->WillUpdateAffectLayers(aRepresentation);
 | |
|     layer->ApplyChanges(aRepresentation, NativeLayerCA::UpdateType::All);
 | |
|     CALayer* caLayer = layer->UnderlyingCALayer(aRepresentation);
 | |
|     if (!caLayer.masksToBounds || !NSIsEmptyRect(caLayer.bounds)) {
 | |
|       // This layer has an extent. If it didn't before, we need to rebuild.
 | |
|       mustRebuild |= !layer->HasExtent();
 | |
|       layer->SetHasExtent(true);
 | |
|       sublayersWithExtent.AppendElement(layer);
 | |
|     } else {
 | |
|       // This layer has no extent. If it did before, we need to rebuild.
 | |
|       mustRebuild |= layer->HasExtent();
 | |
|       layer->SetHasExtent(false);
 | |
|     }
 | |
| 
 | |
|     // One other reason we may need to rebuild is if the caLayer is not part of
 | |
|     // the root layer's sublayers. This might happen if the caLayer was rebuilt.
 | |
|     // We construct this check in a way that maximizes the boolean
 | |
|     // short-circuit, because we don't want to call containsObject unless
 | |
|     // absolutely necessary.
 | |
|     mustRebuild =
 | |
|         mustRebuild || ![mRootCALayer.sublayers containsObject:caLayer];
 | |
|   }
 | |
| 
 | |
|   if (mustRebuild) {
 | |
|     uint32_t sublayersCount = sublayersWithExtent.Length();
 | |
|     NSMutableArray<CALayer*>* sublayers =
 | |
|         [NSMutableArray arrayWithCapacity:sublayersCount];
 | |
|     for (auto layer : sublayersWithExtent) {
 | |
|       [sublayers addObject:layer->UnderlyingCALayer(aRepresentation)];
 | |
|     }
 | |
|     mRootCALayer.sublayers = sublayers;
 | |
|   }
 | |
| 
 | |
|   mMutatedLayerStructure = false;
 | |
| }
 | |
| 
 | |
| /* static */ UniquePtr<NativeLayerRootSnapshotterCA>
 | |
| NativeLayerRootSnapshotterCA::Create(NativeLayerRootCA* aLayerRoot,
 | |
|                                      CALayer* aRootCALayer) {
 | |
|   if (NS_IsMainThread()) {
 | |
|     // Disallow creating snapshotters on the main thread.
 | |
|     // On the main thread, any explicit CATransaction / NSAnimationContext is
 | |
|     // nested within a global implicit transaction. This makes it impossible to
 | |
|     // apply CALayer mutations synchronously such that they become visible to
 | |
|     // CARenderer. As a result, the snapshotter would not capture the right
 | |
|     // output on the main thread.
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCString failureUnused;
 | |
|   RefPtr<gl::GLContext> gl = gl::GLContextProvider::CreateHeadless(
 | |
|       {gl::CreateContextFlags::ALLOW_OFFLINE_RENDERER |
 | |
|        gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE},
 | |
|       &failureUnused);
 | |
|   if (!gl) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return UniquePtr<NativeLayerRootSnapshotterCA>(
 | |
|       new NativeLayerRootSnapshotterCA(aLayerRoot, std::move(gl),
 | |
|                                        aRootCALayer));
 | |
| }
 | |
| 
 | |
| void NativeLayerRootCA::DumpLayerTreeToFile(const char* aPath) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   NSLog(@"Dumping NativeLayer contents to %s", aPath);
 | |
|   std::ofstream fileOutput(aPath);
 | |
|   if (fileOutput.fail()) {
 | |
|     NSLog(@"Opening %s for writing failed.", aPath);
 | |
|   }
 | |
| 
 | |
|   // Make sure floating point values use a period for the decimal separator.
 | |
|   fileOutput.imbue(std::locale("C"));
 | |
| 
 | |
|   fileOutput << "<html>\n";
 | |
|   for (const auto& layer : mSublayers) {
 | |
|     layer->DumpLayer(fileOutput);
 | |
|   }
 | |
|   fileOutput << "</html>\n";
 | |
|   fileOutput.close();
 | |
| }
 | |
| 
 | |
| void NativeLayerRootCA::SetWindowIsFullscreen(bool aFullscreen) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   if (mWindowIsFullscreen != aFullscreen) {
 | |
|     mWindowIsFullscreen = aFullscreen;
 | |
| 
 | |
|     for (auto layer : mSublayers) {
 | |
|       layer->SetRootWindowIsFullscreen(mWindowIsFullscreen);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */ bool IsCGColorOpaqueBlack(CGColorRef aColor) {
 | |
|   if (CGColorEqualToColor(aColor, CGColorGetConstantColor(kCGColorBlack))) {
 | |
|     return true;
 | |
|   }
 | |
|   size_t componentCount = CGColorGetNumberOfComponents(aColor);
 | |
|   if (componentCount == 0) {
 | |
|     // This will happen if aColor is kCGColorClear. It's not opaque black.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   const CGFloat* components = CGColorGetComponents(aColor);
 | |
|   for (size_t c = 0; c < componentCount - 1; ++c) {
 | |
|     if (components[c] > 0.0f) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
|   return components[componentCount - 1] >= 1.0f;
 | |
| }
 | |
| 
 | |
| VideoLowPowerType NativeLayerRootCA::CheckVideoLowPower() {
 | |
|   // This deteremines whether the current layer contents qualify for the
 | |
|   // macOS Core Animation video low power mode. Those requirements are
 | |
|   // summarized at
 | |
|   // https://developer.apple.com/documentation/webkit/delivering_video_content_for_safari
 | |
|   // and we verify them by checking:
 | |
|   // 1) There must be exactly one video showing.
 | |
|   // 2) The topmost CALayer must be a AVSampleBufferDisplayLayer.
 | |
|   // 3) The video layer must be showing a buffer encoded in one of the
 | |
|   //    kCVPixelFormatType_420YpCbCr pixel formats.
 | |
|   // 4) The layer below that must cover the entire screen and have a black
 | |
|   //    background color.
 | |
|   // 5) The window must be fullscreen.
 | |
|   // This function checks these requirements empirically. If one of the checks
 | |
|   // fail, we either return immediately or do additional processing to
 | |
|   // determine more detail.
 | |
| 
 | |
|   uint32_t videoLayerCount = 0;
 | |
|   NativeLayerCA* topLayer = nullptr;
 | |
|   CALayer* topCALayer = nil;
 | |
|   CALayer* secondCALayer = nil;
 | |
|   bool topLayerIsVideo = false;
 | |
| 
 | |
|   for (auto layer : mSublayers) {
 | |
|     // Only layers with extent are contributing to our sublayers.
 | |
|     if (layer->HasExtent()) {
 | |
|       topLayer = layer;
 | |
| 
 | |
|       secondCALayer = topCALayer;
 | |
|       topCALayer = topLayer->UnderlyingCALayer(WhichRepresentation::ONSCREEN);
 | |
|       topLayerIsVideo = topLayer->IsVideo();
 | |
|       if (topLayerIsVideo) {
 | |
|         ++videoLayerCount;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (videoLayerCount == 0) {
 | |
|     return VideoLowPowerType::NotVideo;
 | |
|   }
 | |
| 
 | |
|   // Most importantly, check if the window is fullscreen. If the user is
 | |
|   // watching video in a window, then all of the other enums are irrelevant to
 | |
|   // achieving the low power mode.
 | |
|   if (!mWindowIsFullscreen) {
 | |
|     return VideoLowPowerType::FailWindowed;
 | |
|   }
 | |
| 
 | |
|   if (videoLayerCount > 1) {
 | |
|     return VideoLowPowerType::FailMultipleVideo;
 | |
|   }
 | |
| 
 | |
|   if (!topLayerIsVideo) {
 | |
|     return VideoLowPowerType::FailOverlaid;
 | |
|   }
 | |
| 
 | |
|   if (!secondCALayer || !IsCGColorOpaqueBlack(secondCALayer.backgroundColor) ||
 | |
|       !CGRectContainsRect(secondCALayer.frame,
 | |
|                           secondCALayer.superlayer.bounds)) {
 | |
|     return VideoLowPowerType::FailBacking;
 | |
|   }
 | |
| 
 | |
|   CALayer* topContentCALayer = topCALayer.sublayers[0];
 | |
|   if (![topContentCALayer isKindOfClass:[AVSampleBufferDisplayLayer class]]) {
 | |
|     // We didn't create a AVSampleBufferDisplayLayer for the top video layer.
 | |
|     // Try to figure out why by following some of the logic in
 | |
|     // NativeLayerCA::ShouldSpecializeVideo.
 | |
| 
 | |
|     if (!StaticPrefs::gfx_core_animation_specialize_video()) {
 | |
|       return VideoLowPowerType::FailPref;
 | |
|     }
 | |
| 
 | |
|     // The only remaining reason is that the surface wasn't eligible. We
 | |
|     // assert this instead of if-ing it, to ensure that we always have a
 | |
|     // return value from this clause.
 | |
| #ifdef DEBUG
 | |
|     MOZ_ASSERT(topLayer->mTextureHost);
 | |
|     MacIOSurface* macIOSurface = topLayer->mTextureHost->GetSurface();
 | |
|     CFTypeRefPtr<IOSurfaceRef> surface = macIOSurface->GetIOSurfaceRef();
 | |
|     OSType pixelFormat = IOSurfaceGetPixelFormat(surface.get());
 | |
|     MOZ_ASSERT(
 | |
|         !(pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange ||
 | |
|           pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
 | |
|           pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange ||
 | |
|           pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange));
 | |
| #endif
 | |
|     return VideoLowPowerType::FailSurface;
 | |
|   }
 | |
| 
 | |
|   AVSampleBufferDisplayLayer* topVideoLayer =
 | |
|       (AVSampleBufferDisplayLayer*)topContentCALayer;
 | |
|   if (topVideoLayer.status != AVQueuedSampleBufferRenderingStatusRendering) {
 | |
|     return VideoLowPowerType::FailEnqueue;
 | |
|   }
 | |
| 
 | |
|   // As best we can tell, we're eligible for video low power mode. Hurrah!
 | |
|   return VideoLowPowerType::LowPower;
 | |
| }
 | |
| 
 | |
| NativeLayerRootSnapshotterCA::NativeLayerRootSnapshotterCA(
 | |
|     NativeLayerRootCA* aLayerRoot, RefPtr<GLContext>&& aGL,
 | |
|     CALayer* aRootCALayer)
 | |
|     : mLayerRoot(aLayerRoot), mGL(aGL) {
 | |
|   AutoCATransaction transaction;
 | |
|   mRenderer = [[CARenderer
 | |
|       rendererWithCGLContext:gl::GLContextCGL::Cast(mGL)->GetCGLContext()
 | |
|                      options:nil] retain];
 | |
|   mRenderer.layer = aRootCALayer;
 | |
| }
 | |
| 
 | |
| NativeLayerRootSnapshotterCA::~NativeLayerRootSnapshotterCA() {
 | |
|   mLayerRoot->OnNativeLayerRootSnapshotterDestroyed(this);
 | |
|   [mRenderer release];
 | |
| }
 | |
| 
 | |
| already_AddRefed<profiler_screenshots::RenderSource>
 | |
| NativeLayerRootSnapshotterCA::GetWindowContents(const IntSize& aWindowSize) {
 | |
|   UpdateSnapshot(aWindowSize);
 | |
|   return do_AddRef(mSnapshot);
 | |
| }
 | |
| 
 | |
| void NativeLayerRootSnapshotterCA::UpdateSnapshot(const IntSize& aSize) {
 | |
|   CGRect bounds = CGRectMake(0, 0, aSize.width, aSize.height);
 | |
| 
 | |
|   {
 | |
|     // Set the correct bounds and scale on the renderer and its root layer.
 | |
|     // CARenderer always renders at unit scale, i.e. the coordinates on the root
 | |
|     // layer must map 1:1 to render target pixels. But the coordinates on our
 | |
|     // content layers are in "points", where 1 point maps to 2 device pixels on
 | |
|     // HiDPI. So in order to render at the full device pixel resolution, we set
 | |
|     // a scale transform on the root offscreen layer.
 | |
|     AutoCATransaction transaction;
 | |
|     mRenderer.layer.bounds = bounds;
 | |
|     float scale = mLayerRoot->BackingScale();
 | |
|     mRenderer.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1);
 | |
|     mRenderer.bounds = bounds;
 | |
|   }
 | |
| 
 | |
|   mLayerRoot->CommitOffscreen();
 | |
| 
 | |
|   mGL->MakeCurrent();
 | |
| 
 | |
|   bool needToRedrawEverything = false;
 | |
|   if (!mSnapshot || mSnapshot->Size() != aSize) {
 | |
|     mSnapshot = nullptr;
 | |
|     auto fb = gl::MozFramebuffer::Create(mGL, aSize, 0, false);
 | |
|     if (!fb) {
 | |
|       return;
 | |
|     }
 | |
|     mSnapshot = new RenderSourceNLRS(std::move(fb));
 | |
|     needToRedrawEverything = true;
 | |
|   }
 | |
| 
 | |
|   const gl::ScopedBindFramebuffer bindFB(mGL, mSnapshot->FB().mFB);
 | |
|   mGL->fViewport(0.0, 0.0, aSize.width, aSize.height);
 | |
| 
 | |
|   // These legacy OpenGL function calls are part of CARenderer's API contract,
 | |
|   // see CARenderer.h. The size passed to glOrtho must be the device pixel size
 | |
|   // of the render target, otherwise CARenderer will produce incorrect results.
 | |
|   glMatrixMode(GL_PROJECTION);
 | |
|   glLoadIdentity();
 | |
|   glOrtho(0.0, aSize.width, 0.0, aSize.height, -1, 1);
 | |
| 
 | |
|   float mediaTime = CACurrentMediaTime();
 | |
|   [mRenderer beginFrameAtTime:mediaTime timeStamp:nullptr];
 | |
|   if (needToRedrawEverything) {
 | |
|     [mRenderer addUpdateRect:bounds];
 | |
|   }
 | |
|   if (!CGRectIsEmpty([mRenderer updateBounds])) {
 | |
|     // CARenderer assumes the layer tree is opaque. It only ever paints over
 | |
|     // existing content, it never erases anything. However, our layer tree is
 | |
|     // not necessarily opaque. So we manually erase the area that's going to be
 | |
|     // redrawn. This ensures correct rendering in the transparent areas.
 | |
|     //
 | |
|     // Since we erase the bounds of the update area, this will erase more than
 | |
|     // necessary if the update area is not a single rectangle. Unfortunately we
 | |
|     // cannot get the precise update region from CARenderer, we can only get the
 | |
|     // bounds.
 | |
|     CGRect updateBounds = [mRenderer updateBounds];
 | |
|     gl::ScopedGLState scopedScissorTestState(mGL, LOCAL_GL_SCISSOR_TEST, true);
 | |
|     gl::ScopedScissorRect scissor(
 | |
|         mGL, updateBounds.origin.x, updateBounds.origin.y,
 | |
|         updateBounds.size.width, updateBounds.size.height);
 | |
|     mGL->fClearColor(0.0, 0.0, 0.0, 0.0);
 | |
|     mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
 | |
|     // We erased the update region's bounds. Make sure the entire update bounds
 | |
|     // get repainted.
 | |
|     [mRenderer addUpdateRect:updateBounds];
 | |
|   }
 | |
|   [mRenderer render];
 | |
|   [mRenderer endFrame];
 | |
| }
 | |
| 
 | |
| bool NativeLayerRootSnapshotterCA::ReadbackPixels(
 | |
|     const IntSize& aReadbackSize, SurfaceFormat aReadbackFormat,
 | |
|     const Range<uint8_t>& aReadbackBuffer) {
 | |
|   if (aReadbackFormat != SurfaceFormat::B8G8R8A8) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   UpdateSnapshot(aReadbackSize);
 | |
|   if (!mSnapshot) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   const gl::ScopedBindFramebuffer bindFB(mGL, mSnapshot->FB().mFB);
 | |
|   gl::ScopedPackState safePackState(mGL);
 | |
|   mGL->fReadPixels(0.0f, 0.0f, aReadbackSize.width, aReadbackSize.height,
 | |
|                    LOCAL_GL_BGRA, LOCAL_GL_UNSIGNED_BYTE, &aReadbackBuffer[0]);
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| already_AddRefed<profiler_screenshots::DownscaleTarget>
 | |
| NativeLayerRootSnapshotterCA::CreateDownscaleTarget(const IntSize& aSize) {
 | |
|   auto fb = gl::MozFramebuffer::Create(mGL, aSize, 0, false);
 | |
|   if (!fb) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   RefPtr<profiler_screenshots::DownscaleTarget> dt =
 | |
|       new DownscaleTargetNLRS(mGL, std::move(fb));
 | |
|   return dt.forget();
 | |
| }
 | |
| 
 | |
| already_AddRefed<profiler_screenshots::AsyncReadbackBuffer>
 | |
| NativeLayerRootSnapshotterCA::CreateAsyncReadbackBuffer(const IntSize& aSize) {
 | |
|   size_t bufferByteCount = aSize.width * aSize.height * 4;
 | |
|   GLuint bufferHandle = 0;
 | |
|   mGL->fGenBuffers(1, &bufferHandle);
 | |
| 
 | |
|   gl::ScopedPackState scopedPackState(mGL);
 | |
|   mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, bufferHandle);
 | |
|   mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
 | |
|   mGL->fBufferData(LOCAL_GL_PIXEL_PACK_BUFFER, bufferByteCount, nullptr,
 | |
|                    LOCAL_GL_STREAM_READ);
 | |
|   return MakeAndAddRef<AsyncReadbackBufferNLRS>(mGL, aSize, bufferHandle);
 | |
| }
 | |
| 
 | |
| NativeLayerCA::NativeLayerCA(const IntSize& aSize, bool aIsOpaque,
 | |
|                              SurfacePoolHandleCA* aSurfacePoolHandle)
 | |
|     : mMutex("NativeLayerCA"),
 | |
|       mSurfacePoolHandle(aSurfacePoolHandle),
 | |
|       mSize(aSize),
 | |
|       mIsOpaque(aIsOpaque) {
 | |
|   MOZ_RELEASE_ASSERT(mSurfacePoolHandle,
 | |
|                      "Need a non-null surface pool handle.");
 | |
| }
 | |
| 
 | |
| NativeLayerCA::NativeLayerCA(bool aIsOpaque)
 | |
|     : mMutex("NativeLayerCA"),
 | |
|       mSurfacePoolHandle(nullptr),
 | |
|       mIsOpaque(aIsOpaque) {
 | |
| #ifdef NIGHTLY_BUILD
 | |
|   if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|     NSLog(@"VIDEO_LOG: NativeLayerCA: %p is being created to host video, which "
 | |
|           @"will force a video "
 | |
|           @"layer rebuild.",
 | |
|           this);
 | |
|   }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| CGColorRef CGColorCreateForDeviceColor(gfx::DeviceColor aColor) {
 | |
|   if (StaticPrefs::gfx_color_management_native_srgb()) {
 | |
|     return CGColorCreateSRGB(aColor.r, aColor.g, aColor.b, aColor.a);
 | |
|   }
 | |
| 
 | |
|   return CGColorCreateGenericRGB(aColor.r, aColor.g, aColor.b, aColor.a);
 | |
| }
 | |
| 
 | |
| NativeLayerCA::NativeLayerCA(gfx::DeviceColor aColor)
 | |
|     : mMutex("NativeLayerCA"),
 | |
|       mSurfacePoolHandle(nullptr),
 | |
|       mIsOpaque(aColor.a >= 1.0f) {
 | |
|   MOZ_ASSERT(aColor.a > 0.0f, "Can't handle a fully transparent backdrop.");
 | |
|   mColor.AssignUnderCreateRule(CGColorCreateForDeviceColor(aColor));
 | |
| }
 | |
| 
 | |
| NativeLayerCA::~NativeLayerCA() {
 | |
| #ifdef NIGHTLY_BUILD
 | |
|   if (mHasEverAttachExternalImage &&
 | |
|       StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|     NSLog(@"VIDEO_LOG: ~NativeLayerCA: %p is being destroyed after hosting "
 | |
|           @"video.",
 | |
|           this);
 | |
|   }
 | |
| #endif
 | |
|   if (mInProgressLockedIOSurface) {
 | |
|     mInProgressLockedIOSurface->Unlock(false);
 | |
|     mInProgressLockedIOSurface = nullptr;
 | |
|   }
 | |
|   if (mInProgressSurface) {
 | |
|     IOSurfaceDecrementUseCount(mInProgressSurface->mSurface.get());
 | |
|     mSurfacePoolHandle->ReturnSurfaceToPool(mInProgressSurface->mSurface);
 | |
|   }
 | |
|   if (mFrontSurface) {
 | |
|     mSurfacePoolHandle->ReturnSurfaceToPool(mFrontSurface->mSurface);
 | |
|   }
 | |
|   for (const auto& surf : mSurfaces) {
 | |
|     mSurfacePoolHandle->ReturnSurfaceToPool(surf.mEntry.mSurface);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::AttachExternalImage(wr::RenderTextureHost* aExternalImage) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
| #ifdef NIGHTLY_BUILD
 | |
|   mHasEverAttachExternalImage = true;
 | |
|   MOZ_RELEASE_ASSERT(!mHasEverNotifySurfaceReady,
 | |
|                      "Shouldn't change layer type to external.");
 | |
| #endif
 | |
| 
 | |
|   wr::RenderMacIOSurfaceTextureHost* texture =
 | |
|       aExternalImage->AsRenderMacIOSurfaceTextureHost();
 | |
|   MOZ_ASSERT(texture);
 | |
|   mTextureHost = texture;
 | |
|   if (!mTextureHost) {
 | |
|     gfxCriticalNoteOnce << "ExternalImage is not RenderMacIOSurfaceTextureHost";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   gfx::IntSize oldSize = mSize;
 | |
|   mSize = texture->GetSize(0);
 | |
|   bool changedSizeAndDisplayRect = (mSize != oldSize);
 | |
| 
 | |
|   mDisplayRect = IntRect(IntPoint{}, mSize);
 | |
| 
 | |
|   bool oldSpecializeVideo = mSpecializeVideo;
 | |
|   mSpecializeVideo = ShouldSpecializeVideo(lock);
 | |
|   bool changedSpecializeVideo = (mSpecializeVideo != oldSpecializeVideo);
 | |
| #ifdef NIGHTLY_BUILD
 | |
|   if (changedSpecializeVideo &&
 | |
|       StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|     NSLog(
 | |
|         @"VIDEO_LOG: AttachExternalImage: %p is forcing a video layer rebuild.",
 | |
|         this);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   bool oldIsDRM = mIsDRM;
 | |
|   mIsDRM = aExternalImage->IsFromDRMSource();
 | |
|   bool changedIsDRM = (mIsDRM != oldIsDRM);
 | |
| 
 | |
|   ForAllRepresentations([&](Representation& r) {
 | |
|     r.mMutatedFrontSurface = true;
 | |
|     r.mMutatedDisplayRect |= changedSizeAndDisplayRect;
 | |
|     r.mMutatedSize |= changedSizeAndDisplayRect;
 | |
|     r.mMutatedSpecializeVideo |= changedSpecializeVideo;
 | |
|     r.mMutatedIsDRM |= changedIsDRM;
 | |
|   });
 | |
| }
 | |
| 
 | |
| bool NativeLayerCA::IsVideo() {
 | |
|   // Anything with a texture host is considered a video source.
 | |
|   return mTextureHost;
 | |
| }
 | |
| 
 | |
| bool NativeLayerCA::IsVideoAndLocked(const MutexAutoLock& aProofOfLock) {
 | |
|   // Anything with a texture host is considered a video source.
 | |
|   return mTextureHost;
 | |
| }
 | |
| 
 | |
| bool NativeLayerCA::ShouldSpecializeVideo(const MutexAutoLock& aProofOfLock) {
 | |
|   if (!IsVideoAndLocked(aProofOfLock)) {
 | |
|     // Only videos are eligible.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(mTextureHost);
 | |
| 
 | |
|   // DRM video is supported in macOS 10.15 and beyond, and such video must use
 | |
|   // a specialized video layer.
 | |
|   if (mTextureHost->IsFromDRMSource()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Beyond this point, we need to know about the format of the video.
 | |
|   MacIOSurface* macIOSurface = mTextureHost->GetSurface();
 | |
|   if (macIOSurface->GetYUVColorSpace() == gfx::YUVColorSpace::BT2020) {
 | |
|     // BT2020 is a signifier of HDR color space, whether or not the bit depth
 | |
|     // is expanded to cover that color space. This video needs a specialized
 | |
|     // video layer.
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   CFTypeRefPtr<IOSurfaceRef> surface = macIOSurface->GetIOSurfaceRef();
 | |
|   OSType pixelFormat = IOSurfaceGetPixelFormat(surface.get());
 | |
|   if (pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange ||
 | |
|       pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange) {
 | |
|     // HDR videos require specialized video layers.
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Beyond this point, we return true if-and-only-if we think we can achieve
 | |
|   // the power-saving "detached mode" of the macOS compositor.
 | |
| 
 | |
|   if (!StaticPrefs::gfx_core_animation_specialize_video()) {
 | |
|     // Pref must be set.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (pixelFormat != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange &&
 | |
|       pixelFormat != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
 | |
|     // The video is not in one of the formats that qualifies for detachment.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // It will only detach if we're fullscreen.
 | |
|   return mRootWindowIsFullscreen;
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::SetRootWindowIsFullscreen(bool aFullscreen) {
 | |
|   if (mRootWindowIsFullscreen == aFullscreen) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   mRootWindowIsFullscreen = aFullscreen;
 | |
| 
 | |
|   bool oldSpecializeVideo = mSpecializeVideo;
 | |
|   mSpecializeVideo = ShouldSpecializeVideo(lock);
 | |
|   bool changedSpecializeVideo = (mSpecializeVideo != oldSpecializeVideo);
 | |
| 
 | |
|   if (changedSpecializeVideo) {
 | |
| #ifdef NIGHTLY_BUILD
 | |
|     if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|       NSLog(@"VIDEO_LOG: SetRootWindowIsFullscreen: %p is forcing a video "
 | |
|             @"layer rebuild.",
 | |
|             this);
 | |
|     }
 | |
| #endif
 | |
| 
 | |
|     ForAllRepresentations(
 | |
|         [&](Representation& r) { r.mMutatedSpecializeVideo = true; });
 | |
|   }
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::SetSurfaceIsFlipped(bool aIsFlipped) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   if (aIsFlipped != mSurfaceIsFlipped) {
 | |
|     mSurfaceIsFlipped = aIsFlipped;
 | |
|     ForAllRepresentations(
 | |
|         [&](Representation& r) { r.mMutatedSurfaceIsFlipped = true; });
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool NativeLayerCA::SurfaceIsFlipped() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return mSurfaceIsFlipped;
 | |
| }
 | |
| 
 | |
| IntSize NativeLayerCA::GetSize() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return mSize;
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::SetPosition(const IntPoint& aPosition) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   if (aPosition != mPosition) {
 | |
|     mPosition = aPosition;
 | |
|     ForAllRepresentations(
 | |
|         [&](Representation& r) { r.mMutatedPosition = true; });
 | |
|   }
 | |
| }
 | |
| 
 | |
| IntPoint NativeLayerCA::GetPosition() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return mPosition;
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::SetTransform(const Matrix4x4& aTransform) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_ASSERT(aTransform.IsRectilinear());
 | |
| 
 | |
|   if (aTransform != mTransform) {
 | |
|     mTransform = aTransform;
 | |
|     ForAllRepresentations(
 | |
|         [&](Representation& r) { r.mMutatedTransform = true; });
 | |
|   }
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::SetSamplingFilter(gfx::SamplingFilter aSamplingFilter) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   if (aSamplingFilter != mSamplingFilter) {
 | |
|     mSamplingFilter = aSamplingFilter;
 | |
|     ForAllRepresentations(
 | |
|         [&](Representation& r) { r.mMutatedSamplingFilter = true; });
 | |
|   }
 | |
| }
 | |
| 
 | |
| Matrix4x4 NativeLayerCA::GetTransform() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return mTransform;
 | |
| }
 | |
| 
 | |
| IntRect NativeLayerCA::GetRect() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return IntRect(mPosition, mSize);
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::SetBackingScale(float aBackingScale) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   if (aBackingScale != mBackingScale) {
 | |
|     mBackingScale = aBackingScale;
 | |
|     ForAllRepresentations(
 | |
|         [&](Representation& r) { r.mMutatedBackingScale = true; });
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool NativeLayerCA::IsOpaque() {
 | |
|   // mIsOpaque is const, so no need for a lock.
 | |
|   return mIsOpaque;
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::SetClipRect(const Maybe<gfx::IntRect>& aClipRect) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   if (aClipRect != mClipRect) {
 | |
|     mClipRect = aClipRect;
 | |
|     ForAllRepresentations(
 | |
|         [&](Representation& r) { r.mMutatedClipRect = true; });
 | |
|   }
 | |
| }
 | |
| 
 | |
| Maybe<gfx::IntRect> NativeLayerCA::ClipRect() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return mClipRect;
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::DumpLayer(std::ostream& aOutputStream) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   Maybe<CGRect> scaledClipRect = CalculateClipGeometry(
 | |
|       mSize, mPosition, mTransform, mDisplayRect, mClipRect, mBackingScale);
 | |
| 
 | |
|   CGRect useClipRect;
 | |
|   if (scaledClipRect.isSome()) {
 | |
|     useClipRect = *scaledClipRect;
 | |
|   } else {
 | |
|     useClipRect = CGRectZero;
 | |
|   }
 | |
| 
 | |
|   aOutputStream << "<div style=\"";
 | |
|   aOutputStream << "position: absolute; ";
 | |
|   aOutputStream << "left: " << useClipRect.origin.x << "px; ";
 | |
|   aOutputStream << "top: " << useClipRect.origin.y << "px; ";
 | |
|   aOutputStream << "width: " << useClipRect.size.width << "px; ";
 | |
|   aOutputStream << "height: " << useClipRect.size.height << "px; ";
 | |
| 
 | |
|   if (scaledClipRect.isSome()) {
 | |
|     aOutputStream << "overflow: hidden; ";
 | |
|   }
 | |
| 
 | |
|   if (mColor) {
 | |
|     const CGFloat* components = CGColorGetComponents(mColor.get());
 | |
|     aOutputStream << "background: rgb(" << components[0] * 255.0f << " "
 | |
|                   << components[1] * 255.0f << " " << components[2] * 255.0f
 | |
|                   << "); opacity: " << components[3] << "; ";
 | |
| 
 | |
|     // That's all we need for color layers. We don't need to specify an image.
 | |
|     aOutputStream << "\"/></div>\n";
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   aOutputStream << "\">";
 | |
| 
 | |
|   auto size = gfx::Size(mSize) / mBackingScale;
 | |
| 
 | |
|   aOutputStream << "<img style=\"";
 | |
|   aOutputStream << "width: " << size.width << "px; ";
 | |
|   aOutputStream << "height: " << size.height << "px; ";
 | |
| 
 | |
|   if (mSamplingFilter == gfx::SamplingFilter::POINT) {
 | |
|     aOutputStream << "image-rendering: crisp-edges; ";
 | |
|   }
 | |
| 
 | |
|   Matrix4x4 transform = mTransform;
 | |
|   transform.PreTranslate(mPosition.x, mPosition.y, 0);
 | |
|   transform.PostTranslate((-useClipRect.origin.x * mBackingScale),
 | |
|                           (-useClipRect.origin.y * mBackingScale), 0);
 | |
| 
 | |
|   if (mSurfaceIsFlipped) {
 | |
|     transform.PreTranslate(0, mSize.height, 0).PreScale(1, -1, 1);
 | |
|   }
 | |
| 
 | |
|   if (!transform.IsIdentity()) {
 | |
|     const auto& m = transform;
 | |
|     aOutputStream << "transform-origin: top left; ";
 | |
|     aOutputStream << "transform: matrix3d(";
 | |
|     aOutputStream << m._11 << ", " << m._12 << ", " << m._13 << ", " << m._14
 | |
|                   << ", ";
 | |
|     aOutputStream << m._21 << ", " << m._22 << ", " << m._23 << ", " << m._24
 | |
|                   << ", ";
 | |
|     aOutputStream << m._31 << ", " << m._32 << ", " << m._33 << ", " << m._34
 | |
|                   << ", ";
 | |
|     aOutputStream << m._41 / mBackingScale << ", " << m._42 / mBackingScale
 | |
|                   << ", " << m._43 << ", " << m._44;
 | |
|     aOutputStream << "); ";
 | |
|   }
 | |
|   aOutputStream << "\" ";
 | |
| 
 | |
|   CFTypeRefPtr<IOSurfaceRef> surface;
 | |
|   if (mFrontSurface) {
 | |
|     surface = mFrontSurface->mSurface;
 | |
|     aOutputStream << "alt=\"regular surface 0x" << std::hex
 | |
|                   << int(IOSurfaceGetID(surface.get())) << "\" ";
 | |
|   } else if (mTextureHost) {
 | |
|     surface = mTextureHost->GetSurface()->GetIOSurfaceRef();
 | |
|     aOutputStream << "alt=\"TextureHost surface 0x" << std::hex
 | |
|                   << int(IOSurfaceGetID(surface.get())) << "\" ";
 | |
|   } else {
 | |
|     aOutputStream << "alt=\"no surface 0x\" ";
 | |
|   }
 | |
| 
 | |
|   aOutputStream << "src=\"";
 | |
| 
 | |
|   if (surface) {
 | |
|     // Attempt to render the surface as a PNG. Skia can do this for RGB
 | |
|     // surfaces.
 | |
|     RefPtr<MacIOSurface> surf = new MacIOSurface(surface);
 | |
|     surf->Lock(true);
 | |
|     SurfaceFormat format = surf->GetFormat();
 | |
|     if (format == SurfaceFormat::B8G8R8A8 ||
 | |
|         format == SurfaceFormat::B8G8R8X8) {
 | |
|       RefPtr<gfx::DrawTarget> dt =
 | |
|           surf->GetAsDrawTargetLocked(gfx::BackendType::SKIA);
 | |
|       if (dt) {
 | |
|         RefPtr<gfx::SourceSurface> sourceSurf = dt->Snapshot();
 | |
|         nsCString dataUrl;
 | |
|         gfxUtils::EncodeSourceSurface(sourceSurf, ImageType::PNG, u""_ns,
 | |
|                                       gfxUtils::eDataURIEncode, nullptr,
 | |
|                                       &dataUrl);
 | |
|         aOutputStream << dataUrl.get();
 | |
|       }
 | |
|     }
 | |
|     surf->Unlock(true);
 | |
|   }
 | |
| 
 | |
|   aOutputStream << "\"/></div>\n";
 | |
| }
 | |
| 
 | |
| gfx::IntRect NativeLayerCA::CurrentSurfaceDisplayRect() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return mDisplayRect;
 | |
| }
 | |
| 
 | |
| NativeLayerCA::Representation::Representation()
 | |
|     : mMutatedPosition(true),
 | |
|       mMutatedTransform(true),
 | |
|       mMutatedDisplayRect(true),
 | |
|       mMutatedClipRect(true),
 | |
|       mMutatedBackingScale(true),
 | |
|       mMutatedSize(true),
 | |
|       mMutatedSurfaceIsFlipped(true),
 | |
|       mMutatedFrontSurface(true),
 | |
|       mMutatedSamplingFilter(true),
 | |
|       mMutatedSpecializeVideo(true),
 | |
|       mMutatedIsDRM(true) {}
 | |
| 
 | |
| NativeLayerCA::Representation::~Representation() {
 | |
|   [mContentCALayer release];
 | |
|   [mOpaquenessTintLayer release];
 | |
|   [mWrappingCALayer release];
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::InvalidateRegionThroughoutSwapchain(
 | |
|     const MutexAutoLock& aProofOfLock, const IntRegion& aRegion) {
 | |
|   IntRegion r = aRegion;
 | |
|   if (mInProgressSurface) {
 | |
|     mInProgressSurface->mInvalidRegion.OrWith(r);
 | |
|   }
 | |
|   if (mFrontSurface) {
 | |
|     mFrontSurface->mInvalidRegion.OrWith(r);
 | |
|   }
 | |
|   for (auto& surf : mSurfaces) {
 | |
|     surf.mEntry.mInvalidRegion.OrWith(r);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool NativeLayerCA::NextSurface(const MutexAutoLock& aProofOfLock) {
 | |
|   if (mSize.IsEmpty()) {
 | |
|     gfxCriticalError()
 | |
|         << "NextSurface returning false because of invalid mSize ("
 | |
|         << mSize.width << ", " << mSize.height << ").";
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   MOZ_RELEASE_ASSERT(!mInProgressSurface,
 | |
|                      "ERROR: Do not call NextSurface twice in sequence. Call "
 | |
|                      "NotifySurfaceReady before the "
 | |
|                      "next call to NextSurface.");
 | |
| 
 | |
|   Maybe<SurfaceWithInvalidRegion> surf =
 | |
|       GetUnusedSurfaceAndCleanUp(aProofOfLock);
 | |
|   if (!surf) {
 | |
|     CFTypeRefPtr<IOSurfaceRef> newSurf =
 | |
|         mSurfacePoolHandle->ObtainSurfaceFromPool(mSize);
 | |
|     MOZ_RELEASE_ASSERT(
 | |
|         newSurf, "NextSurface IOSurfaceCreate failed to create the surface.");
 | |
|     surf = Some(SurfaceWithInvalidRegion{newSurf, IntRect({}, mSize)});
 | |
|   }
 | |
| 
 | |
|   mInProgressSurface = std::move(surf);
 | |
|   IOSurfaceIncrementUseCount(mInProgressSurface->mSurface.get());
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| template <typename F>
 | |
| void NativeLayerCA::HandlePartialUpdate(const MutexAutoLock& aProofOfLock,
 | |
|                                         const IntRect& aDisplayRect,
 | |
|                                         const IntRegion& aUpdateRegion,
 | |
|                                         F&& aCopyFn) {
 | |
|   MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aUpdateRegion.GetBounds()),
 | |
|                      "The update region should be within the surface bounds.");
 | |
|   MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aDisplayRect),
 | |
|                      "The display rect should be within the surface bounds.");
 | |
| 
 | |
|   MOZ_RELEASE_ASSERT(!mInProgressUpdateRegion);
 | |
|   MOZ_RELEASE_ASSERT(!mInProgressDisplayRect);
 | |
| 
 | |
|   mInProgressUpdateRegion = Some(aUpdateRegion);
 | |
|   mInProgressDisplayRect = Some(aDisplayRect);
 | |
| 
 | |
|   if (mFrontSurface) {
 | |
|     // Copy not-overwritten valid content from mFrontSurface so that valid
 | |
|     // content never gets lost.
 | |
|     gfx::IntRegion copyRegion;
 | |
|     copyRegion.Sub(mInProgressSurface->mInvalidRegion, aUpdateRegion);
 | |
|     copyRegion.SubOut(mFrontSurface->mInvalidRegion);
 | |
| 
 | |
|     if (!copyRegion.IsEmpty()) {
 | |
|       // Now copy the valid content, using a caller-provided copy function.
 | |
|       aCopyFn(mFrontSurface->mSurface, copyRegion);
 | |
|       mInProgressSurface->mInvalidRegion.SubOut(copyRegion);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   InvalidateRegionThroughoutSwapchain(aProofOfLock, aUpdateRegion);
 | |
| }
 | |
| 
 | |
| RefPtr<gfx::DrawTarget> NativeLayerCA::NextSurfaceAsDrawTarget(
 | |
|     const IntRect& aDisplayRect, const IntRegion& aUpdateRegion,
 | |
|     gfx::BackendType aBackendType) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   if (!NextSurface(lock)) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   mInProgressLockedIOSurface = new MacIOSurface(mInProgressSurface->mSurface);
 | |
|   mInProgressLockedIOSurface->Lock(false);
 | |
|   RefPtr<gfx::DrawTarget> dt =
 | |
|       mInProgressLockedIOSurface->GetAsDrawTargetLocked(aBackendType);
 | |
| 
 | |
|   HandlePartialUpdate(
 | |
|       lock, aDisplayRect, aUpdateRegion,
 | |
|       [&](CFTypeRefPtr<IOSurfaceRef> validSource,
 | |
|           const gfx::IntRegion& copyRegion) {
 | |
|         RefPtr<MacIOSurface> source = new MacIOSurface(validSource);
 | |
|         source->Lock(true);
 | |
|         {
 | |
|           RefPtr<gfx::DrawTarget> sourceDT =
 | |
|               source->GetAsDrawTargetLocked(aBackendType);
 | |
|           RefPtr<gfx::SourceSurface> sourceSurface = sourceDT->Snapshot();
 | |
| 
 | |
|           for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
 | |
|             const gfx::IntRect& r = iter.Get();
 | |
|             dt->CopySurface(sourceSurface, r, r.TopLeft());
 | |
|           }
 | |
|         }
 | |
|         source->Unlock(true);
 | |
|       });
 | |
| 
 | |
|   return dt;
 | |
| }
 | |
| 
 | |
| Maybe<GLuint> NativeLayerCA::NextSurfaceAsFramebuffer(
 | |
|     const IntRect& aDisplayRect, const IntRegion& aUpdateRegion,
 | |
|     bool aNeedsDepth) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   MOZ_RELEASE_ASSERT(NextSurface(lock),
 | |
|                      "NextSurfaceAsFramebuffer needs a surface.");
 | |
| 
 | |
|   Maybe<GLuint> fbo = mSurfacePoolHandle->GetFramebufferForSurface(
 | |
|       mInProgressSurface->mSurface, aNeedsDepth);
 | |
|   MOZ_RELEASE_ASSERT(fbo, "GetFramebufferForSurface failed.");
 | |
| 
 | |
|   HandlePartialUpdate(
 | |
|       lock, aDisplayRect, aUpdateRegion,
 | |
|       [&](CFTypeRefPtr<IOSurfaceRef> validSource,
 | |
|           const gfx::IntRegion& copyRegion) {
 | |
|         // Copy copyRegion from validSource to fbo.
 | |
|         MOZ_RELEASE_ASSERT(mSurfacePoolHandle->gl());
 | |
|         mSurfacePoolHandle->gl()->MakeCurrent();
 | |
|         Maybe<GLuint> sourceFBO =
 | |
|             mSurfacePoolHandle->GetFramebufferForSurface(validSource, false);
 | |
|         MOZ_RELEASE_ASSERT(
 | |
|             sourceFBO,
 | |
|             "GetFramebufferForSurface failed during HandlePartialUpdate.");
 | |
|         for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
 | |
|           gfx::IntRect r = iter.Get();
 | |
|           if (mSurfaceIsFlipped) {
 | |
|             r.y = mSize.height - r.YMost();
 | |
|           }
 | |
|           mSurfacePoolHandle->gl()->BlitHelper()->BlitFramebufferToFramebuffer(
 | |
|               *sourceFBO, *fbo, r, r, LOCAL_GL_NEAREST);
 | |
|         }
 | |
|       });
 | |
| 
 | |
|   return fbo;
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::NotifySurfaceReady() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
| #ifdef NIGHTLY_BUILD
 | |
|   mHasEverNotifySurfaceReady = true;
 | |
|   MOZ_RELEASE_ASSERT(!mHasEverAttachExternalImage,
 | |
|                      "Shouldn't change layer type to drawn.");
 | |
| #endif
 | |
| 
 | |
|   MOZ_RELEASE_ASSERT(
 | |
|       mInProgressSurface,
 | |
|       "NotifySurfaceReady called without preceding call to NextSurface");
 | |
| 
 | |
|   if (mInProgressLockedIOSurface) {
 | |
|     mInProgressLockedIOSurface->Unlock(false);
 | |
|     mInProgressLockedIOSurface = nullptr;
 | |
|   }
 | |
| 
 | |
|   if (mFrontSurface) {
 | |
|     mSurfaces.push_back({*mFrontSurface, 0});
 | |
|     mFrontSurface = Nothing();
 | |
|   }
 | |
| 
 | |
|   MOZ_RELEASE_ASSERT(mInProgressUpdateRegion);
 | |
|   IOSurfaceDecrementUseCount(mInProgressSurface->mSurface.get());
 | |
|   mFrontSurface = std::move(mInProgressSurface);
 | |
|   mFrontSurface->mInvalidRegion.SubOut(mInProgressUpdateRegion.extract());
 | |
| 
 | |
|   ForAllRepresentations(
 | |
|       [&](Representation& r) { r.mMutatedFrontSurface = true; });
 | |
| 
 | |
|   MOZ_RELEASE_ASSERT(mInProgressDisplayRect);
 | |
|   if (!mDisplayRect.IsEqualInterior(*mInProgressDisplayRect)) {
 | |
|     mDisplayRect = *mInProgressDisplayRect;
 | |
|     ForAllRepresentations(
 | |
|         [&](Representation& r) { r.mMutatedDisplayRect = true; });
 | |
|   }
 | |
|   mInProgressDisplayRect = Nothing();
 | |
| }
 | |
| 
 | |
| void NativeLayerCA::DiscardBackbuffers() {
 | |
|   MutexAutoLock lock(mMutex);
 | |
| 
 | |
|   for (const auto& surf : mSurfaces) {
 | |
|     mSurfacePoolHandle->ReturnSurfaceToPool(surf.mEntry.mSurface);
 | |
|   }
 | |
|   mSurfaces.clear();
 | |
| }
 | |
| 
 | |
| NativeLayerCA::Representation& NativeLayerCA::GetRepresentation(
 | |
|     WhichRepresentation aRepresentation) {
 | |
|   switch (aRepresentation) {
 | |
|     case WhichRepresentation::ONSCREEN:
 | |
|       return mOnscreenRepresentation;
 | |
|     case WhichRepresentation::OFFSCREEN:
 | |
|       return mOffscreenRepresentation;
 | |
|   }
 | |
| }
 | |
| 
 | |
| template <typename F>
 | |
| void NativeLayerCA::ForAllRepresentations(F aFn) {
 | |
|   aFn(mOnscreenRepresentation);
 | |
|   aFn(mOffscreenRepresentation);
 | |
| }
 | |
| 
 | |
| NativeLayerCA::UpdateType NativeLayerCA::HasUpdate(
 | |
|     WhichRepresentation aRepresentation) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return GetRepresentation(aRepresentation).HasUpdate(IsVideoAndLocked(lock));
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| Maybe<CGRect> NativeLayerCA::CalculateClipGeometry(
 | |
|     const gfx::IntSize& aSize, const gfx::IntPoint& aPosition,
 | |
|     const gfx::Matrix4x4& aTransform, const gfx::IntRect& aDisplayRect,
 | |
|     const Maybe<gfx::IntRect>& aClipRect, float aBackingScale) {
 | |
|   Maybe<IntRect> clipFromDisplayRect;
 | |
|   if (!aDisplayRect.IsEqualInterior(IntRect({}, aSize))) {
 | |
|     // When the display rect is a subset of the layer, then we want to guarantee
 | |
|     // that no pixels outside that rect are sampled, since they might be
 | |
|     // uninitialized. Transforming the display rect into a post-transform clip
 | |
|     // only maintains this if it's an integer translation, which is all we
 | |
|     // support for this case currently.
 | |
|     MOZ_ASSERT(aTransform.Is2DIntegerTranslation());
 | |
|     clipFromDisplayRect = Some(RoundedToInt(
 | |
|         aTransform.TransformBounds(IntRectToRect(aDisplayRect + aPosition))));
 | |
|   }
 | |
| 
 | |
|   Maybe<gfx::IntRect> effectiveClip =
 | |
|       IntersectMaybeRects(aClipRect, clipFromDisplayRect);
 | |
|   if (!effectiveClip) {
 | |
|     return Nothing();
 | |
|   }
 | |
| 
 | |
|   return Some(CGRectMake(effectiveClip->X() / aBackingScale,
 | |
|                          effectiveClip->Y() / aBackingScale,
 | |
|                          effectiveClip->Width() / aBackingScale,
 | |
|                          effectiveClip->Height() / aBackingScale));
 | |
| }
 | |
| 
 | |
| bool NativeLayerCA::ApplyChanges(WhichRepresentation aRepresentation,
 | |
|                                  NativeLayerCA::UpdateType aUpdate) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   CFTypeRefPtr<IOSurfaceRef> surface;
 | |
|   if (mFrontSurface) {
 | |
|     surface = mFrontSurface->mSurface;
 | |
|   } else if (mTextureHost) {
 | |
|     surface = mTextureHost->GetSurface()->GetIOSurfaceRef();
 | |
|   }
 | |
|   return GetRepresentation(aRepresentation)
 | |
|       .ApplyChanges(aUpdate, mSize, mIsOpaque, mPosition, mTransform,
 | |
|                     mDisplayRect, mClipRect, mBackingScale, mSurfaceIsFlipped,
 | |
|                     mSamplingFilter, mSpecializeVideo, surface, mColor, mIsDRM,
 | |
|                     IsVideo());
 | |
| }
 | |
| 
 | |
| CALayer* NativeLayerCA::UnderlyingCALayer(WhichRepresentation aRepresentation) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   return GetRepresentation(aRepresentation).UnderlyingCALayer();
 | |
| }
 | |
| 
 | |
| static NSString* NSStringForOSType(OSType type) {
 | |
|   unichar c[4];
 | |
|   c[0] = (type >> 24) & 0xFF;
 | |
|   c[1] = (type >> 16) & 0xFF;
 | |
|   c[2] = (type >> 8) & 0xFF;
 | |
|   c[3] = (type >> 0) & 0xFF;
 | |
|   NSString* string = [[NSString stringWithCharacters:c length:4] autorelease];
 | |
|   return string;
 | |
| }
 | |
| 
 | |
| /* static */ void LogSurface(IOSurfaceRef aSurfaceRef, CVPixelBufferRef aBuffer,
 | |
|                              CMVideoFormatDescriptionRef aFormat) {
 | |
|   NSLog(@"VIDEO_LOG: LogSurface...\n");
 | |
| 
 | |
|   CFDictionaryRef surfaceValues = IOSurfaceCopyAllValues(aSurfaceRef);
 | |
|   NSLog(@"Surface values are %@.\n", surfaceValues);
 | |
|   CFRelease(surfaceValues);
 | |
| 
 | |
|   if (aBuffer) {
 | |
|     CGColorSpaceRef colorSpace = CVImageBufferGetColorSpace(aBuffer);
 | |
|     NSLog(@"ColorSpace is %@.\n", colorSpace);
 | |
| 
 | |
|     CFDictionaryRef bufferAttachments =
 | |
|         CVBufferGetAttachments(aBuffer, kCVAttachmentMode_ShouldPropagate);
 | |
|     NSLog(@"Buffer attachments are %@.\n", bufferAttachments);
 | |
|   }
 | |
| 
 | |
|   if (aFormat) {
 | |
|     OSType codec = CMFormatDescriptionGetMediaSubType(aFormat);
 | |
|     NSLog(@"Codec is %@.\n", NSStringForOSType(codec));
 | |
| 
 | |
|     CFDictionaryRef extensions = CMFormatDescriptionGetExtensions(aFormat);
 | |
|     NSLog(@"Format extensions are %@.\n", extensions);
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool NativeLayerCA::Representation::EnqueueSurface(IOSurfaceRef aSurfaceRef) {
 | |
|   MOZ_ASSERT(
 | |
|       [mContentCALayer isKindOfClass:[AVSampleBufferDisplayLayer class]]);
 | |
|   AVSampleBufferDisplayLayer* videoLayer =
 | |
|       (AVSampleBufferDisplayLayer*)mContentCALayer;
 | |
| 
 | |
|   if (@available(macOS 11.0, iOS 14.0, *)) {
 | |
|     if (videoLayer.requiresFlushToResumeDecoding) {
 | |
|       [videoLayer flush];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If the layer can't handle a new sample, early exit.
 | |
|   if (!videoLayer.readyForMoreMediaData) {
 | |
| #ifdef NIGHTLY_BUILD
 | |
|     if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|       NSLog(@"VIDEO_LOG: EnqueueSurface failed on readyForMoreMediaData.");
 | |
|     }
 | |
| #endif
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Convert the IOSurfaceRef into a CMSampleBuffer, so we can enqueue it in
 | |
|   // mContentCALayer
 | |
|   CVPixelBufferRef pixelBuffer = nullptr;
 | |
|   CVReturn cvValue = CVPixelBufferCreateWithIOSurface(
 | |
|       kCFAllocatorDefault, aSurfaceRef, nullptr, &pixelBuffer);
 | |
|   if (cvValue != kCVReturnSuccess) {
 | |
|     MOZ_ASSERT(pixelBuffer == nullptr,
 | |
|                "Failed call shouldn't allocate memory.");
 | |
| #ifdef NIGHTLY_BUILD
 | |
|     if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|       NSLog(@"VIDEO_LOG: EnqueueSurface failed on allocating pixel buffer.");
 | |
|     }
 | |
| #endif
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
| #ifdef NIGHTLY_BUILD
 | |
|   if (StaticPrefs::gfx_core_animation_specialize_video_check_color_space()) {
 | |
|     // Ensure the resulting pixel buffer has a color space. If it doesn't, then
 | |
|     // modify the surface and create the buffer again.
 | |
|     CFTypeRefPtr<CGColorSpaceRef> colorSpace =
 | |
|         CFTypeRefPtr<CGColorSpaceRef>::WrapUnderGetRule(
 | |
|             CVImageBufferGetColorSpace(pixelBuffer));
 | |
|     if (!colorSpace) {
 | |
|       // Use our main display color space.
 | |
|       colorSpace = CFTypeRefPtr<CGColorSpaceRef>::WrapUnderCreateRule(
 | |
|           CGDisplayCopyColorSpace(CGMainDisplayID()));
 | |
|       auto colorData = CFTypeRefPtr<CFDataRef>::WrapUnderCreateRule(
 | |
|           CGColorSpaceCopyICCData(colorSpace.get()));
 | |
|       IOSurfaceSetValue(aSurfaceRef, CFSTR("IOSurfaceColorSpace"),
 | |
|                         colorData.get());
 | |
| 
 | |
|       // Get rid of our old pixel buffer and create a new one.
 | |
|       CFRelease(pixelBuffer);
 | |
|       cvValue = CVPixelBufferCreateWithIOSurface(
 | |
|           kCFAllocatorDefault, aSurfaceRef, nullptr, &pixelBuffer);
 | |
|       if (cvValue != kCVReturnSuccess) {
 | |
|         MOZ_ASSERT(pixelBuffer == nullptr,
 | |
|                    "Failed call shouldn't allocate memory.");
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
|     MOZ_ASSERT(CVImageBufferGetColorSpace(pixelBuffer),
 | |
|                "Pixel buffer should have a color space.");
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   CFTypeRefPtr<CVPixelBufferRef> pixelBufferDeallocator =
 | |
|       CFTypeRefPtr<CVPixelBufferRef>::WrapUnderCreateRule(pixelBuffer);
 | |
| 
 | |
|   CMVideoFormatDescriptionRef formatDescription = nullptr;
 | |
|   OSStatus osValue = CMVideoFormatDescriptionCreateForImageBuffer(
 | |
|       kCFAllocatorDefault, pixelBuffer, &formatDescription);
 | |
|   if (osValue != noErr) {
 | |
|     MOZ_ASSERT(formatDescription == nullptr,
 | |
|                "Failed call shouldn't allocate memory.");
 | |
| #ifdef NIGHTLY_BUILD
 | |
|     if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|       NSLog(@"VIDEO_LOG: EnqueueSurface failed on allocating format "
 | |
|             @"description.");
 | |
|     }
 | |
| #endif
 | |
|     return false;
 | |
|   }
 | |
|   CFTypeRefPtr<CMVideoFormatDescriptionRef> formatDescriptionDeallocator =
 | |
|       CFTypeRefPtr<CMVideoFormatDescriptionRef>::WrapUnderCreateRule(
 | |
|           formatDescription);
 | |
| 
 | |
| #ifdef NIGHTLY_BUILD
 | |
|   if (mLogNextVideoSurface &&
 | |
|       StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|     LogSurface(aSurfaceRef, pixelBuffer, formatDescription);
 | |
|     mLogNextVideoSurface = false;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   CMSampleTimingInfo timingInfo = kCMTimingInfoInvalid;
 | |
| 
 | |
|   bool spoofTiming = false;
 | |
| #ifdef NIGHTLY_BUILD
 | |
|   spoofTiming = StaticPrefs::gfx_core_animation_specialize_video_spoof_timing();
 | |
| #endif
 | |
|   if (spoofTiming) {
 | |
|     // Since we don't have timing information for the sample, set the sample to
 | |
|     // play at the current timestamp.
 | |
|     CMTimebaseRef timebase =
 | |
|         [(AVSampleBufferDisplayLayer*)mContentCALayer controlTimebase];
 | |
|     CMTime nowTime = CMTimebaseGetTime(timebase);
 | |
|     timingInfo = {.presentationTimeStamp = nowTime};
 | |
|   }
 | |
| 
 | |
|   CMSampleBufferRef sampleBuffer = nullptr;
 | |
|   osValue = CMSampleBufferCreateReadyWithImageBuffer(
 | |
|       kCFAllocatorDefault, pixelBuffer, formatDescription, &timingInfo,
 | |
|       &sampleBuffer);
 | |
|   if (osValue != noErr) {
 | |
|     MOZ_ASSERT(sampleBuffer == nullptr,
 | |
|                "Failed call shouldn't allocate memory.");
 | |
| #ifdef NIGHTLY_BUILD
 | |
|     if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|       NSLog(@"VIDEO_LOG: EnqueueSurface failed on allocating sample buffer.");
 | |
|     }
 | |
| #endif
 | |
|     return false;
 | |
|   }
 | |
|   CFTypeRefPtr<CMSampleBufferRef> sampleBufferDeallocator =
 | |
|       CFTypeRefPtr<CMSampleBufferRef>::WrapUnderCreateRule(sampleBuffer);
 | |
| 
 | |
|   if (!spoofTiming) {
 | |
|     // Since we don't have timing information for the sample, before we enqueue
 | |
|     // it, we attach an attribute that specifies that the sample should be
 | |
|     // played immediately.
 | |
|     CFArrayRef attachmentsArray =
 | |
|         CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
 | |
|     if (!attachmentsArray || CFArrayGetCount(attachmentsArray) == 0) {
 | |
|       // No dictionary to alter.
 | |
|       return false;
 | |
|     }
 | |
|     CFMutableDictionaryRef sample0Dictionary =
 | |
|         (__bridge CFMutableDictionaryRef)CFArrayGetValueAtIndex(
 | |
|             attachmentsArray, 0);
 | |
|     CFDictionarySetValue(sample0Dictionary,
 | |
|                          kCMSampleAttachmentKey_DisplayImmediately,
 | |
|                          kCFBooleanTrue);
 | |
|   }
 | |
| 
 | |
|   [videoLayer enqueueSampleBuffer:sampleBuffer];
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool NativeLayerCA::Representation::ApplyChanges(
 | |
|     NativeLayerCA::UpdateType aUpdate, const IntSize& aSize, bool aIsOpaque,
 | |
|     const IntPoint& aPosition, const Matrix4x4& aTransform,
 | |
|     const IntRect& aDisplayRect, const Maybe<IntRect>& aClipRect,
 | |
|     float aBackingScale, bool aSurfaceIsFlipped,
 | |
|     gfx::SamplingFilter aSamplingFilter, bool aSpecializeVideo,
 | |
|     CFTypeRefPtr<IOSurfaceRef> aFrontSurface, CFTypeRefPtr<CGColorRef> aColor,
 | |
|     bool aIsDRM, bool aIsVideo) {
 | |
|   // If we have an OnlyVideo update, handle it and early exit.
 | |
|   if (aUpdate == UpdateType::OnlyVideo) {
 | |
|     // If we don't have any updates to do, exit early with success. This is
 | |
|     // important to do so that the overall OnlyVideo pass will succeed as long
 | |
|     // as the video layers are successful.
 | |
|     if (HasUpdate(true) == UpdateType::None) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     MOZ_ASSERT(!mMutatedSpecializeVideo && mMutatedFrontSurface,
 | |
|                "Shouldn't attempt a OnlyVideo update in this case.");
 | |
| 
 | |
|     bool updateSucceeded = false;
 | |
|     if (aSpecializeVideo) {
 | |
|       IOSurfaceRef surface = aFrontSurface.get();
 | |
|       updateSucceeded = EnqueueSurface(surface);
 | |
| 
 | |
|       if (updateSucceeded) {
 | |
|         mMutatedFrontSurface = false;
 | |
|       } else {
 | |
|         // Set mMutatedSpecializeVideo, which will ensure that the next update
 | |
|         // will rebuild the video layer.
 | |
|         mMutatedSpecializeVideo = true;
 | |
| #ifdef NIGHTLY_BUILD
 | |
|         if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|           NSLog(@"VIDEO_LOG: EnqueueSurface failed in OnlyVideo update.");
 | |
|         }
 | |
| #endif
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return updateSucceeded;
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(aUpdate == UpdateType::All);
 | |
| 
 | |
|   if (mWrappingCALayer && mMutatedSpecializeVideo) {
 | |
|     // Since specialize video changes the way we construct our wrapping and
 | |
|     // content layers, we have to scrap them if this value has changed.
 | |
| #ifdef NIGHTLY_BUILD
 | |
|     if (aIsVideo && StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|       NSLog(@"VIDEO_LOG: Scrapping existing video layer.");
 | |
|     }
 | |
| #endif
 | |
|     [mContentCALayer release];
 | |
|     mContentCALayer = nil;
 | |
|     [mOpaquenessTintLayer release];
 | |
|     mOpaquenessTintLayer = nil;
 | |
|     [mWrappingCALayer release];
 | |
|     mWrappingCALayer = nil;
 | |
|   }
 | |
| 
 | |
|   bool layerNeedsInitialization = false;
 | |
|   if (!mWrappingCALayer) {
 | |
|     layerNeedsInitialization = true;
 | |
|     mWrappingCALayer = [[CALayer layer] retain];
 | |
|     mWrappingCALayer.position = CGPointZero;
 | |
|     mWrappingCALayer.bounds = CGRectZero;
 | |
|     mWrappingCALayer.anchorPoint = CGPointZero;
 | |
|     mWrappingCALayer.contentsGravity = kCAGravityTopLeft;
 | |
|     mWrappingCALayer.edgeAntialiasingMask = 0;
 | |
| 
 | |
|     if (aColor) {
 | |
|       // Color layers set a color on the wrapping layer and don't get a content
 | |
|       // layer.
 | |
|       mWrappingCALayer.backgroundColor = aColor.get();
 | |
|     } else {
 | |
|       if (aSpecializeVideo) {
 | |
| #ifdef NIGHTLY_BUILD
 | |
|         if (aIsVideo &&
 | |
|             StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|           NSLog(@"VIDEO_LOG: Rebuilding video layer with "
 | |
|                 @"AVSampleBufferDisplayLayer.");
 | |
|           mLogNextVideoSurface = true;
 | |
|         }
 | |
| #endif
 | |
|         mContentCALayer = [[AVSampleBufferDisplayLayer layer] retain];
 | |
|         CMTimebaseRef timebase;
 | |
| #ifdef CMTIMEBASE_USE_SOURCE_TERMINOLOGY
 | |
|         CMTimebaseCreateWithSourceClock(kCFAllocatorDefault,
 | |
|                                         CMClockGetHostTimeClock(), &timebase);
 | |
| #else
 | |
|         CMTimebaseCreateWithMasterClock(kCFAllocatorDefault,
 | |
|                                         CMClockGetHostTimeClock(), &timebase);
 | |
| #endif
 | |
|         CMTimebaseSetRate(timebase, 1.0f);
 | |
|         [(AVSampleBufferDisplayLayer*)mContentCALayer
 | |
|             setControlTimebase:timebase];
 | |
|         CFRelease(timebase);
 | |
|       } else {
 | |
| #ifdef NIGHTLY_BUILD
 | |
|         if (aIsVideo &&
 | |
|             StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|           NSLog(@"VIDEO_LOG: Rebuilding video layer with CALayer.");
 | |
|           mLogNextVideoSurface = true;
 | |
|         }
 | |
| #endif
 | |
|         mContentCALayer = [[CALayer layer] retain];
 | |
|       }
 | |
|       mContentCALayer.position = CGPointZero;
 | |
|       mContentCALayer.anchorPoint = CGPointZero;
 | |
|       mContentCALayer.contentsGravity = kCAGravityTopLeft;
 | |
|       mContentCALayer.contentsScale = 1;
 | |
|       mContentCALayer.bounds = CGRectMake(0, 0, aSize.width, aSize.height);
 | |
|       mContentCALayer.edgeAntialiasingMask = 0;
 | |
|       mContentCALayer.opaque = aIsOpaque;
 | |
|       if ([mContentCALayer respondsToSelector:@selector(setContentsOpaque:)]) {
 | |
|         // The opaque property seems to not be enough when using IOSurface
 | |
|         // contents. Additionally, call the private method setContentsOpaque.
 | |
|         [mContentCALayer setContentsOpaque:aIsOpaque];
 | |
|       }
 | |
| 
 | |
|       [mWrappingCALayer addSublayer:mContentCALayer];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aSpecializeVideo && mMutatedIsDRM) {
 | |
|     ((AVSampleBufferDisplayLayer*)mContentCALayer).preventsCapture = aIsDRM;
 | |
|   }
 | |
| 
 | |
|   bool shouldTintOpaqueness = StaticPrefs::gfx_core_animation_tint_opaque();
 | |
|   if (shouldTintOpaqueness && !mOpaquenessTintLayer) {
 | |
|     mOpaquenessTintLayer = [[CALayer layer] retain];
 | |
|     mOpaquenessTintLayer.position = CGPointZero;
 | |
|     mOpaquenessTintLayer.bounds = mContentCALayer.bounds;
 | |
|     mOpaquenessTintLayer.anchorPoint = CGPointZero;
 | |
|     mOpaquenessTintLayer.contentsGravity = kCAGravityTopLeft;
 | |
|     if (aIsOpaque) {
 | |
|       mOpaquenessTintLayer.backgroundColor =
 | |
|           [[[NSColor greenColor] colorWithAlphaComponent:0.5] CGColor];
 | |
|     } else {
 | |
|       mOpaquenessTintLayer.backgroundColor =
 | |
|           [[[NSColor redColor] colorWithAlphaComponent:0.5] CGColor];
 | |
|     }
 | |
|     [mWrappingCALayer addSublayer:mOpaquenessTintLayer];
 | |
|   } else if (!shouldTintOpaqueness && mOpaquenessTintLayer) {
 | |
|     [mOpaquenessTintLayer removeFromSuperlayer];
 | |
|     [mOpaquenessTintLayer release];
 | |
|     mOpaquenessTintLayer = nullptr;
 | |
|   }
 | |
| 
 | |
|   // CALayers have a position and a size, specified through the position and the
 | |
|   // bounds properties. layer.bounds.origin must always be (0, 0). A layer's
 | |
|   // position affects the layer's entire layer subtree. In other words, each
 | |
|   // layer's position is relative to its superlayer's position. We implement the
 | |
|   // clip rect using masksToBounds on mWrappingCALayer. So mContentCALayer's
 | |
|   // position is relative to the clip rect position. Note: The Core Animation
 | |
|   // docs on "Positioning and Sizing Sublayers" say:
 | |
|   //  Important: Always use integral numbers for the width and height of your
 | |
|   //  layer.
 | |
|   // We hope that this refers to integral physical pixels, and not to integral
 | |
|   // logical coordinates.
 | |
| 
 | |
|   if (mContentCALayer &&
 | |
|       (mMutatedBackingScale || mMutatedSize || layerNeedsInitialization)) {
 | |
|     mContentCALayer.bounds = CGRectMake(0, 0, aSize.width / aBackingScale,
 | |
|                                         aSize.height / aBackingScale);
 | |
|     if (mOpaquenessTintLayer) {
 | |
|       mOpaquenessTintLayer.bounds = mContentCALayer.bounds;
 | |
|     }
 | |
|     mContentCALayer.contentsScale = aBackingScale;
 | |
|   }
 | |
| 
 | |
|   if (mMutatedBackingScale || mMutatedPosition || mMutatedDisplayRect ||
 | |
|       mMutatedClipRect || mMutatedTransform || mMutatedSurfaceIsFlipped ||
 | |
|       mMutatedSize || layerNeedsInitialization) {
 | |
|     Maybe<CGRect> scaledClipRect = CalculateClipGeometry(
 | |
|         aSize, aPosition, aTransform, aDisplayRect, aClipRect, aBackingScale);
 | |
| 
 | |
|     CGRect useClipRect;
 | |
|     if (scaledClipRect.isSome()) {
 | |
|       useClipRect = *scaledClipRect;
 | |
|     } else {
 | |
|       useClipRect = CGRectZero;
 | |
|     }
 | |
| 
 | |
|     mWrappingCALayer.position = useClipRect.origin;
 | |
|     mWrappingCALayer.bounds =
 | |
|         CGRectMake(0, 0, useClipRect.size.width, useClipRect.size.height);
 | |
|     mWrappingCALayer.masksToBounds = scaledClipRect.isSome();
 | |
| 
 | |
|     if (mContentCALayer) {
 | |
|       Matrix4x4 transform = aTransform;
 | |
|       transform.PreTranslate(aPosition.x, aPosition.y, 0);
 | |
|       transform.PostTranslate((-useClipRect.origin.x * aBackingScale),
 | |
|                               (-useClipRect.origin.y * aBackingScale), 0);
 | |
| 
 | |
|       if (aSurfaceIsFlipped) {
 | |
|         transform.PreTranslate(0, aSize.height, 0).PreScale(1, -1, 1);
 | |
|       }
 | |
| 
 | |
|       CATransform3D transformCA{transform._11,
 | |
|                                 transform._12,
 | |
|                                 transform._13,
 | |
|                                 transform._14,
 | |
|                                 transform._21,
 | |
|                                 transform._22,
 | |
|                                 transform._23,
 | |
|                                 transform._24,
 | |
|                                 transform._31,
 | |
|                                 transform._32,
 | |
|                                 transform._33,
 | |
|                                 transform._34,
 | |
|                                 transform._41 / aBackingScale,
 | |
|                                 transform._42 / aBackingScale,
 | |
|                                 transform._43,
 | |
|                                 transform._44};
 | |
|       mContentCALayer.transform = transformCA;
 | |
|       if (mOpaquenessTintLayer) {
 | |
|         mOpaquenessTintLayer.transform = mContentCALayer.transform;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mContentCALayer && (mMutatedSamplingFilter || layerNeedsInitialization)) {
 | |
|     if (aSamplingFilter == gfx::SamplingFilter::POINT) {
 | |
|       mContentCALayer.minificationFilter = kCAFilterNearest;
 | |
|       mContentCALayer.magnificationFilter = kCAFilterNearest;
 | |
|     } else {
 | |
|       mContentCALayer.minificationFilter = kCAFilterLinear;
 | |
|       mContentCALayer.magnificationFilter = kCAFilterLinear;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (mMutatedFrontSurface) {
 | |
|     // This is handled last because a video update could fail, causing us to
 | |
|     // early exit, leaving the mutation bits untouched. We do this so that the
 | |
|     // *next* update will clear the video layer and setup a regular layer.
 | |
| 
 | |
|     IOSurfaceRef surface = aFrontSurface.get();
 | |
|     if (aSpecializeVideo) {
 | |
|       // Attempt to enqueue this as a video frame. If we fail, we'll rebuild
 | |
|       // our video layer in the next update.
 | |
|       bool isEnqueued = EnqueueSurface(surface);
 | |
|       if (!isEnqueued) {
 | |
|         // Set mMutatedSpecializeVideo, which will ensure that the next update
 | |
|         // will rebuild the video layer.
 | |
|         mMutatedSpecializeVideo = true;
 | |
| #ifdef NIGHTLY_BUILD
 | |
|         if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|           NSLog(@"VIDEO_LOG: EnqueueSurface failed in All update.");
 | |
|         }
 | |
| #endif
 | |
|         return false;
 | |
|       }
 | |
|     } else {
 | |
| #ifdef NIGHTLY_BUILD
 | |
|       if (mLogNextVideoSurface &&
 | |
|           StaticPrefs::gfx_core_animation_specialize_video_log()) {
 | |
|         LogSurface(surface, nullptr, nullptr);
 | |
|         mLogNextVideoSurface = false;
 | |
|       }
 | |
| #endif
 | |
|       mContentCALayer.contents = (id)surface;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mMutatedPosition = false;
 | |
|   mMutatedTransform = false;
 | |
|   mMutatedBackingScale = false;
 | |
|   mMutatedSize = false;
 | |
|   mMutatedSurfaceIsFlipped = false;
 | |
|   mMutatedDisplayRect = false;
 | |
|   mMutatedClipRect = false;
 | |
|   mMutatedFrontSurface = false;
 | |
|   mMutatedSamplingFilter = false;
 | |
|   mMutatedSpecializeVideo = false;
 | |
|   mMutatedIsDRM = false;
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| NativeLayerCA::UpdateType NativeLayerCA::Representation::HasUpdate(
 | |
|     bool aIsVideo) {
 | |
|   if (!mWrappingCALayer) {
 | |
|     return UpdateType::All;
 | |
|   }
 | |
| 
 | |
|   // This check intentionally skips mMutatedFrontSurface. We'll check it later
 | |
|   // to see if we can attempt an OnlyVideo update.
 | |
|   if (mMutatedPosition || mMutatedTransform || mMutatedDisplayRect ||
 | |
|       mMutatedClipRect || mMutatedBackingScale || mMutatedSize ||
 | |
|       mMutatedSurfaceIsFlipped || mMutatedSamplingFilter ||
 | |
|       mMutatedSpecializeVideo || mMutatedIsDRM) {
 | |
|     return UpdateType::All;
 | |
|   }
 | |
| 
 | |
|   // Check if we should try an OnlyVideo update. We know from the above check
 | |
|   // that our specialize video is stable (we don't know what value we'll
 | |
|   // receive, though), so we just have to check that we have a surface to
 | |
|   // display.
 | |
|   if (mMutatedFrontSurface) {
 | |
|     return (aIsVideo ? UpdateType::OnlyVideo : UpdateType::All);
 | |
|   }
 | |
| 
 | |
|   return UpdateType::None;
 | |
| }
 | |
| 
 | |
| bool NativeLayerCA::WillUpdateAffectLayers(
 | |
|     WhichRepresentation aRepresentation) {
 | |
|   MutexAutoLock lock(mMutex);
 | |
|   auto& r = GetRepresentation(aRepresentation);
 | |
|   return r.mMutatedSpecializeVideo || !r.UnderlyingCALayer();
 | |
| }
 | |
| 
 | |
| // Called when mMutex is already being held by the current thread.
 | |
| Maybe<NativeLayerCA::SurfaceWithInvalidRegion>
 | |
| NativeLayerCA::GetUnusedSurfaceAndCleanUp(const MutexAutoLock& aProofOfLock) {
 | |
|   std::vector<SurfaceWithInvalidRegionAndCheckCount> usedSurfaces;
 | |
|   Maybe<SurfaceWithInvalidRegion> unusedSurface;
 | |
| 
 | |
|   // Separate mSurfaces into used and unused surfaces.
 | |
|   for (auto& surf : mSurfaces) {
 | |
|     if (IOSurfaceIsInUse(surf.mEntry.mSurface.get())) {
 | |
|       surf.mCheckCount++;
 | |
|       if (surf.mCheckCount < 10) {
 | |
|         usedSurfaces.push_back(std::move(surf));
 | |
|       } else {
 | |
|         // The window server has been holding on to this surface for an
 | |
|         // unreasonably long time. This is known to happen sometimes, for
 | |
|         // example in occluded windows or after a GPU switch. In that case,
 | |
|         // release our references to the surface so that it doesn't look like
 | |
|         // we're trying to keep it alive.
 | |
|         mSurfacePoolHandle->ReturnSurfaceToPool(
 | |
|             std::move(surf.mEntry.mSurface));
 | |
|       }
 | |
|     } else {
 | |
|       if (unusedSurface) {
 | |
|         // Multiple surfaces are unused. Keep the most recent one and release
 | |
|         // any earlier ones. The most recent one requires the least amount of
 | |
|         // copying during partial repaints.
 | |
|         mSurfacePoolHandle->ReturnSurfaceToPool(
 | |
|             std::move(unusedSurface->mSurface));
 | |
|       }
 | |
|       unusedSurface = Some(std::move(surf.mEntry));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Put the used surfaces back into mSurfaces.
 | |
|   mSurfaces = std::move(usedSurfaces);
 | |
| 
 | |
|   return unusedSurface;
 | |
| }
 | |
| 
 | |
| bool DownscaleTargetNLRS::DownscaleFrom(
 | |
|     profiler_screenshots::RenderSource* aSource, const IntRect& aSourceRect,
 | |
|     const IntRect& aDestRect) {
 | |
|   mGL->BlitHelper()->BlitFramebufferToFramebuffer(
 | |
|       static_cast<RenderSourceNLRS*>(aSource)->FB().mFB,
 | |
|       mRenderSource->FB().mFB, aSourceRect, aDestRect, LOCAL_GL_LINEAR);
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void AsyncReadbackBufferNLRS::CopyFrom(
 | |
|     profiler_screenshots::RenderSource* aSource) {
 | |
|   IntSize size = aSource->Size();
 | |
|   MOZ_RELEASE_ASSERT(Size() == size);
 | |
| 
 | |
|   gl::ScopedPackState scopedPackState(mGL);
 | |
|   mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, mBufferHandle);
 | |
|   mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
 | |
|   const gl::ScopedBindFramebuffer bindFB(
 | |
|       mGL, static_cast<RenderSourceNLRS*>(aSource)->FB().mFB);
 | |
|   mGL->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA,
 | |
|                    LOCAL_GL_UNSIGNED_BYTE, 0);
 | |
| }
 | |
| 
 | |
| bool AsyncReadbackBufferNLRS::MapAndCopyInto(DataSourceSurface* aSurface,
 | |
|                                              const IntSize& aReadSize) {
 | |
|   MOZ_RELEASE_ASSERT(aReadSize <= aSurface->GetSize());
 | |
| 
 | |
|   if (!mGL || !mGL->MakeCurrent()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   gl::ScopedPackState scopedPackState(mGL);
 | |
|   mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, mBufferHandle);
 | |
|   mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
 | |
| 
 | |
|   const uint8_t* srcData = nullptr;
 | |
|   if (mGL->IsSupported(gl::GLFeature::map_buffer_range)) {
 | |
|     srcData = static_cast<uint8_t*>(mGL->fMapBufferRange(
 | |
|         LOCAL_GL_PIXEL_PACK_BUFFER, 0, aReadSize.height * aReadSize.width * 4,
 | |
|         LOCAL_GL_MAP_READ_BIT));
 | |
|   } else {
 | |
|     srcData = static_cast<uint8_t*>(
 | |
|         mGL->fMapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, LOCAL_GL_READ_ONLY));
 | |
|   }
 | |
| 
 | |
|   if (!srcData) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   int32_t srcStride = mSize.width * 4;  // Bind() sets an alignment of 1
 | |
|   DataSourceSurface::ScopedMap map(aSurface, DataSourceSurface::WRITE);
 | |
|   uint8_t* destData = map.GetData();
 | |
|   int32_t destStride = map.GetStride();
 | |
|   SurfaceFormat destFormat = aSurface->GetFormat();
 | |
|   for (int32_t destRow = 0; destRow < aReadSize.height; destRow++) {
 | |
|     // Turn srcData upside down during the copy.
 | |
|     int32_t srcRow = aReadSize.height - 1 - destRow;
 | |
|     const uint8_t* src = &srcData[srcRow * srcStride];
 | |
|     uint8_t* dest = &destData[destRow * destStride];
 | |
|     SwizzleData(src, srcStride, SurfaceFormat::R8G8B8A8, dest, destStride,
 | |
|                 destFormat, IntSize(aReadSize.width, 1));
 | |
|   }
 | |
| 
 | |
|   mGL->fUnmapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER);
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| AsyncReadbackBufferNLRS::~AsyncReadbackBufferNLRS() {
 | |
|   if (mGL && mGL->MakeCurrent()) {
 | |
|     mGL->fDeleteBuffers(1, &mBufferHandle);
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace layers
 | |
| }  // namespace mozilla
 |