tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

NativeLayerCA.mm (77174B)


      1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
      2 * This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "mozilla/layers/NativeLayerCA.h"
      7 
      8 #ifdef XP_MACOSX
      9 #  import <AppKit/NSAnimationContext.h>
     10 #  import <AppKit/NSColor.h>
     11 #  import <OpenGL/gl.h>
     12 #endif
     13 #import <AVFoundation/AVFoundation.h>
     14 #import <QuartzCore/QuartzCore.h>
     15 
     16 #include <algorithm>
     17 #include <fstream>
     18 #include <iostream>
     19 #include <sstream>
     20 #include <utility>
     21 
     22 #include "gfxUtils.h"
     23 #include "GLBlitHelper.h"
     24 #ifdef XP_MACOSX
     25 #  include "GLContextCGL.h"
     26 #else
     27 #  include "GLContextEAGL.h"
     28 #endif
     29 #include "GLContextProvider.h"
     30 #include "MozFramebuffer.h"
     31 #include "mozilla/gfx/Swizzle.h"
     32 #include "mozilla/layers/ScreenshotGrabber.h"
     33 #include "mozilla/layers/SurfacePoolCA.h"
     34 #include "mozilla/StaticPrefs_gfx.h"
     35 #include "mozilla/glean/GfxMetrics.h"
     36 #include "mozilla/webrender/RenderMacIOSurfaceTextureHost.h"
     37 #include "ScopedGLHelpers.h"
     38 
     39 @interface CALayer (PrivateSetContentsOpaque)
     40 - (void)setContentsOpaque:(BOOL)opaque;
     41 @end
     42 
     43 namespace mozilla {
     44 namespace layers {
     45 
     46 using gfx::DataSourceSurface;
     47 using gfx::IntPoint;
     48 using gfx::IntRect;
     49 using gfx::IntRegion;
     50 using gfx::IntSize;
     51 using gfx::Matrix4x4;
     52 using gfx::SurfaceFormat;
     53 using gl::GLContext;
     54 #ifdef XP_MACOSX
     55 using gl::GLContextCGL;
     56 #endif
     57 
     58 static void EmitTelemetryForVideoLowPower(VideoLowPowerType aVideoLowPower) {
     59  switch (aVideoLowPower) {
     60    case VideoLowPowerType::NotVideo:
     61      glean::gfx::macos_video_low_power
     62          .EnumGet(glean::gfx::MacosVideoLowPowerLabel::eNotvideo)
     63          .Add();
     64      return;
     65 
     66    case VideoLowPowerType::LowPower:
     67      glean::gfx::macos_video_low_power
     68          .EnumGet(glean::gfx::MacosVideoLowPowerLabel::eLowpower)
     69          .Add();
     70      return;
     71 
     72    case VideoLowPowerType::FailMultipleVideo:
     73      glean::gfx::macos_video_low_power
     74          .EnumGet(glean::gfx::MacosVideoLowPowerLabel::eFailmultiplevideo)
     75          .Add();
     76      return;
     77 
     78    case VideoLowPowerType::FailWindowed:
     79      glean::gfx::macos_video_low_power
     80          .EnumGet(glean::gfx::MacosVideoLowPowerLabel::eFailwindowed)
     81          .Add();
     82      return;
     83 
     84    case VideoLowPowerType::FailOverlaid:
     85      glean::gfx::macos_video_low_power
     86          .EnumGet(glean::gfx::MacosVideoLowPowerLabel::eFailoverlaid)
     87          .Add();
     88      return;
     89 
     90    case VideoLowPowerType::FailBacking:
     91      glean::gfx::macos_video_low_power
     92          .EnumGet(glean::gfx::MacosVideoLowPowerLabel::eFailbacking)
     93          .Add();
     94      return;
     95 
     96    case VideoLowPowerType::FailMacOSVersion:
     97      glean::gfx::macos_video_low_power
     98          .EnumGet(glean::gfx::MacosVideoLowPowerLabel::eFailmacosversion)
     99          .Add();
    100      return;
    101 
    102    case VideoLowPowerType::FailPref:
    103      glean::gfx::macos_video_low_power
    104          .EnumGet(glean::gfx::MacosVideoLowPowerLabel::eFailpref)
    105          .Add();
    106      return;
    107 
    108    case VideoLowPowerType::FailSurface:
    109      glean::gfx::macos_video_low_power
    110          .EnumGet(glean::gfx::MacosVideoLowPowerLabel::eFailsurface)
    111          .Add();
    112      return;
    113 
    114    case VideoLowPowerType::FailEnqueue:
    115      glean::gfx::macos_video_low_power
    116          .EnumGet(glean::gfx::MacosVideoLowPowerLabel::eFailenqueue)
    117          .Add();
    118      return;
    119  }
    120 }
    121 
    122 // Utility classes for NativeLayerRootSnapshotter (NLRS) profiler screenshots.
    123 
    124 class RenderSourceNLRS : public profiler_screenshots::RenderSource {
    125 public:
    126  explicit RenderSourceNLRS(UniquePtr<gl::MozFramebuffer>&& aFramebuffer)
    127      : RenderSource(aFramebuffer->mSize),
    128        mFramebuffer(std::move(aFramebuffer)) {}
    129  auto& FB() { return *mFramebuffer; }
    130 
    131 protected:
    132  UniquePtr<gl::MozFramebuffer> mFramebuffer;
    133 };
    134 
    135 class DownscaleTargetNLRS : public profiler_screenshots::DownscaleTarget {
    136 public:
    137  DownscaleTargetNLRS(gl::GLContext* aGL,
    138                      UniquePtr<gl::MozFramebuffer>&& aFramebuffer)
    139      : profiler_screenshots::DownscaleTarget(aFramebuffer->mSize),
    140        mGL(aGL),
    141        mRenderSource(new RenderSourceNLRS(std::move(aFramebuffer))) {}
    142  already_AddRefed<profiler_screenshots::RenderSource> AsRenderSource()
    143      override {
    144    return do_AddRef(mRenderSource);
    145  };
    146  bool DownscaleFrom(profiler_screenshots::RenderSource* aSource,
    147                     const IntRect& aSourceRect,
    148                     const IntRect& aDestRect) override;
    149 
    150 protected:
    151  RefPtr<gl::GLContext> mGL;
    152  RefPtr<RenderSourceNLRS> mRenderSource;
    153 };
    154 
    155 class AsyncReadbackBufferNLRS
    156    : public profiler_screenshots::AsyncReadbackBuffer {
    157 public:
    158  AsyncReadbackBufferNLRS(gl::GLContext* aGL, const IntSize& aSize,
    159                          GLuint aBufferHandle)
    160      : profiler_screenshots::AsyncReadbackBuffer(aSize),
    161        mGL(aGL),
    162        mBufferHandle(aBufferHandle) {}
    163  void CopyFrom(profiler_screenshots::RenderSource* aSource) override;
    164  bool MapAndCopyInto(DataSourceSurface* aSurface,
    165                      const IntSize& aReadSize) override;
    166 
    167 protected:
    168  virtual ~AsyncReadbackBufferNLRS();
    169  RefPtr<gl::GLContext> mGL;
    170  GLuint mBufferHandle = 0;
    171 };
    172 
    173 // Needs to be on the stack whenever CALayer mutations are performed.
    174 // (Mutating CALayers outside of a transaction can result in permanently stuck
    175 // rendering, because such mutations create an implicit transaction which never
    176 // auto-commits if the current thread does not have a native runloop.) Uses
    177 // NSAnimationContext, which wraps CATransaction with additional off-main-thread
    178 // protection, see bug 1585523.
    179 struct MOZ_STACK_CLASS AutoCATransaction final {
    180  AutoCATransaction() {
    181 #ifdef XP_MACOSX
    182    [NSAnimationContext beginGrouping];
    183 #else
    184    [CATransaction begin];
    185 #endif
    186    // By default, mutating a CALayer property triggers an animation which
    187    // smoothly transitions the property to the new value. We don't need these
    188    // animations, and this call turns them off:
    189    [CATransaction setDisableActions:YES];
    190  }
    191  ~AutoCATransaction() {
    192 #ifdef XP_MACOSX
    193    [NSAnimationContext endGrouping];
    194 #else
    195    [CATransaction commit];
    196 #endif
    197  }
    198 };
    199 
    200 /* static */ already_AddRefed<NativeLayerRootCA>
    201 NativeLayerRootCA::CreateForCALayer(CALayer* aLayer) {
    202  RefPtr<NativeLayerRootCA> layerRoot = new NativeLayerRootCA(aLayer);
    203  return layerRoot.forget();
    204 }
    205 
    206 NativeLayerRootCA::NativeLayerRootCA(CALayer* aLayer)
    207    : mMutex("NativeLayerRootCA"), mOnscreenRootCALayer([aLayer retain]) {}
    208 
    209 NativeLayerRootCA::~NativeLayerRootCA() {
    210  MOZ_RELEASE_ASSERT(
    211      mSublayers.IsEmpty(),
    212      "Please clear all layers before destroying the layer root.");
    213 
    214  {
    215    // Clear the root layer's sublayers. At this point the window is usually
    216    // closed, so this transaction does not cause any screen updates.
    217    AutoCATransaction transaction;
    218    mOnscreenRootCALayer.sublayers = @[];
    219  }
    220 
    221  [mOnscreenRootCALayer release];
    222  [mOffscreenRootCALayer release];
    223 }
    224 
    225 already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayer(
    226    const IntSize& aSize, bool aIsOpaque,
    227    SurfacePoolHandle* aSurfacePoolHandle) {
    228  RefPtr<NativeLayer> layer = new NativeLayerCA(
    229      aSize, aIsOpaque, aSurfacePoolHandle->AsSurfacePoolHandleCA());
    230  return layer.forget();
    231 }
    232 
    233 already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayerForExternalTexture(
    234    bool aIsOpaque) {
    235  RefPtr<NativeLayer> layer = new NativeLayerCA(aIsOpaque);
    236  return layer.forget();
    237 }
    238 
    239 already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayerForColor(
    240    gfx::DeviceColor aColor) {
    241  RefPtr<NativeLayer> layer = new NativeLayerCA(aColor);
    242  return layer.forget();
    243 }
    244 
    245 already_AddRefed<NativeLayerCA>
    246 NativeLayerRootCA::CreateLayerForSurfacePresentation(const IntSize& aSize,
    247                                                     bool aIsOpaque) {
    248  RefPtr<NativeLayerCA> layer = new NativeLayerCA(aSize, aIsOpaque);
    249  return layer.forget();
    250 }
    251 
    252 void NativeLayerRootCA::AppendLayer(NativeLayer* aLayer) {
    253  MutexAutoLock lock(mMutex);
    254 
    255  RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA();
    256  MOZ_RELEASE_ASSERT(layerCA);
    257 
    258  mSublayers.AppendElement(layerCA);
    259  layerCA->SetBackingScale(mBackingScale);
    260  layerCA->SetRootWindowIsFullscreen(mWindowIsFullscreen);
    261  SetMutatedLayerStructure();
    262 }
    263 
    264 void NativeLayerRootCA::RemoveLayer(NativeLayer* aLayer) {
    265  MutexAutoLock lock(mMutex);
    266 
    267  RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA();
    268  MOZ_RELEASE_ASSERT(layerCA);
    269 
    270  mSublayers.RemoveElement(layerCA);
    271  SetMutatedLayerStructure();
    272 }
    273 
    274 void NativeLayerRootCA::SetLayers(
    275    const nsTArray<RefPtr<NativeLayer>>& aLayers) {
    276  MutexAutoLock lock(mMutex);
    277 
    278  // Ideally, we'd just be able to do mSublayers = std::move(aLayers).
    279  // However, aLayers has a different type: it carries NativeLayer objects,
    280  // whereas mSublayers carries NativeLayerCA objects, so we have to downcast
    281  // all the elements first. There's one other reason to look at all the
    282  // elements in aLayers first: We need to make sure any new layers know about
    283  // our current backing scale.
    284 
    285  nsTArray<RefPtr<NativeLayerCA>> layersCA(aLayers.Length());
    286  for (auto& layer : aLayers) {
    287    RefPtr<NativeLayerCA> layerCA = layer->AsNativeLayerCA();
    288    MOZ_RELEASE_ASSERT(layerCA);
    289    layerCA->SetBackingScale(mBackingScale);
    290    layerCA->SetRootWindowIsFullscreen(mWindowIsFullscreen);
    291    layersCA.AppendElement(std::move(layerCA));
    292  }
    293 
    294  if (layersCA != mSublayers) {
    295    mSublayers = std::move(layersCA);
    296    SetMutatedLayerStructure();
    297  }
    298 }
    299 
    300 void NativeLayerRootCA::SetBackingScale(float aBackingScale) {
    301  MutexAutoLock lock(mMutex);
    302 
    303  mBackingScale = aBackingScale;
    304  for (auto layer : mSublayers) {
    305    layer->SetBackingScale(aBackingScale);
    306  }
    307 }
    308 
    309 float NativeLayerRootCA::BackingScale() {
    310  MutexAutoLock lock(mMutex);
    311  return mBackingScale;
    312 }
    313 
    314 void NativeLayerRootCA::SuspendOffMainThreadCommits() {
    315  MutexAutoLock lock(mMutex);
    316  mOffMainThreadCommitsSuspended = true;
    317 }
    318 
    319 bool NativeLayerRootCA::UnsuspendOffMainThreadCommits() {
    320  MutexAutoLock lock(mMutex);
    321  mOffMainThreadCommitsSuspended = false;
    322  return mCommitPending;
    323 }
    324 
    325 bool NativeLayerRootCA::AreOffMainThreadCommitsSuspended() {
    326  MutexAutoLock lock(mMutex);
    327  return mOffMainThreadCommitsSuspended;
    328 }
    329 
    330 bool NativeLayerRootCA::CommitToScreen() {
    331  MutexAutoLock lock(mMutex);
    332 
    333  if (!NS_IsMainThread() && mOffMainThreadCommitsSuspended) {
    334    mCommitPending = true;
    335    return false;
    336  }
    337 
    338  CommitRepresentation(WhichRepresentation::ONSCREEN, mOnscreenRootCALayer,
    339                       mSublayers, mMutatedOnscreenLayerStructure,
    340                       mWindowIsFullscreen);
    341  mMutatedOnscreenLayerStructure = false;
    342 
    343  mCommitPending = false;
    344 
    345  if (StaticPrefs::gfx_webrender_debug_dump_native_layer_tree_to_file()) {
    346    static uint32_t sFrameID = 0;
    347    uint32_t frameID = sFrameID++;
    348 
    349    NSString* dirPath =
    350        [NSString stringWithFormat:@"%@/Desktop/nativelayerdumps-%d",
    351                                   NSHomeDirectory(), getpid()];
    352    if ([NSFileManager.defaultManager createDirectoryAtPath:dirPath
    353                                withIntermediateDirectories:YES
    354                                                 attributes:nil
    355                                                      error:nullptr]) {
    356      NSString* filename =
    357          [NSString stringWithFormat:@"frame-%d.html", frameID];
    358      NSString* filePath = [dirPath stringByAppendingPathComponent:filename];
    359      DumpLayerTreeToFile([filePath UTF8String], lock);
    360    } else {
    361      NSLog(@"Failed to create directory %@", dirPath);
    362    }
    363  }
    364 
    365  // Decide if we are going to emit telemetry about video low power on this
    366  // commit.
    367  static const int32_t TELEMETRY_COMMIT_PERIOD =
    368      StaticPrefs::gfx_core_animation_low_power_telemetry_frames_AtStartup();
    369  mTelemetryCommitCount = (mTelemetryCommitCount + 1) % TELEMETRY_COMMIT_PERIOD;
    370  if (mTelemetryCommitCount == 0) {
    371    // Figure out if we are hitting video low power mode.
    372    VideoLowPowerType videoLowPower = CheckVideoLowPower(lock);
    373    EmitTelemetryForVideoLowPower(videoLowPower);
    374  }
    375 
    376  return true;
    377 }
    378 
    379 UniquePtr<NativeLayerRootSnapshotter> NativeLayerRootCA::CreateSnapshotter() {
    380 #ifdef XP_MACOSX
    381  MutexAutoLock lock(mMutex);
    382  MOZ_RELEASE_ASSERT(!mWeakSnapshotter,
    383                     "No NativeLayerRootSnapshotter for this NativeLayerRoot "
    384                     "should exist when this is called");
    385 
    386  auto cr = NativeLayerRootSnapshotterCA::Create(
    387      MakeUnique<SnapshotterDelegate>(this));
    388  if (cr) {
    389    mWeakSnapshotter = cr.get();
    390  }
    391  return cr;
    392 #else
    393  return nullptr;
    394 #endif
    395 }
    396 
    397 void NativeLayerRootCA::OnNativeLayerRootSnapshotterDestroyed(
    398    NativeLayerRootSnapshotterCA* aNativeLayerRootSnapshotter) {
    399  MutexAutoLock lock(mMutex);
    400  MOZ_RELEASE_ASSERT(mWeakSnapshotter == aNativeLayerRootSnapshotter);
    401  mWeakSnapshotter = nullptr;
    402 }
    403 
    404 void NativeLayerRootCA::CommitOffscreen(CALayer* aRootCALayer) {
    405  MutexAutoLock lock(mMutex);
    406  if (aRootCALayer != mOffscreenRootCALayer) {
    407    [mOffscreenRootCALayer release];
    408    mOffscreenRootCALayer = [aRootCALayer retain];
    409    mMutatedOffscreenLayerStructure = true;
    410  }
    411  CommitRepresentation(WhichRepresentation::OFFSCREEN, aRootCALayer, mSublayers,
    412                       mMutatedOffscreenLayerStructure, mWindowIsFullscreen);
    413  mMutatedOffscreenLayerStructure = false;
    414 }
    415 
    416 void NativeLayerRootCA::SetMutatedLayerStructure() {
    417  mMutatedOnscreenLayerStructure = true;
    418  mMutatedOffscreenLayerStructure = true;
    419 }
    420 
    421 NativeLayerCAUpdateType NativeLayerRootCA::GetMaxUpdateRequired(
    422    WhichRepresentation aRepresentation,
    423    const nsTArray<RefPtr<NativeLayerCA>>& aSublayers,
    424    bool aMutatedLayerStructure) const {
    425  if (aMutatedLayerStructure) {
    426    return UpdateType::All;
    427  }
    428 
    429  UpdateType maxUpdateRequired = UpdateType::None;
    430  for (const auto& layer : aSublayers) {
    431    UpdateType updateRequired = layer->HasUpdate(aRepresentation);
    432    if (updateRequired == UpdateType::All) {
    433      return UpdateType::All;
    434    }
    435    // Use the ordering of our UpdateType enum values.
    436    maxUpdateRequired = std::max(maxUpdateRequired, updateRequired);
    437  }
    438  return maxUpdateRequired;
    439 }
    440 
    441 void NativeLayerRootCA::CommitRepresentation(
    442    WhichRepresentation aRepresentation, CALayer* aRootCALayer,
    443    const nsTArray<RefPtr<NativeLayerCA>>& aSublayers,
    444    bool aMutatedLayerStructure, bool aWindowIsFullscreen) {
    445  UpdateType updateRequired =
    446      GetMaxUpdateRequired(aRepresentation, aSublayers, aMutatedLayerStructure);
    447  if (updateRequired == NativeLayerCA::UpdateType::OnlyVideo) {
    448    // Attempt a video-only update, which does not require being wrapped in a
    449    // CATransaction.
    450    bool allUpdatesSucceeded =
    451        std::all_of(aSublayers.begin(), aSublayers.end(),
    452                    [=](const RefPtr<NativeLayerCA>& layer) {
    453                      bool ignoredMustRebuild = false;
    454                      return layer->ApplyChanges(
    455                          aRepresentation, NativeLayerCA::UpdateType::OnlyVideo,
    456                          &ignoredMustRebuild);
    457                    });
    458 
    459    if (allUpdatesSucceeded) {
    460      // Nothing more needed, so early exit.
    461      return;
    462    }
    463  }
    464 
    465  // We're going to do a full update now, which requires a transaction. Update
    466  // all of the sublayers. We collect all sublayers with non-zero extents into
    467  // the sublayers array - layers which are completely clipped out will return
    468  // null from UndelyingCALayer.
    469  AutoCATransaction transaction;
    470  NSMutableArray<CALayer*>* sublayers =
    471      [NSMutableArray arrayWithCapacity:aSublayers.Length()];
    472  bool mustRebuild = updateRequired == UpdateType::All;
    473  for (const auto& layer : aSublayers) {
    474    layer->ApplyChanges(aRepresentation, NativeLayerCA::UpdateType::All,
    475                        &mustRebuild);
    476    if (CALayer* caLayer = layer->UnderlyingCALayer(aRepresentation)) {
    477      [sublayers addObject:caLayer];
    478    }
    479  }
    480 
    481  if (mustRebuild) {
    482    aRootCALayer.sublayers = sublayers;
    483  }
    484 }
    485 
    486 SnapshotterCADelegate::~SnapshotterCADelegate() = default;
    487 
    488 NativeLayerRootCA::SnapshotterDelegate::SnapshotterDelegate(
    489    NativeLayerRootCA* aLayerRoot)
    490    : mLayerRoot(aLayerRoot) {}
    491 NativeLayerRootCA::SnapshotterDelegate::~SnapshotterDelegate() = default;
    492 
    493 #ifdef XP_MACOSX
    494 /* static */ UniquePtr<NativeLayerRootSnapshotterCA>
    495 NativeLayerRootSnapshotterCA::Create(
    496    UniquePtr<SnapshotterCADelegate>&& aDelegate) {
    497  if (NS_IsMainThread()) {
    498    // Disallow creating snapshotters on the main thread.
    499    // On the main thread, any explicit CATransaction / NSAnimationContext is
    500    // nested within a global implicit transaction. This makes it impossible to
    501    // apply CALayer mutations synchronously such that they become visible to
    502    // CARenderer. As a result, the snapshotter would not capture the right
    503    // output on the main thread.
    504    return nullptr;
    505  }
    506 
    507  nsCString failureUnused;
    508  RefPtr<gl::GLContext> gl = gl::GLContextProvider::CreateHeadless(
    509      {gl::CreateContextFlags::ALLOW_OFFLINE_RENDERER |
    510       gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE},
    511      &failureUnused);
    512  if (!gl) {
    513    return nullptr;
    514  }
    515 
    516  return UniquePtr<NativeLayerRootSnapshotterCA>(
    517      new NativeLayerRootSnapshotterCA(std::move(aDelegate), std::move(gl)));
    518 }
    519 #endif
    520 
    521 void NativeLayerRootCA::DumpLayerTreeToFile(const char* aPath,
    522                                            const MutexAutoLock& aProofOfLock) {
    523  NSLog(@"Dumping NativeLayer contents to %s", aPath);
    524  std::ofstream fileOutput(aPath);
    525  if (fileOutput.fail()) {
    526    NSLog(@"Opening %s for writing failed.", aPath);
    527  }
    528 
    529  // Make sure floating point values use a period for the decimal separator.
    530  fileOutput.imbue(std::locale("C"));
    531 
    532  fileOutput << "<html>\n";
    533  for (const auto& layer : mSublayers) {
    534    layer->DumpLayer(fileOutput);
    535  }
    536  fileOutput << "</html>\n";
    537  fileOutput.close();
    538 }
    539 
    540 void NativeLayerRootCA::SetWindowIsFullscreen(bool aFullscreen) {
    541  MutexAutoLock lock(mMutex);
    542 
    543  if (mWindowIsFullscreen != aFullscreen) {
    544    mWindowIsFullscreen = aFullscreen;
    545 
    546    for (auto layer : mSublayers) {
    547      layer->SetRootWindowIsFullscreen(mWindowIsFullscreen);
    548    }
    549  }
    550 }
    551 
    552 /* static */ bool IsCGColorOpaqueBlack(CGColorRef aColor) {
    553  if (CGColorEqualToColor(aColor, CGColorGetConstantColor(kCGColorBlack))) {
    554    return true;
    555  }
    556  size_t componentCount = CGColorGetNumberOfComponents(aColor);
    557  if (componentCount == 0) {
    558    // This will happen if aColor is kCGColorClear. It's not opaque black.
    559    return false;
    560  }
    561 
    562  const CGFloat* components = CGColorGetComponents(aColor);
    563  for (size_t c = 0; c < componentCount - 1; ++c) {
    564    if (components[c] > 0.0f) {
    565      return false;
    566    }
    567  }
    568  return components[componentCount - 1] >= 1.0f;
    569 }
    570 
    571 VideoLowPowerType NativeLayerRootCA::CheckVideoLowPower(
    572    const MutexAutoLock& aProofOfLock) {
    573  // This deteremines whether the current layer contents qualify for the
    574  // macOS Core Animation video low power mode. Those requirements are
    575  // summarized at
    576  // https://developer.apple.com/documentation/webkit/delivering_video_content_for_safari
    577  // and we verify them by checking:
    578  // 1) There must be exactly one video showing.
    579  // 2) The topmost CALayer must be a AVSampleBufferDisplayLayer.
    580  // 3) The video layer must be showing a buffer encoded in one of the
    581  //    kCVPixelFormatType_420YpCbCr pixel formats.
    582  // 4) The layer below that must cover the entire screen and have a black
    583  //    background color.
    584  // 5) The window must be fullscreen.
    585  // This function checks these requirements empirically. If one of the checks
    586  // fail, we either return immediately or do additional processing to
    587  // determine more detail.
    588 
    589  uint32_t videoLayerCount = 0;
    590  DebugOnly<RefPtr<NativeLayerCA>> topLayer;
    591  CALayer* topCALayer = nil;
    592  CALayer* secondCALayer = nil;
    593  bool topLayerIsVideo = false;
    594 
    595  for (auto layer : mSublayers) {
    596    // Only layers with extent are contributing to our sublayers.
    597    CALayer* caLayer = layer->UnderlyingCALayer(WhichRepresentation::ONSCREEN);
    598    if (caLayer) {
    599      bool isVideo = layer->IsVideo(aProofOfLock);
    600      if (isVideo) {
    601        ++videoLayerCount;
    602      }
    603 
    604      secondCALayer = topCALayer;
    605 
    606      topLayer = layer;
    607      topCALayer = caLayer;
    608      topLayerIsVideo = isVideo;
    609    }
    610  }
    611 
    612  if (videoLayerCount == 0) {
    613    return VideoLowPowerType::NotVideo;
    614  }
    615 
    616  // Most importantly, check if the window is fullscreen. If the user is
    617  // watching video in a window, then all of the other enums are irrelevant to
    618  // achieving the low power mode.
    619  if (!mWindowIsFullscreen) {
    620    return VideoLowPowerType::FailWindowed;
    621  }
    622 
    623  if (videoLayerCount > 1) {
    624    return VideoLowPowerType::FailMultipleVideo;
    625  }
    626 
    627  if (!topLayerIsVideo) {
    628    return VideoLowPowerType::FailOverlaid;
    629  }
    630 
    631  if (!secondCALayer || !IsCGColorOpaqueBlack(secondCALayer.backgroundColor) ||
    632      !CGRectContainsRect(secondCALayer.frame,
    633                          secondCALayer.superlayer.bounds)) {
    634    return VideoLowPowerType::FailBacking;
    635  }
    636 
    637  CALayer* topContentCALayer = topCALayer.sublayers[0];
    638  if (![topContentCALayer isKindOfClass:[AVSampleBufferDisplayLayer class]]) {
    639    // We didn't create a AVSampleBufferDisplayLayer for the top video layer.
    640    // Try to figure out why by following some of the logic in
    641    // NativeLayerCA::ShouldSpecializeVideo.
    642 
    643    if (!StaticPrefs::gfx_core_animation_specialize_video()) {
    644      return VideoLowPowerType::FailPref;
    645    }
    646 
    647    // The only remaining reason is that the surface wasn't eligible. We
    648    // assert this instead of if-ing it, to ensure that we always have a
    649    // return value from this clause.
    650 #ifdef DEBUG
    651    CFTypeRefPtr<IOSurfaceRef> surface;
    652    if (auto textureHost = topLayer->mTextureHost) {
    653      MacIOSurface* macIOSurface = textureHost->GetSurface();
    654      surface = macIOSurface->GetIOSurfaceRef();
    655    } else {
    656      surface = topLayer->mSurfaceToPresent;
    657    }
    658    MOZ_ASSERT(surface);
    659    OSType pixelFormat = IOSurfaceGetPixelFormat(surface.get());
    660    MOZ_ASSERT(
    661        !(pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange ||
    662          pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
    663          pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange ||
    664          pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange));
    665 #endif
    666    return VideoLowPowerType::FailSurface;
    667  }
    668 
    669  AVSampleBufferDisplayLayer* topVideoLayer =
    670      (AVSampleBufferDisplayLayer*)topContentCALayer;
    671  if (topVideoLayer.status != AVQueuedSampleBufferRenderingStatusRendering) {
    672    return VideoLowPowerType::FailEnqueue;
    673  }
    674 
    675  // As best we can tell, we're eligible for video low power mode. Hurrah!
    676  return VideoLowPowerType::LowPower;
    677 }
    678 
    679 #ifdef XP_MACOSX
    680 NativeLayerRootSnapshotterCA::NativeLayerRootSnapshotterCA(
    681    UniquePtr<SnapshotterCADelegate>&& aDelegate, RefPtr<GLContext>&& aGL)
    682    : mDelegate(std::move(aDelegate)), mGL(aGL) {}
    683 
    684 NativeLayerRootSnapshotterCA::~NativeLayerRootSnapshotterCA() {
    685  mDelegate->OnSnapshotterDestroyed(this);
    686 
    687  if (mRenderer) {
    688    AutoCATransaction transaction;
    689    mRenderer.layer.sublayers = @[];
    690    [mRenderer release];
    691  }
    692 }
    693 
    694 already_AddRefed<profiler_screenshots::RenderSource>
    695 NativeLayerRootSnapshotterCA::GetWindowContents(const IntSize& aWindowSize) {
    696  UpdateSnapshot(aWindowSize);
    697  return do_AddRef(mSnapshot);
    698 }
    699 
    700 void NativeLayerRootSnapshotterCA::UpdateSnapshot(const IntSize& aSize) {
    701  CGRect bounds = CGRectMake(0, 0, aSize.width, aSize.height);
    702 
    703  {
    704    // Lazily initialize our renderer, and set the correct bounds and scale
    705    // on the renderer and its root layer.
    706    AutoCATransaction transaction;
    707    if (!mRenderer) {
    708      mRenderer = [[CARenderer
    709          rendererWithCGLContext:gl::GLContextCGL::Cast(mGL)->GetCGLContext()
    710                         options:nil] retain];
    711      // This layer should behave similarly to the backing layer of a flipped
    712      // NSView. It will never be rendered on the screen and it will never be
    713      // attached to an NSView's layer; instead, it will be the root layer of a
    714      // "local" CAContext. Setting geometryFlipped to YES causes the
    715      // orientation of descendant CALayers' contents (such as IOSurfaces) to be
    716      // consistent with what happens in a layer subtree that is attached to a
    717      // flipped NSView. Setting it to NO would cause the surfaces in individual
    718      // leaf layers to render upside down (rather than just flipping the entire
    719      // layer tree upside down).
    720      AutoCATransaction transaction;
    721      CALayer* layer = [CALayer layer];
    722      layer.position = CGPointZero;
    723      layer.anchorPoint = CGPointZero;
    724      layer.contentsGravity = kCAGravityTopLeft;
    725      layer.masksToBounds = YES;
    726      layer.geometryFlipped = YES;
    727      mRenderer.layer = layer;
    728    }
    729 
    730    // CARenderer always renders at unit scale, i.e. the coordinates on the root
    731    // layer must map 1:1 to render target pixels. But the coordinates on our
    732    // content layers are in "points", where 1 point maps to 2 device pixels on
    733    // HiDPI. So in order to render at the full device pixel resolution, we set
    734    // a scale transform on the root offscreen layer.
    735    mRenderer.layer.bounds = bounds;
    736    float scale = mDelegate->BackingScale();
    737    mRenderer.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1);
    738    mRenderer.bounds = bounds;
    739  }
    740 
    741  mDelegate->UpdateSnapshotterLayers(mRenderer.layer);
    742 
    743  mGL->MakeCurrent();
    744 
    745  bool needToRedrawEverything = false;
    746  if (!mSnapshot || mSnapshot->Size() != aSize) {
    747    mSnapshot = nullptr;
    748    auto fb = gl::MozFramebuffer::Create(mGL, aSize, 0, false);
    749    if (!fb) {
    750      return;
    751    }
    752    mSnapshot = new RenderSourceNLRS(std::move(fb));
    753    needToRedrawEverything = true;
    754  }
    755 
    756  const gl::ScopedBindFramebuffer bindFB(mGL, mSnapshot->FB().mFB);
    757  mGL->fViewport(0.0, 0.0, aSize.width, aSize.height);
    758 
    759  // These legacy OpenGL function calls are part of CARenderer's API contract,
    760  // see CARenderer.h. The size passed to glOrtho must be the device pixel size
    761  // of the render target, otherwise CARenderer will produce incorrect results.
    762  glMatrixMode(GL_PROJECTION);
    763  glLoadIdentity();
    764  glOrtho(0.0, aSize.width, 0.0, aSize.height, -1, 1);
    765 
    766  float mediaTime = CACurrentMediaTime();
    767  [mRenderer beginFrameAtTime:mediaTime timeStamp:nullptr];
    768  if (needToRedrawEverything) {
    769    [mRenderer addUpdateRect:bounds];
    770  }
    771  if (!CGRectIsEmpty([mRenderer updateBounds])) {
    772    // CARenderer assumes the layer tree is opaque. It only ever paints over
    773    // existing content, it never erases anything. However, our layer tree is
    774    // not necessarily opaque. So we manually erase the area that's going to be
    775    // redrawn. This ensures correct rendering in the transparent areas.
    776    //
    777    // Since we erase the bounds of the update area, this will erase more than
    778    // necessary if the update area is not a single rectangle. Unfortunately we
    779    // cannot get the precise update region from CARenderer, we can only get the
    780    // bounds.
    781    CGRect updateBounds = [mRenderer updateBounds];
    782    gl::ScopedGLState scopedScissorTestState(mGL, LOCAL_GL_SCISSOR_TEST, true);
    783    gl::ScopedScissorRect scissor(
    784        mGL, updateBounds.origin.x, updateBounds.origin.y,
    785        updateBounds.size.width, updateBounds.size.height);
    786    mGL->fClearColor(0.0, 0.0, 0.0, 0.0);
    787    mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
    788    // We erased the update region's bounds. Make sure the entire update bounds
    789    // get repainted.
    790    [mRenderer addUpdateRect:updateBounds];
    791  }
    792  [mRenderer render];
    793  [mRenderer endFrame];
    794 }
    795 
    796 bool NativeLayerRootSnapshotterCA::ReadbackPixels(
    797    const IntSize& aReadbackSize, SurfaceFormat aReadbackFormat,
    798    const Range<uint8_t>& aReadbackBuffer) {
    799  if (mDelegate->DoCustomReadbackForReftestsIfDesired(
    800          aReadbackSize, aReadbackFormat, aReadbackBuffer)) {
    801    return true;
    802  }
    803 
    804  if (aReadbackFormat != SurfaceFormat::B8G8R8A8) {
    805    return false;
    806  }
    807 
    808  UpdateSnapshot(aReadbackSize);
    809  if (!mSnapshot) {
    810    return false;
    811  }
    812 
    813  const gl::ScopedBindFramebuffer bindFB(mGL, mSnapshot->FB().mFB);
    814  gl::ScopedPackState safePackState(mGL);
    815  mGL->fReadPixels(0.0f, 0.0f, aReadbackSize.width, aReadbackSize.height,
    816                   LOCAL_GL_BGRA, LOCAL_GL_UNSIGNED_BYTE, &aReadbackBuffer[0]);
    817 
    818  return true;
    819 }
    820 
    821 already_AddRefed<profiler_screenshots::DownscaleTarget>
    822 NativeLayerRootSnapshotterCA::CreateDownscaleTarget(const IntSize& aSize) {
    823  auto fb = gl::MozFramebuffer::Create(mGL, aSize, 0, false);
    824  if (!fb) {
    825    return nullptr;
    826  }
    827  RefPtr<profiler_screenshots::DownscaleTarget> dt =
    828      new DownscaleTargetNLRS(mGL, std::move(fb));
    829  return dt.forget();
    830 }
    831 
    832 already_AddRefed<profiler_screenshots::AsyncReadbackBuffer>
    833 NativeLayerRootSnapshotterCA::CreateAsyncReadbackBuffer(const IntSize& aSize) {
    834  size_t bufferByteCount = aSize.width * aSize.height * 4;
    835  GLuint bufferHandle = 0;
    836  mGL->fGenBuffers(1, &bufferHandle);
    837 
    838  gl::ScopedPackState scopedPackState(mGL);
    839  mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, bufferHandle);
    840  mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
    841  mGL->fBufferData(LOCAL_GL_PIXEL_PACK_BUFFER, bufferByteCount, nullptr,
    842                   LOCAL_GL_STREAM_READ);
    843  return MakeAndAddRef<AsyncReadbackBufferNLRS>(mGL, aSize, bufferHandle);
    844 }
    845 #endif
    846 
    847 NativeLayerCA::NativeLayerCA(const IntSize& aSize, bool aIsOpaque,
    848                             SurfacePoolHandleCA* aSurfacePoolHandle)
    849    : mMutex("NativeLayerCA"), mIsOpaque(aIsOpaque) {
    850  // We need a surface handler for this type of layer.
    851  mSurfaceHandler.emplace(aSize, aSurfacePoolHandle);
    852 }
    853 
    854 NativeLayerCA::NativeLayerCA(bool aIsOpaque)
    855    : mMutex("NativeLayerCA"), mIsOpaque(aIsOpaque) {
    856 #ifdef NIGHTLY_BUILD
    857  if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
    858    NSLog(@"VIDEO_LOG: NativeLayerCA: %p is being created to host an external "
    859          @"image, which may force a video layer rebuild.",
    860          this);
    861  }
    862 #endif
    863 }
    864 
    865 CGColorRef CGColorCreateForDeviceColor(const gfx::DeviceColor& aColor) {
    866  if (StaticPrefs::gfx_color_management_native_srgb()) {
    867    return CGColorCreateSRGB(aColor.r, aColor.g, aColor.b, aColor.a);
    868  }
    869 
    870  return CGColorCreateGenericRGB(aColor.r, aColor.g, aColor.b, aColor.a);
    871 }
    872 
    873 NativeLayerCA::NativeLayerCA(gfx::DeviceColor aColor)
    874    : mMutex("NativeLayerCA"),
    875      mColor(Some(aColor)),
    876      mIsOpaque(aColor.a >= 1.0f) {
    877  MOZ_ASSERT(aColor.a > 0.0f, "Can't handle a fully transparent backdrop.");
    878 }
    879 
    880 NativeLayerCA::NativeLayerCA(const IntSize& aSize, bool aIsOpaque)
    881    : mMutex("NativeLayerCA"), mSize(aSize), mIsOpaque(aIsOpaque) {}
    882 
    883 NativeLayerCA::~NativeLayerCA() {
    884 #ifdef NIGHTLY_BUILD
    885  if (mHasEverAttachExternalImage &&
    886      StaticPrefs::gfx_core_animation_specialize_video_log()) {
    887    NSLog(@"VIDEO_LOG: ~NativeLayerCA: %p is being destroyed after hosting "
    888          @"an external image.",
    889          this);
    890  }
    891 #endif
    892 
    893  if (mSurfaceToPresent) {
    894    IOSurfaceDecrementUseCount(mSurfaceToPresent.get());
    895  }
    896 }
    897 
    898 void NativeLayerCA::AttachExternalImage(wr::RenderTextureHost* aExternalImage) {
    899  MutexAutoLock lock(mMutex);
    900 
    901 #ifdef NIGHTLY_BUILD
    902  mHasEverAttachExternalImage = true;
    903  MOZ_RELEASE_ASSERT(!mHasEverNotifySurfaceReady,
    904                     "Shouldn't change layer type to external.");
    905 #endif
    906 
    907  MOZ_ASSERT(!mSurfaceHandler,
    908             "Shouldn't have a surface handler for external images.");
    909 
    910  wr::RenderMacIOSurfaceTextureHost* texture =
    911      aExternalImage->AsRenderMacIOSurfaceTextureHost();
    912  MOZ_ASSERT(texture);
    913  mTextureHost = texture;
    914  if (!mTextureHost) {
    915    gfxCriticalNoteOnce << "ExternalImage is not RenderMacIOSurfaceTextureHost";
    916    return;
    917  }
    918 
    919  // Determine if TextureHost is a video surface.
    920  mTextureHostIsVideo = gfx::Info(mTextureHost->GetFormat())->isYuv;
    921 
    922  gfx::IntSize oldSize = mSize;
    923  mSize = texture->GetSize(0);
    924  bool changedSizeAndDisplayRect = (mSize != oldSize);
    925 
    926  mDisplayRect = IntRect(IntPoint{}, mSize);
    927 
    928  bool isDRM = aExternalImage->IsFromDRMSource();
    929  bool changedIsDRM = (mIsDRM != isDRM);
    930  mIsDRM = isDRM;
    931 
    932  bool isHDR = false;
    933  MacIOSurface* macIOSurface = texture->GetSurface();
    934  if (macIOSurface->GetYUVColorSpace() == gfx::YUVColorSpace::BT2020 &&
    935      StaticPrefs::gfx_color_management_hdr_video_assume_rec2020_uses_pq()) {
    936    // BT2020 colorSpace is a signifier of HDR.
    937    isHDR = true;
    938  }
    939 
    940  if (macIOSurface->GetColorDepth() == gfx::ColorDepth::COLOR_10) {
    941    // 10-bit color is a signifier of HDR.
    942    isHDR = true;
    943  }
    944  mIsHDR = isHDR && StaticPrefs::gfx_color_management_hdr_video();
    945 
    946  bool specializeVideo = ShouldSpecializeVideo(lock);
    947  bool changedSpecializeVideo = (mSpecializeVideo != specializeVideo);
    948  mSpecializeVideo = specializeVideo;
    949 
    950 #ifdef NIGHTLY_BUILD
    951  if (changedSpecializeVideo &&
    952      StaticPrefs::gfx_core_animation_specialize_video_log()) {
    953    NSLog(
    954        @"VIDEO_LOG: AttachExternalImage: %p is forcing a video layer rebuild.",
    955        this);
    956  }
    957 #endif
    958 
    959  ForAllRepresentations([&](Representation& r) {
    960    r.mMutatedFrontSurface = true;
    961    r.mMutatedDisplayRect |= changedSizeAndDisplayRect;
    962    r.mMutatedSize |= changedSizeAndDisplayRect;
    963    r.mMutatedSpecializeVideo |= changedSpecializeVideo;
    964    r.mMutatedIsDRM |= changedIsDRM;
    965  });
    966 }
    967 
    968 GpuFence* NativeLayerCA::GetGpuFence() {
    969  if (!mTextureHost) {
    970    return nullptr;
    971  }
    972 
    973  wr::RenderMacIOSurfaceTextureHost* texture =
    974      mTextureHost->AsRenderMacIOSurfaceTextureHost();
    975  if (!texture) {
    976    MOZ_ASSERT_UNREACHABLE("unexpected to be called");
    977    gfxCriticalNoteOnce << "ExternalImage is not RenderMacIOSurfaceTextureHost";
    978    return nullptr;
    979  }
    980 
    981  return texture->GetGpuFence();
    982 }
    983 
    984 bool NativeLayerCA::IsVideo(const MutexAutoLock& aProofOfLock) {
    985  return mTextureHostIsVideo;
    986 }
    987 
    988 bool NativeLayerCA::ShouldSpecializeVideo(const MutexAutoLock& aProofOfLock) {
    989  if (!IsVideo(aProofOfLock)) {
    990    // Only videos are eligible.
    991    return false;
    992  }
    993 
    994  // DRM video is supported in macOS 10.15 and beyond, and such video must use
    995  // a specialized video layer.
    996  if (mIsDRM) {
    997    return true;
    998  }
    999 
   1000  if (mIsHDR) {
   1001    return true;
   1002  }
   1003 
   1004  // Beyond this point, we return true if-and-only-if we think we can achieve
   1005  // the power-saving "detached mode" of the macOS compositor.
   1006 
   1007  if (!StaticPrefs::gfx_core_animation_specialize_video()) {
   1008    // Pref must be set.
   1009    return false;
   1010  }
   1011 
   1012  // It will only detach if we're fullscreen.
   1013  return mRootWindowIsFullscreen;
   1014 }
   1015 
   1016 void NativeLayerCA::SetRootWindowIsFullscreen(bool aFullscreen) {
   1017  if (mRootWindowIsFullscreen == aFullscreen) {
   1018    return;
   1019  }
   1020 
   1021  MutexAutoLock lock(mMutex);
   1022 
   1023  mRootWindowIsFullscreen = aFullscreen;
   1024 
   1025  bool oldSpecializeVideo = mSpecializeVideo;
   1026  mSpecializeVideo = ShouldSpecializeVideo(lock);
   1027  bool changedSpecializeVideo = (mSpecializeVideo != oldSpecializeVideo);
   1028 
   1029  if (changedSpecializeVideo) {
   1030 #ifdef NIGHTLY_BUILD
   1031    if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
   1032      NSLog(@"VIDEO_LOG: SetRootWindowIsFullscreen: %p is forcing a video "
   1033            @"layer rebuild.",
   1034            this);
   1035    }
   1036 #endif
   1037 
   1038    ForAllRepresentations(
   1039        [&](Representation& r) { r.mMutatedSpecializeVideo = true; });
   1040  }
   1041 }
   1042 
   1043 void NativeLayerCA::SetSurfaceIsFlipped(bool aIsFlipped) {
   1044  MutexAutoLock lock(mMutex);
   1045 
   1046  bool oldIsFlipped = mSurfaceIsFlipped;
   1047  if (mSurfaceHandler) {
   1048    oldIsFlipped = mSurfaceHandler->SurfaceIsFlipped();
   1049    mSurfaceHandler->SetSurfaceIsFlipped(aIsFlipped);
   1050  } else {
   1051    mSurfaceIsFlipped = aIsFlipped;
   1052  }
   1053 
   1054  if (aIsFlipped != oldIsFlipped) {
   1055    ForAllRepresentations(
   1056        [&](Representation& r) { r.mMutatedSurfaceIsFlipped = true; });
   1057  }
   1058 }
   1059 
   1060 bool NativeLayerCA::SurfaceIsFlipped() {
   1061  MutexAutoLock lock(mMutex);
   1062  if (mSurfaceHandler) {
   1063    return mSurfaceHandler->SurfaceIsFlipped();
   1064  }
   1065  return mSurfaceIsFlipped;
   1066 }
   1067 
   1068 IntSize NativeLayerCA::GetSize() {
   1069  MutexAutoLock lock(mMutex);
   1070  if (mSurfaceHandler) {
   1071    return mSurfaceHandler->Size();
   1072  }
   1073  return mSize;
   1074 }
   1075 
   1076 void NativeLayerCA::SetPosition(const IntPoint& aPosition) {
   1077  MutexAutoLock lock(mMutex);
   1078 
   1079  if (aPosition != mPosition) {
   1080    mPosition = aPosition;
   1081    ForAllRepresentations(
   1082        [&](Representation& r) { r.mMutatedPosition = true; });
   1083  }
   1084 }
   1085 
   1086 IntPoint NativeLayerCA::GetPosition() {
   1087  MutexAutoLock lock(mMutex);
   1088  return mPosition;
   1089 }
   1090 
   1091 void NativeLayerCA::SetTransform(const Matrix4x4& aTransform) {
   1092  MutexAutoLock lock(mMutex);
   1093  MOZ_ASSERT(aTransform.IsRectilinear());
   1094 
   1095  if (aTransform != mTransform) {
   1096    mTransform = aTransform;
   1097    ForAllRepresentations(
   1098        [&](Representation& r) { r.mMutatedTransform = true; });
   1099  }
   1100 }
   1101 
   1102 void NativeLayerCA::SetSamplingFilter(gfx::SamplingFilter aSamplingFilter) {
   1103  MutexAutoLock lock(mMutex);
   1104 
   1105  if (aSamplingFilter != mSamplingFilter) {
   1106    mSamplingFilter = aSamplingFilter;
   1107    ForAllRepresentations(
   1108        [&](Representation& r) { r.mMutatedSamplingFilter = true; });
   1109  }
   1110 }
   1111 
   1112 Matrix4x4 NativeLayerCA::GetTransform() {
   1113  MutexAutoLock lock(mMutex);
   1114  return mTransform;
   1115 }
   1116 
   1117 IntRect NativeLayerCA::GetRect() {
   1118  MutexAutoLock lock(mMutex);
   1119  IntSize size = mSize;
   1120  if (mSurfaceHandler) {
   1121    size = mSurfaceHandler->Size();
   1122  }
   1123  return IntRect(mPosition, size);
   1124 }
   1125 
   1126 void NativeLayerCA::SetBackingScale(float aBackingScale) {
   1127  MutexAutoLock lock(mMutex);
   1128 
   1129  if (aBackingScale != mBackingScale) {
   1130    mBackingScale = aBackingScale;
   1131    ForAllRepresentations(
   1132        [&](Representation& r) { r.mMutatedBackingScale = true; });
   1133  }
   1134 }
   1135 
   1136 bool NativeLayerCA::IsOpaque() {
   1137  // mIsOpaque is const, so no need for a lock.
   1138  return mIsOpaque;
   1139 }
   1140 
   1141 void NativeLayerCA::SetClipRect(const Maybe<gfx::IntRect>& aClipRect) {
   1142  MutexAutoLock lock(mMutex);
   1143 
   1144  if (aClipRect != mClipRect) {
   1145    mClipRect = aClipRect;
   1146    ForAllRepresentations(
   1147        [&](Representation& r) { r.mMutatedClipRect = true; });
   1148  }
   1149 }
   1150 
   1151 Maybe<gfx::IntRect> NativeLayerCA::ClipRect() {
   1152  MutexAutoLock lock(mMutex);
   1153  return mClipRect;
   1154 }
   1155 
   1156 void NativeLayerCA::SetRoundedClipRect(const Maybe<gfx::RoundedRect>& aClip) {
   1157  MutexAutoLock lock(mMutex);
   1158 
   1159  if (aClip != mRoundedClipRect) {
   1160    mRoundedClipRect = aClip;
   1161    ForAllRepresentations(
   1162        [&](Representation& r) { r.mMutatedRoundedClipRect = true; });
   1163  }
   1164 }
   1165 
   1166 Maybe<gfx::RoundedRect> NativeLayerCA::RoundedClipRect() {
   1167  MutexAutoLock lock(mMutex);
   1168  return mRoundedClipRect;
   1169 }
   1170 
   1171 void NativeLayerCA::DumpLayer(std::ostream& aOutputStream) {
   1172  MutexAutoLock lock(mMutex);
   1173 
   1174  IntSize surfaceSize = mSize;
   1175  IntRect displayRect = mDisplayRect;
   1176  bool surfaceIsFlipped = mSurfaceIsFlipped;
   1177  if (mSurfaceHandler) {
   1178    surfaceSize = mSurfaceHandler->Size();
   1179    displayRect = mSurfaceHandler->DisplayRect();
   1180    surfaceIsFlipped = mSurfaceHandler->SurfaceIsFlipped();
   1181  }
   1182 
   1183  Maybe<CGRect> scaledClipRect =
   1184      CalculateClipGeometry(surfaceSize, mPosition, mTransform, displayRect,
   1185                            mClipRect, mBackingScale);
   1186 
   1187  CGRect useClipRect;
   1188  if (scaledClipRect.isSome()) {
   1189    useClipRect = *scaledClipRect;
   1190  } else {
   1191    useClipRect = CGRectZero;
   1192  }
   1193 
   1194  aOutputStream << "<div style=\"";
   1195  aOutputStream << "position: absolute; ";
   1196  aOutputStream << "left: " << useClipRect.origin.x << "px; ";
   1197  aOutputStream << "top: " << useClipRect.origin.y << "px; ";
   1198  aOutputStream << "width: " << useClipRect.size.width << "px; ";
   1199  aOutputStream << "height: " << useClipRect.size.height << "px; ";
   1200 
   1201  if (scaledClipRect.isSome()) {
   1202    aOutputStream << "overflow: hidden; ";
   1203  }
   1204 
   1205  if (mColor) {
   1206    aOutputStream << "background: rgb(" << mColor->r * 255.0f << " "
   1207                  << mColor->g * 255.0f << " " << mColor->b * 255.0f
   1208                  << "); opacity: " << mColor->a << "; ";
   1209 
   1210    // That's all we need for color layers. We don't need to specify an image.
   1211    aOutputStream << "\"/></div>\n";
   1212    return;
   1213  }
   1214 
   1215  aOutputStream << "\">";
   1216 
   1217  auto size = gfx::Size(surfaceSize) / mBackingScale;
   1218 
   1219  aOutputStream << "<img style=\"";
   1220  aOutputStream << "width: " << size.width << "px; ";
   1221  aOutputStream << "height: " << size.height << "px; ";
   1222 
   1223  if (mSamplingFilter == gfx::SamplingFilter::POINT) {
   1224    aOutputStream << "image-rendering: crisp-edges; ";
   1225  }
   1226 
   1227  Matrix4x4 transform = mTransform;
   1228  transform.PreTranslate(mPosition.x, mPosition.y, 0);
   1229  transform.PostTranslate((-useClipRect.origin.x * mBackingScale),
   1230                          (-useClipRect.origin.y * mBackingScale), 0);
   1231 
   1232  if (surfaceIsFlipped) {
   1233    transform.PreTranslate(0, surfaceSize.height, 0).PreScale(1, -1, 1);
   1234  }
   1235 
   1236  if (!transform.IsIdentity()) {
   1237    const auto& m = transform;
   1238    aOutputStream << "transform-origin: top left; ";
   1239    aOutputStream << "transform: matrix3d(";
   1240    aOutputStream << m._11 << ", " << m._12 << ", " << m._13 << ", " << m._14
   1241                  << ", ";
   1242    aOutputStream << m._21 << ", " << m._22 << ", " << m._23 << ", " << m._24
   1243                  << ", ";
   1244    aOutputStream << m._31 << ", " << m._32 << ", " << m._33 << ", " << m._34
   1245                  << ", ";
   1246    aOutputStream << m._41 / mBackingScale << ", " << m._42 / mBackingScale
   1247                  << ", " << m._43 << ", " << m._44;
   1248    aOutputStream << "); ";
   1249  }
   1250  aOutputStream << "\" ";
   1251 
   1252  CFTypeRefPtr<IOSurfaceRef> surface;
   1253  if (mSurfaceToPresent) {
   1254    surface = mSurfaceToPresent;
   1255    aOutputStream << "alt=\"presented surface 0x" << std::hex
   1256                  << int(IOSurfaceGetID(surface.get())) << "\" ";
   1257  } else if (mSurfaceHandler) {
   1258    if (auto frontSurface = mSurfaceHandler->FrontSurface()) {
   1259      surface = frontSurface->mSurface;
   1260      aOutputStream << "alt=\"regular surface 0x" << std::hex
   1261                    << int(IOSurfaceGetID(surface.get())) << "\" ";
   1262    }
   1263  } else if (mTextureHost) {
   1264    surface = mTextureHost->GetSurface()->GetIOSurfaceRef();
   1265    aOutputStream << "alt=\"TextureHost surface 0x" << std::hex
   1266                  << int(IOSurfaceGetID(surface.get())) << "\" ";
   1267  }
   1268  if (!surface) {
   1269    aOutputStream << "alt=\"no surface 0x\" ";
   1270  }
   1271 
   1272  aOutputStream << "src=\"";
   1273 
   1274  if (surface) {
   1275    // Attempt to render the surface as a PNG. Skia can do this for RGB
   1276    // surfaces.
   1277    RefPtr<MacIOSurface> surf = new MacIOSurface(surface);
   1278    if (surf->Lock(true)) {
   1279      SurfaceFormat format = surf->GetFormat();
   1280      if (format == SurfaceFormat::B8G8R8A8 ||
   1281          format == SurfaceFormat::B8G8R8X8) {
   1282        RefPtr<gfx::DrawTarget> dt =
   1283            surf->GetAsDrawTargetLocked(gfx::BackendType::SKIA);
   1284        if (dt) {
   1285          RefPtr<gfx::SourceSurface> sourceSurf = dt->Snapshot();
   1286          nsCString dataUrl;
   1287          gfxUtils::EncodeSourceSurface(sourceSurf, ImageType::PNG, u""_ns,
   1288                                        gfxUtils::eDataURIEncode, nullptr,
   1289                                        &dataUrl);
   1290          aOutputStream << dataUrl.get();
   1291        }
   1292      }
   1293      surf->Unlock(true);
   1294    }
   1295  }
   1296 
   1297  aOutputStream << "\"/></div>\n";
   1298 }
   1299 
   1300 gfx::IntRect NativeLayerCA::CurrentSurfaceDisplayRect() {
   1301  MutexAutoLock lock(mMutex);
   1302  if (mSurfaceHandler) {
   1303    return mSurfaceHandler->DisplayRect();
   1304  }
   1305  return mDisplayRect;
   1306 }
   1307 
   1308 void NativeLayerCA::SetDisplayRect(const gfx::IntRect& aDisplayRect) {
   1309  MutexAutoLock lock(mMutex);
   1310  MOZ_ASSERT(!mSurfaceHandler, "Setting display rect will have no effect.");
   1311  if (!mDisplayRect.IsEqualInterior(aDisplayRect)) {
   1312    mDisplayRect = aDisplayRect;
   1313 
   1314    ForAllRepresentations(
   1315        [&](Representation& r) { r.mMutatedDisplayRect = true; });
   1316  }
   1317 }
   1318 
   1319 void NativeLayerCA::SetSurfaceToPresent(CFTypeRefPtr<IOSurfaceRef> aSurfaceRef,
   1320                                        gfx::IntSize& aSize, bool aIsDRM,
   1321                                        bool aIsHDR) {
   1322  MutexAutoLock lock(mMutex);
   1323  MOZ_ASSERT(!mSurfaceHandler,
   1324             "Shouldn't call this for layers that manage their own surfaces.");
   1325  MOZ_ASSERT(!mTextureHost,
   1326             "Shouldn't call this for layers that get external surfaces.");
   1327 
   1328  bool changedSurface = (mSurfaceToPresent != aSurfaceRef);
   1329  if (changedSurface) {
   1330    if (mSurfaceToPresent) {
   1331      IOSurfaceDecrementUseCount(mSurfaceToPresent.get());
   1332    }
   1333    mSurfaceToPresent = aSurfaceRef;
   1334    if (mSurfaceToPresent) {
   1335      IOSurfaceIncrementUseCount(mSurfaceToPresent.get());
   1336    }
   1337  }
   1338 
   1339  bool changedSize = (mSize != aSize);
   1340  mSize = aSize;
   1341 
   1342  // Figure out if the surface is a video.
   1343  if (mSurfaceToPresent) {
   1344    auto pixelFormat = IOSurfaceGetPixelFormat(mSurfaceToPresent.get());
   1345    bool hasAlpha = !mIsOpaque;
   1346    auto surfaceFormat =
   1347        MacIOSurface::SurfaceFormatForPixelFormat(pixelFormat, hasAlpha);
   1348    mTextureHostIsVideo = gfx::Info(surfaceFormat)->isYuv;
   1349  } else {
   1350    mTextureHostIsVideo = false;
   1351  }
   1352 
   1353  bool changedIsDRM = (mIsDRM != aIsDRM);
   1354  mIsDRM = aIsDRM;
   1355 
   1356  mIsHDR = aIsHDR;
   1357 
   1358  bool specializeVideo = ShouldSpecializeVideo(lock);
   1359  bool changedSpecializeVideo = (mSpecializeVideo != specializeVideo);
   1360  mSpecializeVideo = specializeVideo;
   1361 
   1362 #ifdef NIGHTLY_BUILD
   1363  if (changedSpecializeVideo &&
   1364      StaticPrefs::gfx_core_animation_specialize_video_log()) {
   1365    NSLog(
   1366        @"VIDEO_LOG: SetSurfaceToPresent: %p is forcing a video layer rebuild.",
   1367        this);
   1368  }
   1369 #endif
   1370 
   1371  ForAllRepresentations([&](Representation& r) {
   1372    r.mMutatedFrontSurface |= changedSurface;
   1373    r.mMutatedSize |= changedSize;
   1374    r.mMutatedSpecializeVideo |= changedSpecializeVideo;
   1375    r.mMutatedIsDRM |= changedIsDRM;
   1376  });
   1377 }
   1378 
   1379 NativeLayerCARepresentation::NativeLayerCARepresentation()
   1380    : mWrappingCALayerHasExtent(false),
   1381      mMutatedPosition(true),
   1382      mMutatedTransform(true),
   1383      mMutatedDisplayRect(true),
   1384      mMutatedClipRect(true),
   1385      mMutatedRoundedClipRect(true),
   1386      mMutatedBackingScale(true),
   1387      mMutatedSize(true),
   1388      mMutatedSurfaceIsFlipped(true),
   1389      mMutatedFrontSurface(true),
   1390      mMutatedSamplingFilter(true),
   1391      mMutatedSpecializeVideo(true),
   1392      mMutatedIsDRM(true) {}
   1393 
   1394 NativeLayerCARepresentation::~NativeLayerCARepresentation() {
   1395  [mContentCALayer release];
   1396  [mOpaquenessTintLayer release];
   1397  [mWrappingCALayer release];
   1398  [mRoundedClipCALayer release];
   1399 }
   1400 
   1401 template <typename F>
   1402 void NativeLayerCA::HandlePartialUpdate(const MutexAutoLock& aProofOfLock,
   1403                                        const IntRect& aDisplayRect,
   1404                                        const IntRegion& aUpdateRegion,
   1405                                        F&& aCopyFn) {
   1406  MOZ_ASSERT(mSurfaceHandler);
   1407  mSurfaceHandler->HandlePartialUpdate(aDisplayRect, aUpdateRegion, aCopyFn);
   1408 }
   1409 
   1410 RefPtr<gfx::DrawTarget> NativeLayerCA::NextSurfaceAsDrawTarget(
   1411    const IntRect& aDisplayRect, const IntRegion& aUpdateRegion,
   1412    gfx::BackendType aBackendType) {
   1413  MutexAutoLock lock(mMutex);
   1414  MOZ_ASSERT(mSurfaceHandler);
   1415  return mSurfaceHandler->NextSurfaceAsDrawTarget(aDisplayRect, aUpdateRegion,
   1416                                                  aBackendType);
   1417 }
   1418 
   1419 Maybe<GLuint> NativeLayerCA::NextSurfaceAsFramebuffer(
   1420    const IntRect& aDisplayRect, const IntRegion& aUpdateRegion,
   1421    bool aNeedsDepth) {
   1422  MutexAutoLock lock(mMutex);
   1423  MOZ_ASSERT(mSurfaceHandler);
   1424  return mSurfaceHandler->NextSurfaceAsFramebuffer(aDisplayRect, aUpdateRegion,
   1425                                                   aNeedsDepth);
   1426 }
   1427 
   1428 void NativeLayerCA::NotifySurfaceReady() {
   1429  MutexAutoLock lock(mMutex);
   1430 
   1431 #ifdef NIGHTLY_BUILD
   1432  mHasEverNotifySurfaceReady = true;
   1433  MOZ_RELEASE_ASSERT(!mHasEverAttachExternalImage,
   1434                     "Shouldn't change layer type to drawn.");
   1435 #endif
   1436 
   1437  MOZ_ASSERT(mSurfaceHandler);
   1438  bool mutatedDisplayRect = mSurfaceHandler->NotifySurfaceReady();
   1439 
   1440  ForAllRepresentations([&](Representation& r) {
   1441    r.mMutatedFrontSurface = true;
   1442    r.mMutatedDisplayRect = mutatedDisplayRect;
   1443  });
   1444 }
   1445 
   1446 void NativeLayerCA::DiscardBackbuffers() {
   1447  MutexAutoLock lock(mMutex);
   1448  MOZ_ASSERT(mSurfaceHandler);
   1449  mSurfaceHandler->DiscardBackbuffers();
   1450 }
   1451 
   1452 NativeLayerCARepresentation& NativeLayerCA::GetRepresentation(
   1453    WhichRepresentation aRepresentation) {
   1454  switch (aRepresentation) {
   1455    case WhichRepresentation::ONSCREEN:
   1456      return mOnscreenRepresentation;
   1457    case WhichRepresentation::OFFSCREEN:
   1458      return mOffscreenRepresentation;
   1459  }
   1460 }
   1461 
   1462 template <typename F>
   1463 void NativeLayerCA::ForAllRepresentations(F aFn) {
   1464  aFn(mOnscreenRepresentation);
   1465  aFn(mOffscreenRepresentation);
   1466 }
   1467 
   1468 NativeLayerCA::UpdateType NativeLayerCA::HasUpdate(
   1469    WhichRepresentation aRepresentation) {
   1470  MutexAutoLock lock(mMutex);
   1471  return GetRepresentation(aRepresentation).HasUpdate(IsVideo(lock));
   1472 }
   1473 
   1474 /* static */
   1475 Maybe<CGRect> NativeLayerCA::CalculateClipGeometry(
   1476    const gfx::IntSize& aSize, const gfx::IntPoint& aPosition,
   1477    const gfx::Matrix4x4& aTransform, const gfx::IntRect& aDisplayRect,
   1478    const Maybe<gfx::IntRect>& aClipRect, float aBackingScale) {
   1479  Maybe<IntRect> clipFromDisplayRect;
   1480  if (!aDisplayRect.IsEqualInterior(IntRect({}, aSize))) {
   1481    // When the display rect is a subset of the layer, then we want to guarantee
   1482    // that no pixels outside that rect are sampled, since they might be
   1483    // uninitialized. Transforming the display rect into a post-transform clip
   1484    // only maintains this if it's an integer translation, which is all we
   1485    // support for this case currently.
   1486    MOZ_ASSERT(aTransform.Is2DIntegerTranslation());
   1487    clipFromDisplayRect = Some(RoundedToInt(
   1488        aTransform.TransformBounds(IntRectToRect(aDisplayRect + aPosition))));
   1489  }
   1490 
   1491  Maybe<gfx::IntRect> effectiveClip =
   1492      IntersectMaybeRects(aClipRect, clipFromDisplayRect);
   1493  if (!effectiveClip) {
   1494    return Nothing();
   1495  }
   1496 
   1497  return Some(CGRectMake(effectiveClip->X() / aBackingScale,
   1498                         effectiveClip->Y() / aBackingScale,
   1499                         effectiveClip->Width() / aBackingScale,
   1500                         effectiveClip->Height() / aBackingScale));
   1501 }
   1502 
   1503 bool NativeLayerCA::ApplyChanges(WhichRepresentation aRepresentation,
   1504                                 NativeLayerCA::UpdateType aUpdate,
   1505                                 bool* aMustRebuild) {
   1506  MutexAutoLock lock(mMutex);
   1507  CFTypeRefPtr<IOSurfaceRef> surface;
   1508  IntSize size = mSize;
   1509  IntRect displayRect = mDisplayRect;
   1510  bool surfaceIsFlipped = mSurfaceIsFlipped;
   1511 
   1512  if (mSurfaceToPresent) {
   1513    surface = mSurfaceToPresent;
   1514  } else if (mSurfaceHandler) {
   1515    if (auto frontSurface = mSurfaceHandler->FrontSurface()) {
   1516      surface = frontSurface->mSurface;
   1517    }
   1518    size = mSurfaceHandler->Size();
   1519    displayRect = mSurfaceHandler->DisplayRect();
   1520    surfaceIsFlipped = mSurfaceHandler->SurfaceIsFlipped();
   1521  } else if (mTextureHost) {
   1522    surface = mTextureHost->GetSurface()->GetIOSurfaceRef();
   1523  }
   1524 
   1525  auto& r = GetRepresentation(aRepresentation);
   1526  if (r.mMutatedSpecializeVideo) {
   1527    *aMustRebuild = true;
   1528  }
   1529  bool hadExtentBeforeUpdate = r.UnderlyingCALayer() != nullptr;
   1530  bool updateSucceeded = r.ApplyChanges(
   1531      aUpdate, size, mIsOpaque, mPosition, mTransform, displayRect, mClipRect,
   1532      mRoundedClipRect, mBackingScale, surfaceIsFlipped, mSamplingFilter,
   1533      mSpecializeVideo, surface, mColor, mIsDRM, IsVideo(lock));
   1534  bool hasExtentAfterUpdate = r.UnderlyingCALayer() != nullptr;
   1535  if (hasExtentAfterUpdate != hadExtentBeforeUpdate) {
   1536    *aMustRebuild = true;
   1537  }
   1538  return updateSucceeded;
   1539 }
   1540 
   1541 CALayer* NativeLayerCA::UnderlyingCALayer(WhichRepresentation aRepresentation) {
   1542  MutexAutoLock lock(mMutex);
   1543  return GetRepresentation(aRepresentation).UnderlyingCALayer();
   1544 }
   1545 
   1546 static NSString* NSStringForOSType(OSType type) {
   1547  unichar c[4];
   1548  c[0] = (type >> 24) & 0xFF;
   1549  c[1] = (type >> 16) & 0xFF;
   1550  c[2] = (type >> 8) & 0xFF;
   1551  c[3] = (type >> 0) & 0xFF;
   1552  NSString* string = [[NSString stringWithCharacters:c length:4] autorelease];
   1553  return string;
   1554 }
   1555 
   1556 /* static */ void LogSurface(IOSurfaceRef aSurfaceRef, CVPixelBufferRef aBuffer,
   1557                             CMVideoFormatDescriptionRef aFormat) {
   1558  NSLog(@"VIDEO_LOG: LogSurface...\n");
   1559 
   1560  CFDictionaryRef surfaceValues = IOSurfaceCopyAllValues(aSurfaceRef);
   1561  NSLog(@"Surface values are %@.\n", surfaceValues);
   1562  CFRelease(surfaceValues);
   1563 
   1564  if (aBuffer) {
   1565 #ifdef XP_MACOSX
   1566    CGColorSpaceRef colorSpace = CVImageBufferGetColorSpace(aBuffer);
   1567    NSLog(@"ColorSpace is %@.\n", colorSpace);
   1568 #endif
   1569 
   1570    CFDictionaryRef bufferAttachments =
   1571        CVBufferGetAttachments(aBuffer, kCVAttachmentMode_ShouldPropagate);
   1572    NSLog(@"Buffer attachments are %@.\n", bufferAttachments);
   1573  }
   1574 
   1575  if (aFormat) {
   1576    OSType codec = CMFormatDescriptionGetMediaSubType(aFormat);
   1577    NSLog(@"Codec is %@.\n", NSStringForOSType(codec));
   1578 
   1579    CFDictionaryRef extensions = CMFormatDescriptionGetExtensions(aFormat);
   1580    NSLog(@"Format extensions are %@.\n", extensions);
   1581  }
   1582 }
   1583 
   1584 bool NativeLayerCARepresentation::EnqueueSurface(IOSurfaceRef aSurfaceRef) {
   1585  MOZ_ASSERT(
   1586      [mContentCALayer isKindOfClass:[AVSampleBufferDisplayLayer class]]);
   1587  AVSampleBufferDisplayLayer* videoLayer =
   1588      (AVSampleBufferDisplayLayer*)mContentCALayer;
   1589 
   1590  if (@available(macOS 11.0, iOS 14.0, *)) {
   1591    if (videoLayer.requiresFlushToResumeDecoding) {
   1592      [videoLayer flush];
   1593    }
   1594  }
   1595 
   1596  // If the layer can't handle a new sample, note that in the log.
   1597  if (!videoLayer.readyForMoreMediaData) {
   1598 #ifdef NIGHTLY_BUILD
   1599    if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
   1600      NSLog(@"VIDEO_LOG: EnqueueSurface even though layer is not ready for "
   1601            @"more data.");
   1602    }
   1603 #endif
   1604  }
   1605 
   1606  // Convert the IOSurfaceRef into a CMSampleBuffer, so we can enqueue it in
   1607  // mContentCALayer
   1608  CVPixelBufferRef pixelBuffer = nullptr;
   1609  CVReturn cvValue = CVPixelBufferCreateWithIOSurface(
   1610      kCFAllocatorDefault, aSurfaceRef, nullptr, &pixelBuffer);
   1611  if (cvValue != kCVReturnSuccess) {
   1612    MOZ_ASSERT(pixelBuffer == nullptr,
   1613               "Failed call shouldn't allocate memory.");
   1614 #ifdef NIGHTLY_BUILD
   1615    if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
   1616      NSLog(@"VIDEO_LOG: EnqueueSurface failed on allocating pixel buffer.");
   1617    }
   1618 #endif
   1619    return false;
   1620  }
   1621 
   1622 #if defined(NIGHTLY_BUILD) && defined(XP_MACOSX)
   1623  if (StaticPrefs::gfx_core_animation_specialize_video_check_color_space()) {
   1624    // Ensure the resulting pixel buffer has a color space. If it doesn't, then
   1625    // modify the surface and create the buffer again.
   1626    CFTypeRefPtr<CGColorSpaceRef> colorSpace =
   1627        CFTypeRefPtr<CGColorSpaceRef>::WrapUnderGetRule(
   1628            CVImageBufferGetColorSpace(pixelBuffer));
   1629    if (!colorSpace) {
   1630      // Use our main display color space.
   1631      colorSpace = CFTypeRefPtr<CGColorSpaceRef>::WrapUnderCreateRule(
   1632          CGDisplayCopyColorSpace(CGMainDisplayID()));
   1633      auto colorData = CFTypeRefPtr<CFDataRef>::WrapUnderCreateRule(
   1634          CGColorSpaceCopyICCData(colorSpace.get()));
   1635      IOSurfaceSetValue(aSurfaceRef, CFSTR("IOSurfaceColorSpace"),
   1636                        colorData.get());
   1637 
   1638      // Get rid of our old pixel buffer and create a new one.
   1639      CFRelease(pixelBuffer);
   1640      cvValue = CVPixelBufferCreateWithIOSurface(
   1641          kCFAllocatorDefault, aSurfaceRef, nullptr, &pixelBuffer);
   1642      if (cvValue != kCVReturnSuccess) {
   1643        MOZ_ASSERT(pixelBuffer == nullptr,
   1644                   "Failed call shouldn't allocate memory.");
   1645        return false;
   1646      }
   1647    }
   1648    MOZ_ASSERT(CVImageBufferGetColorSpace(pixelBuffer),
   1649               "Pixel buffer should have a color space.");
   1650  }
   1651 #endif
   1652 
   1653  CFTypeRefPtr<CVPixelBufferRef> pixelBufferDeallocator =
   1654      CFTypeRefPtr<CVPixelBufferRef>::WrapUnderCreateRule(pixelBuffer);
   1655 
   1656  CMVideoFormatDescriptionRef formatDescription = nullptr;
   1657  OSStatus osValue = CMVideoFormatDescriptionCreateForImageBuffer(
   1658      kCFAllocatorDefault, pixelBuffer, &formatDescription);
   1659  if (osValue != noErr) {
   1660    MOZ_ASSERT(formatDescription == nullptr,
   1661               "Failed call shouldn't allocate memory.");
   1662 #ifdef NIGHTLY_BUILD
   1663    if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
   1664      NSLog(@"VIDEO_LOG: EnqueueSurface failed on allocating format "
   1665            @"description.");
   1666    }
   1667 #endif
   1668    return false;
   1669  }
   1670  CFTypeRefPtr<CMVideoFormatDescriptionRef> formatDescriptionDeallocator =
   1671      CFTypeRefPtr<CMVideoFormatDescriptionRef>::WrapUnderCreateRule(
   1672          formatDescription);
   1673 
   1674 #ifdef NIGHTLY_BUILD
   1675  if (mLogNextVideoSurface &&
   1676      StaticPrefs::gfx_core_animation_specialize_video_log()) {
   1677    LogSurface(aSurfaceRef, pixelBuffer, formatDescription);
   1678    mLogNextVideoSurface = false;
   1679  }
   1680 #endif
   1681 
   1682  CMSampleTimingInfo timingInfo = kCMTimingInfoInvalid;
   1683 
   1684  bool spoofTiming = false;
   1685 #ifdef NIGHTLY_BUILD
   1686  spoofTiming = StaticPrefs::gfx_core_animation_specialize_video_spoof_timing();
   1687 #endif
   1688  if (spoofTiming) {
   1689    // Since we don't have timing information for the sample, set the sample to
   1690    // play at the current timestamp.
   1691    CMTimebaseRef timebase =
   1692        [(AVSampleBufferDisplayLayer*)mContentCALayer controlTimebase];
   1693    CMTime nowTime = CMTimebaseGetTime(timebase);
   1694    timingInfo = {.presentationTimeStamp = nowTime};
   1695  }
   1696 
   1697  CMSampleBufferRef sampleBuffer = nullptr;
   1698  osValue = CMSampleBufferCreateReadyWithImageBuffer(
   1699      kCFAllocatorDefault, pixelBuffer, formatDescription, &timingInfo,
   1700      &sampleBuffer);
   1701  if (osValue != noErr) {
   1702    MOZ_ASSERT(sampleBuffer == nullptr,
   1703               "Failed call shouldn't allocate memory.");
   1704 #ifdef NIGHTLY_BUILD
   1705    if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
   1706      NSLog(@"VIDEO_LOG: EnqueueSurface failed on allocating sample buffer.");
   1707    }
   1708 #endif
   1709    return false;
   1710  }
   1711  CFTypeRefPtr<CMSampleBufferRef> sampleBufferDeallocator =
   1712      CFTypeRefPtr<CMSampleBufferRef>::WrapUnderCreateRule(sampleBuffer);
   1713 
   1714  if (!spoofTiming) {
   1715    // Since we don't have timing information for the sample, before we enqueue
   1716    // it, we attach an attribute that specifies that the sample should be
   1717    // played immediately.
   1718    CFArrayRef attachmentsArray =
   1719        CMSampleBufferGetSampleAttachmentsArray(sampleBuffer, YES);
   1720    if (!attachmentsArray || CFArrayGetCount(attachmentsArray) == 0) {
   1721      // No dictionary to alter.
   1722      return false;
   1723    }
   1724    CFMutableDictionaryRef sample0Dictionary =
   1725        (__bridge CFMutableDictionaryRef)CFArrayGetValueAtIndex(
   1726            attachmentsArray, 0);
   1727    CFDictionarySetValue(sample0Dictionary,
   1728                         kCMSampleAttachmentKey_DisplayImmediately,
   1729                         kCFBooleanTrue);
   1730  }
   1731 
   1732  [videoLayer enqueueSampleBuffer:sampleBuffer];
   1733 
   1734  return true;
   1735 }
   1736 
   1737 bool NativeLayerCARepresentation::ApplyChanges(
   1738    UpdateType aUpdate, const IntSize& aSize, bool aIsOpaque,
   1739    const IntPoint& aPosition, const Matrix4x4& aTransform,
   1740    const IntRect& aDisplayRect, const Maybe<IntRect>& aClipRect,
   1741    const Maybe<gfx::RoundedRect>& aRoundedClip, float aBackingScale,
   1742    bool aSurfaceIsFlipped, gfx::SamplingFilter aSamplingFilter,
   1743    bool aSpecializeVideo, const CFTypeRefPtr<IOSurfaceRef>& aFrontSurface,
   1744    const Maybe<gfx::DeviceColor>& aColor, bool aIsDRM, bool aIsVideo) {
   1745  // If we have an OnlyVideo update, handle it and early exit.
   1746  if (aUpdate == UpdateType::OnlyVideo) {
   1747    // If we don't have any updates to do, exit early with success. This is
   1748    // important to do so that the overall OnlyVideo pass will succeed as long
   1749    // as the video layers are successful.
   1750    if (HasUpdate(true) == UpdateType::None) {
   1751      return true;
   1752    }
   1753 
   1754    MOZ_ASSERT(!mMutatedSpecializeVideo && mMutatedFrontSurface,
   1755               "Shouldn't attempt a OnlyVideo update in this case.");
   1756 
   1757    bool updateSucceeded = false;
   1758    if (aSpecializeVideo) {
   1759      IOSurfaceRef surface = aFrontSurface.get();
   1760      updateSucceeded = EnqueueSurface(surface);
   1761 
   1762      if (updateSucceeded) {
   1763        mMutatedFrontSurface = false;
   1764      } else {
   1765        // Set mMutatedSpecializeVideo, which will ensure that the next update
   1766        // will rebuild the video layer.
   1767        mMutatedSpecializeVideo = true;
   1768 #ifdef NIGHTLY_BUILD
   1769        if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
   1770          NSLog(@"VIDEO_LOG: EnqueueSurface failed in OnlyVideo update.");
   1771        }
   1772 #endif
   1773      }
   1774    }
   1775 
   1776    return updateSucceeded;
   1777  }
   1778 
   1779  MOZ_ASSERT(aUpdate == UpdateType::All);
   1780 
   1781  if (mWrappingCALayer && mMutatedSpecializeVideo) {
   1782    // Since specialize video changes the way we construct our wrapping and
   1783    // content layers, we have to scrap them if this value has changed.
   1784 #ifdef NIGHTLY_BUILD
   1785    if (aIsVideo && StaticPrefs::gfx_core_animation_specialize_video_log()) {
   1786      NSLog(@"VIDEO_LOG: Scrapping existing video layer.");
   1787    }
   1788 #endif
   1789    [mContentCALayer release];
   1790    mContentCALayer = nil;
   1791    [mOpaquenessTintLayer release];
   1792    mOpaquenessTintLayer = nil;
   1793    [mWrappingCALayer release];
   1794    mWrappingCALayer = nil;
   1795    [mRoundedClipCALayer release];
   1796    mRoundedClipCALayer = nil;
   1797  }
   1798 
   1799  bool layerNeedsInitialization = false;
   1800  if (!mWrappingCALayer) {
   1801    layerNeedsInitialization = true;
   1802    mWrappingCALayer = [[CALayer layer] retain];
   1803    mWrappingCALayer.position = CGPointZero;
   1804    mWrappingCALayer.bounds = CGRectZero;
   1805    mWrappingCALayer.anchorPoint = CGPointZero;
   1806    mWrappingCALayer.contentsGravity = kCAGravityTopLeft;
   1807    mWrappingCALayer.edgeAntialiasingMask = 0;
   1808 
   1809    mRoundedClipCALayer = [[CALayer layer] retain];
   1810    mRoundedClipCALayer.position = CGPointZero;
   1811    mRoundedClipCALayer.bounds = CGRectZero;
   1812    mRoundedClipCALayer.anchorPoint = CGPointZero;
   1813    mRoundedClipCALayer.contentsGravity = kCAGravityTopLeft;
   1814    mRoundedClipCALayer.edgeAntialiasingMask = 0;
   1815    mRoundedClipCALayer.masksToBounds = NO;
   1816 
   1817    [mWrappingCALayer addSublayer:mRoundedClipCALayer];
   1818 
   1819    if (aColor) {
   1820      // Color layers set a color on the clip layer and don't get a content
   1821      // layer.
   1822      mRoundedClipCALayer.backgroundColor =
   1823          CGColorCreateForDeviceColor(*aColor);
   1824    } else {
   1825      if (aSpecializeVideo) {
   1826 #ifdef NIGHTLY_BUILD
   1827        if (aIsVideo &&
   1828            StaticPrefs::gfx_core_animation_specialize_video_log()) {
   1829          NSLog(@"VIDEO_LOG: Rebuilding video layer with "
   1830                @"AVSampleBufferDisplayLayer.");
   1831          mLogNextVideoSurface = true;
   1832        }
   1833 #endif
   1834        mContentCALayer = [[AVSampleBufferDisplayLayer layer] retain];
   1835        CMTimebaseRef timebase;
   1836 #ifdef CMTIMEBASE_USE_SOURCE_TERMINOLOGY
   1837        CMTimebaseCreateWithSourceClock(kCFAllocatorDefault,
   1838                                        CMClockGetHostTimeClock(), &timebase);
   1839 #else
   1840        CMTimebaseCreateWithMasterClock(kCFAllocatorDefault,
   1841                                        CMClockGetHostTimeClock(), &timebase);
   1842 #endif
   1843        CMTimebaseSetRate(timebase, 1.0f);
   1844        [(AVSampleBufferDisplayLayer*)mContentCALayer
   1845            setControlTimebase:timebase];
   1846        CFRelease(timebase);
   1847      } else {
   1848 #ifdef NIGHTLY_BUILD
   1849        if (aIsVideo &&
   1850            StaticPrefs::gfx_core_animation_specialize_video_log()) {
   1851          NSLog(@"VIDEO_LOG: Rebuilding video layer with CALayer.");
   1852          mLogNextVideoSurface = true;
   1853        }
   1854 #endif
   1855        mContentCALayer = [[CALayer layer] retain];
   1856      }
   1857      mContentCALayer.position = CGPointZero;
   1858      mContentCALayer.anchorPoint = CGPointZero;
   1859      mContentCALayer.contentsGravity = kCAGravityTopLeft;
   1860      mContentCALayer.contentsScale = 1;
   1861      mContentCALayer.bounds = CGRectMake(0, 0, aSize.width, aSize.height);
   1862      mContentCALayer.edgeAntialiasingMask = 0;
   1863      mContentCALayer.opaque = aIsOpaque;
   1864      if ([mContentCALayer respondsToSelector:@selector(setContentsOpaque:)]) {
   1865        // The opaque property seems to not be enough when using IOSurface
   1866        // contents. Additionally, call the private method setContentsOpaque.
   1867        [mContentCALayer setContentsOpaque:aIsOpaque];
   1868      }
   1869 
   1870      [mRoundedClipCALayer addSublayer:mContentCALayer];
   1871    }
   1872  }
   1873 
   1874  if (aSpecializeVideo && mMutatedIsDRM) {
   1875    ((AVSampleBufferDisplayLayer*)mContentCALayer).preventsCapture = aIsDRM;
   1876  }
   1877 
   1878  bool shouldTintOpaqueness = StaticPrefs::gfx_core_animation_tint_opaque();
   1879  if (shouldTintOpaqueness && !mOpaquenessTintLayer) {
   1880    mOpaquenessTintLayer = [[CALayer layer] retain];
   1881    mOpaquenessTintLayer.position = CGPointZero;
   1882    mOpaquenessTintLayer.bounds = mContentCALayer.bounds;
   1883    mOpaquenessTintLayer.anchorPoint = CGPointZero;
   1884    mOpaquenessTintLayer.contentsGravity = kCAGravityTopLeft;
   1885    if (aIsOpaque) {
   1886      mOpaquenessTintLayer.backgroundColor =
   1887          CGColorCreateGenericRGB(0, 1, 0, 0.5);
   1888    } else {
   1889      mOpaquenessTintLayer.backgroundColor =
   1890          CGColorCreateGenericRGB(1, 0, 0, 0.5);
   1891    }
   1892    [mRoundedClipCALayer addSublayer:mOpaquenessTintLayer];
   1893  } else if (!shouldTintOpaqueness && mOpaquenessTintLayer) {
   1894    [mOpaquenessTintLayer removeFromSuperlayer];
   1895    [mOpaquenessTintLayer release];
   1896    mOpaquenessTintLayer = nullptr;
   1897  }
   1898 
   1899  // CALayers have a position and a size, specified through the position and the
   1900  // bounds properties. layer.bounds.origin must always be (0, 0). A layer's
   1901  // position affects the layer's entire layer subtree. In other words, each
   1902  // layer's position is relative to its superlayer's position. We implement the
   1903  // clip rect using masksToBounds on mWrappingCALayer. So mContentCALayer's
   1904  // position is relative to the clip rect position. Note: The Core Animation
   1905  // docs on "Positioning and Sizing Sublayers" say:
   1906  //  Important: Always use integral numbers for the width and height of your
   1907  //  layer.
   1908  // We hope that this refers to integral physical pixels, and not to integral
   1909  // logical coordinates.
   1910 
   1911  if (mContentCALayer &&
   1912      (mMutatedBackingScale || mMutatedSize || layerNeedsInitialization)) {
   1913    mContentCALayer.bounds = CGRectMake(0, 0, aSize.width / aBackingScale,
   1914                                        aSize.height / aBackingScale);
   1915    if (mOpaquenessTintLayer) {
   1916      mOpaquenessTintLayer.bounds = mContentCALayer.bounds;
   1917    }
   1918    mContentCALayer.contentsScale = aBackingScale;
   1919  }
   1920 
   1921  if (mMutatedBackingScale || mMutatedPosition || mMutatedDisplayRect ||
   1922      mMutatedClipRect || mMutatedRoundedClipRect || mMutatedTransform ||
   1923      mMutatedSurfaceIsFlipped || mMutatedSize || layerNeedsInitialization) {
   1924    Maybe<CGRect> scaledClipRect = NativeLayerCA::CalculateClipGeometry(
   1925        aSize, aPosition, aTransform, aDisplayRect, aClipRect, aBackingScale);
   1926 
   1927    CGRect useClipRect;
   1928    if (scaledClipRect.isSome()) {
   1929      useClipRect = *scaledClipRect;
   1930    } else {
   1931      useClipRect = CGRectZero;
   1932    }
   1933 
   1934    mWrappingCALayer.position = useClipRect.origin;
   1935    mWrappingCALayer.bounds =
   1936        CGRectMake(0, 0, useClipRect.size.width, useClipRect.size.height);
   1937    mWrappingCALayer.masksToBounds = scaledClipRect.isSome();
   1938    mWrappingCALayerHasExtent =
   1939        scaledClipRect.isNothing() || !CGRectIsEmpty(useClipRect);
   1940 
   1941    // Default the clip rect for the rounded rect clip layer to be the
   1942    // same as the wrapping layer clip. This ensures that if it's not used,
   1943    // and the background color is applied to this layer, it draws correctly.
   1944    CGRect rrClipRect =
   1945        CGRectMake(0, 0, useClipRect.size.width, useClipRect.size.height);
   1946 
   1947    // We currently support only the easy case here (where the radii is uniform
   1948    // or zero). If a clip is supplied with non-uniform, non-zero radii we'll
   1949    // silently render incorrectly. However, WR only promotes compositor clips
   1950    // that match this criteria, so we won't hit an incorrect rendering path due
   1951    // to the higher level check.
   1952    // https://bugzilla.mozilla.org/show_bug.cgi?id=1960515#c2 has details on
   1953    // supporting the harder cases.
   1954 
   1955    // Reset the rounded clip layer params to disabled, in case they were
   1956    // mutated and don't get selected below.
   1957    mRoundedClipCALayer.cornerRadius = 0.0f;
   1958    mRoundedClipCALayer.masksToBounds = NO;
   1959    mRoundedClipCALayer.maskedCorners = 0;
   1960 
   1961    if (aRoundedClip.isSome()) {
   1962      // Select which corner(s) the rounded clip should be applied to
   1963      // Select from the corners which is the maximum radius (since we know
   1964      // they are either uniform or zero).
   1965      CACornerMask maskedCorners = 0;
   1966      auto effectiveRadius = 0.0f;
   1967      if (aRoundedClip->corners.radii[0].width > 0.0) {
   1968        maskedCorners |= kCALayerMinXMinYCorner;
   1969        effectiveRadius =
   1970            std::max(effectiveRadius, aRoundedClip->corners.radii[0].width);
   1971      }
   1972      if (aRoundedClip->corners.radii[1].width > 0.0) {
   1973        maskedCorners |= kCALayerMaxXMinYCorner;
   1974        effectiveRadius =
   1975            std::max(effectiveRadius, aRoundedClip->corners.radii[1].width);
   1976      }
   1977      if (aRoundedClip->corners.radii[2].width > 0.0) {
   1978        maskedCorners |= kCALayerMaxXMaxYCorner;
   1979        effectiveRadius =
   1980            std::max(effectiveRadius, aRoundedClip->corners.radii[2].width);
   1981      }
   1982      if (aRoundedClip->corners.radii[3].width > 0.0) {
   1983        maskedCorners |= kCALayerMinXMaxYCorner;
   1984        effectiveRadius =
   1985            std::max(effectiveRadius, aRoundedClip->corners.radii[3].width);
   1986      }
   1987 
   1988      // Only create a rounded clip mask if we had 1+ non-zero corner radius
   1989      if (maskedCorners != 0) {
   1990        rrClipRect.origin.x = aRoundedClip->rect.x / aBackingScale;
   1991        rrClipRect.origin.y = aRoundedClip->rect.y / aBackingScale;
   1992        rrClipRect.size.width = aRoundedClip->rect.width / aBackingScale;
   1993        rrClipRect.size.height = aRoundedClip->rect.height / aBackingScale;
   1994 
   1995        // Move in to local space relative to the parent wrapping layer
   1996        rrClipRect.origin.x -= useClipRect.origin.x;
   1997        rrClipRect.origin.y -= useClipRect.origin.y;
   1998 
   1999        mRoundedClipCALayer.cornerRadius = effectiveRadius / aBackingScale;
   2000        mRoundedClipCALayer.masksToBounds = YES;
   2001        mRoundedClipCALayer.maskedCorners = maskedCorners;
   2002      }
   2003    }
   2004 
   2005    // Position the rounded clip layer in the right space
   2006    mRoundedClipCALayer.position = rrClipRect.origin;
   2007    mRoundedClipCALayer.bounds =
   2008        CGRectMake(0, 0, rrClipRect.size.width, rrClipRect.size.height);
   2009 
   2010    if (mContentCALayer) {
   2011      Matrix4x4 transform = aTransform;
   2012      transform.PreTranslate(aPosition.x, aPosition.y, 0);
   2013      transform.PostTranslate(
   2014          ((-useClipRect.origin.x - rrClipRect.origin.x) * aBackingScale),
   2015          ((-useClipRect.origin.y - rrClipRect.origin.y) * aBackingScale), 0);
   2016 
   2017      if (aSurfaceIsFlipped) {
   2018        transform.PreTranslate(0, aSize.height, 0).PreScale(1, -1, 1);
   2019      }
   2020 
   2021      CATransform3D transformCA{transform._11,
   2022                                transform._12,
   2023                                transform._13,
   2024                                transform._14,
   2025                                transform._21,
   2026                                transform._22,
   2027                                transform._23,
   2028                                transform._24,
   2029                                transform._31,
   2030                                transform._32,
   2031                                transform._33,
   2032                                transform._34,
   2033                                transform._41 / aBackingScale,
   2034                                transform._42 / aBackingScale,
   2035                                transform._43,
   2036                                transform._44};
   2037      mContentCALayer.transform = transformCA;
   2038      if (mOpaquenessTintLayer) {
   2039        mOpaquenessTintLayer.transform = mContentCALayer.transform;
   2040      }
   2041    }
   2042  }
   2043 
   2044  if (mContentCALayer && (mMutatedSamplingFilter || layerNeedsInitialization)) {
   2045    if (aSamplingFilter == gfx::SamplingFilter::POINT) {
   2046      mContentCALayer.minificationFilter = kCAFilterNearest;
   2047      mContentCALayer.magnificationFilter = kCAFilterNearest;
   2048    } else {
   2049      mContentCALayer.minificationFilter = kCAFilterLinear;
   2050      mContentCALayer.magnificationFilter = kCAFilterLinear;
   2051    }
   2052  }
   2053 
   2054  if (mMutatedFrontSurface) {
   2055    // This is handled last because a video update could fail, causing us to
   2056    // early exit, leaving the mutation bits untouched. We do this so that the
   2057    // *next* update will clear the video layer and setup a regular layer.
   2058 
   2059    IOSurfaceRef surface = aFrontSurface.get();
   2060    if (aSpecializeVideo) {
   2061      // If we just rebuilt our layer, ensure the first frame is visible by
   2062      // forcing the layer contents to display that frame. Our call to
   2063      // enqueueSampleBuffer will handle future async updates to the layer;
   2064      // buffers queued with enqueueSampleBuffer overwrite the layer contents.
   2065      if (layerNeedsInitialization) {
   2066        mContentCALayer.contents = (id)surface;
   2067      }
   2068 
   2069      // Attempt to enqueue this as a video frame. If we fail, we'll rebuild
   2070      // our video layer in the next update.
   2071      bool isEnqueued = EnqueueSurface(surface);
   2072      if (!isEnqueued) {
   2073        // Set mMutatedSpecializeVideo, which will ensure that the next update
   2074        // will rebuild the video layer.
   2075        mMutatedSpecializeVideo = true;
   2076 #ifdef NIGHTLY_BUILD
   2077        if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
   2078          NSLog(@"VIDEO_LOG: EnqueueSurface failed in All update.");
   2079        }
   2080 #endif
   2081        return false;
   2082      }
   2083    } else {
   2084 #ifdef NIGHTLY_BUILD
   2085      if (mLogNextVideoSurface &&
   2086          StaticPrefs::gfx_core_animation_specialize_video_log()) {
   2087        LogSurface(surface, nullptr, nullptr);
   2088        mLogNextVideoSurface = false;
   2089      }
   2090 #endif
   2091      mContentCALayer.contents = (id)surface;
   2092    }
   2093  }
   2094 
   2095  mMutatedPosition = false;
   2096  mMutatedTransform = false;
   2097  mMutatedBackingScale = false;
   2098  mMutatedSize = false;
   2099  mMutatedSurfaceIsFlipped = false;
   2100  mMutatedDisplayRect = false;
   2101  mMutatedClipRect = false;
   2102  mMutatedRoundedClipRect = false;
   2103  mMutatedFrontSurface = false;
   2104  mMutatedSamplingFilter = false;
   2105  mMutatedSpecializeVideo = false;
   2106  mMutatedIsDRM = false;
   2107 
   2108  return true;
   2109 }
   2110 
   2111 NativeLayerCA::UpdateType NativeLayerCARepresentation::HasUpdate(
   2112    bool aIsVideo) {
   2113  if (!mWrappingCALayer) {
   2114    return UpdateType::All;
   2115  }
   2116 
   2117  // This check intentionally skips mMutatedFrontSurface. We'll check it later
   2118  // to see if we can attempt an OnlyVideo update.
   2119  if (mMutatedPosition || mMutatedTransform || mMutatedDisplayRect ||
   2120      mMutatedClipRect || mMutatedRoundedClipRect || mMutatedBackingScale ||
   2121      mMutatedSize || mMutatedSurfaceIsFlipped || mMutatedSamplingFilter ||
   2122      mMutatedSpecializeVideo || mMutatedIsDRM) {
   2123    return UpdateType::All;
   2124  }
   2125 
   2126  // Check if we should try an OnlyVideo update. We know from the above check
   2127  // that our specialize video is stable (we don't know what value we'll
   2128  // receive, though), so we just have to check that we have a surface to
   2129  // display.
   2130  if (mMutatedFrontSurface) {
   2131    return (aIsVideo ? UpdateType::OnlyVideo : UpdateType::All);
   2132  }
   2133 
   2134  return UpdateType::None;
   2135 }
   2136 
   2137 bool DownscaleTargetNLRS::DownscaleFrom(
   2138    profiler_screenshots::RenderSource* aSource, const IntRect& aSourceRect,
   2139    const IntRect& aDestRect) {
   2140  mGL->BlitHelper()->BlitFramebufferToFramebuffer(
   2141      static_cast<RenderSourceNLRS*>(aSource)->FB().mFB,
   2142      mRenderSource->FB().mFB, aSourceRect, aDestRect, LOCAL_GL_LINEAR);
   2143 
   2144  return true;
   2145 }
   2146 
   2147 void AsyncReadbackBufferNLRS::CopyFrom(
   2148    profiler_screenshots::RenderSource* aSource) {
   2149  IntSize size = aSource->Size();
   2150  MOZ_RELEASE_ASSERT(Size() == size);
   2151 
   2152  gl::ScopedPackState scopedPackState(mGL);
   2153  mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, mBufferHandle);
   2154  mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
   2155  const gl::ScopedBindFramebuffer bindFB(
   2156      mGL, static_cast<RenderSourceNLRS*>(aSource)->FB().mFB);
   2157  mGL->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA,
   2158                   LOCAL_GL_UNSIGNED_BYTE, 0);
   2159 }
   2160 
   2161 bool AsyncReadbackBufferNLRS::MapAndCopyInto(DataSourceSurface* aSurface,
   2162                                             const IntSize& aReadSize) {
   2163  MOZ_RELEASE_ASSERT(aReadSize <= aSurface->GetSize());
   2164 
   2165  if (!mGL || !mGL->MakeCurrent()) {
   2166    return false;
   2167  }
   2168 
   2169  gl::ScopedPackState scopedPackState(mGL);
   2170  mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, mBufferHandle);
   2171  mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
   2172 
   2173  const uint8_t* srcData = nullptr;
   2174  if (mGL->IsSupported(gl::GLFeature::map_buffer_range)) {
   2175    srcData = static_cast<uint8_t*>(mGL->fMapBufferRange(
   2176        LOCAL_GL_PIXEL_PACK_BUFFER, 0, aReadSize.height * aReadSize.width * 4,
   2177        LOCAL_GL_MAP_READ_BIT));
   2178  } else {
   2179    srcData = static_cast<uint8_t*>(
   2180        mGL->fMapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, LOCAL_GL_READ_ONLY));
   2181  }
   2182 
   2183  if (!srcData) {
   2184    return false;
   2185  }
   2186 
   2187  int32_t srcStride = mSize.width * 4;  // Bind() sets an alignment of 1
   2188  DataSourceSurface::ScopedMap map(aSurface, DataSourceSurface::WRITE);
   2189  uint8_t* destData = map.GetData();
   2190  int32_t destStride = map.GetStride();
   2191  SurfaceFormat destFormat = aSurface->GetFormat();
   2192  for (int32_t destRow = 0; destRow < aReadSize.height; destRow++) {
   2193    // Turn srcData upside down during the copy.
   2194    int32_t srcRow = aReadSize.height - 1 - destRow;
   2195    const uint8_t* src = &srcData[srcRow * srcStride];
   2196    uint8_t* dest = &destData[destRow * destStride];
   2197    SwizzleData(src, srcStride, SurfaceFormat::R8G8B8A8, dest, destStride,
   2198                destFormat, IntSize(aReadSize.width, 1));
   2199  }
   2200 
   2201  mGL->fUnmapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER);
   2202 
   2203  return true;
   2204 }
   2205 
   2206 AsyncReadbackBufferNLRS::~AsyncReadbackBufferNLRS() {
   2207  if (mGL && mGL->MakeCurrent()) {
   2208    mGL->fDeleteBuffers(1, &mBufferHandle);
   2209  }
   2210 }
   2211 
   2212 }  // namespace layers
   2213 }  // namespace mozilla