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