Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/layers/NativeLayerCA.h"
#ifdef XP_MACOSX
# import <AppKit/NSAnimationContext.h>
# import <AppKit/NSColor.h>
# import <OpenGL/gl.h>
#endif
#import <AVFoundation/AVFoundation.h>
#import <QuartzCore/QuartzCore.h>
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include <utility>
#include "gfxUtils.h"
#include "GLBlitHelper.h"
#ifdef XP_MACOSX
# include "GLContextCGL.h"
#else
# include "GLContextEAGL.h"
#endif
#include "GLContextProvider.h"
#include "MozFramebuffer.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/layers/ScreenshotGrabber.h"
#include "mozilla/layers/SurfacePoolCA.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/Telemetry.h"
#include "mozilla/webrender/RenderMacIOSurfaceTextureHost.h"
#include "ScopedGLHelpers.h"
@interface CALayer (PrivateSetContentsOpaque)
- (void)setContentsOpaque:(BOOL)opaque;
@end
namespace mozilla {
namespace layers {
using gfx::DataSourceSurface;
using gfx::IntPoint;
using gfx::IntRect;
using gfx::IntRegion;
using gfx::IntSize;
using gfx::Matrix4x4;
using gfx::SurfaceFormat;
using gl::GLContext;
#ifdef XP_MACOSX
using gl::GLContextCGL;
#endif
static Maybe<Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER>
VideoLowPowerTypeToTelemetryType(VideoLowPowerType aVideoLowPower) {
switch (aVideoLowPower) {
case VideoLowPowerType::LowPower:
return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::LowPower);
case VideoLowPowerType::FailMultipleVideo:
return Some(
Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailMultipleVideo);
case VideoLowPowerType::FailWindowed:
return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailWindowed);
case VideoLowPowerType::FailOverlaid:
return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailOverlaid);
case VideoLowPowerType::FailBacking:
return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailBacking);
case VideoLowPowerType::FailMacOSVersion:
return Some(
Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailMacOSVersion);
case VideoLowPowerType::FailPref:
return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailPref);
case VideoLowPowerType::FailSurface:
return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailSurface);
case VideoLowPowerType::FailEnqueue:
return Some(Telemetry::LABELS_GFX_MACOS_VIDEO_LOW_POWER::FailEnqueue);
default:
return Nothing();
}
}
static void EmitTelemetryForVideoLowPower(VideoLowPowerType aVideoLowPower) {
auto telemetryValue = VideoLowPowerTypeToTelemetryType(aVideoLowPower);
if (telemetryValue.isSome()) {
Telemetry::AccumulateCategorical(telemetryValue.value());
}
}
// Utility classes for NativeLayerRootSnapshotter (NLRS) profiler screenshots.
class RenderSourceNLRS : public profiler_screenshots::RenderSource {
public:
explicit RenderSourceNLRS(UniquePtr<gl::MozFramebuffer>&& aFramebuffer)
: RenderSource(aFramebuffer->mSize),
mFramebuffer(std::move(aFramebuffer)) {}
auto& FB() { return *mFramebuffer; }
protected:
UniquePtr<gl::MozFramebuffer> mFramebuffer;
};
class DownscaleTargetNLRS : public profiler_screenshots::DownscaleTarget {
public:
DownscaleTargetNLRS(gl::GLContext* aGL,
UniquePtr<gl::MozFramebuffer>&& aFramebuffer)
: profiler_screenshots::DownscaleTarget(aFramebuffer->mSize),
mGL(aGL),
mRenderSource(new RenderSourceNLRS(std::move(aFramebuffer))) {}
already_AddRefed<profiler_screenshots::RenderSource> AsRenderSource()
override {
return do_AddRef(mRenderSource);
};
bool DownscaleFrom(profiler_screenshots::RenderSource* aSource,
const IntRect& aSourceRect,
const IntRect& aDestRect) override;
protected:
RefPtr<gl::GLContext> mGL;
RefPtr<RenderSourceNLRS> mRenderSource;
};
class AsyncReadbackBufferNLRS
: public profiler_screenshots::AsyncReadbackBuffer {
public:
AsyncReadbackBufferNLRS(gl::GLContext* aGL, const IntSize& aSize,
GLuint aBufferHandle)
: profiler_screenshots::AsyncReadbackBuffer(aSize),
mGL(aGL),
mBufferHandle(aBufferHandle) {}
void CopyFrom(profiler_screenshots::RenderSource* aSource) override;
bool MapAndCopyInto(DataSourceSurface* aSurface,
const IntSize& aReadSize) override;
protected:
virtual ~AsyncReadbackBufferNLRS();
RefPtr<gl::GLContext> mGL;
GLuint mBufferHandle = 0;
};
// Needs to be on the stack whenever CALayer mutations are performed.
// (Mutating CALayers outside of a transaction can result in permanently stuck
// rendering, because such mutations create an implicit transaction which never
// auto-commits if the current thread does not have a native runloop.) Uses
// NSAnimationContext, which wraps CATransaction with additional off-main-thread
// protection, see bug 1585523.
struct MOZ_STACK_CLASS AutoCATransaction final {
AutoCATransaction() {
#ifdef XP_MACOSX
[NSAnimationContext beginGrouping];
#else
[CATransaction begin];
#endif
// By default, mutating a CALayer property triggers an animation which
// smoothly transitions the property to the new value. We don't need these
// animations, and this call turns them off:
[CATransaction setDisableActions:YES];
}
~AutoCATransaction() {
#ifdef XP_MACOSX
[NSAnimationContext endGrouping];
#else
[CATransaction commit];
#endif
}
};
/* static */ already_AddRefed<NativeLayerRootCA>
NativeLayerRootCA::CreateForCALayer(CALayer* aLayer) {
RefPtr<NativeLayerRootCA> layerRoot = new NativeLayerRootCA(aLayer);
return layerRoot.forget();
}
// Returns an autoreleased CALayer* object.
static CALayer* MakeOffscreenRootCALayer() {
// This layer should behave similarly to the backing layer of a flipped
// NSView. It will never be rendered on the screen and it will never be
// attached to an NSView's layer; instead, it will be the root layer of a
// "local" CAContext. Setting geometryFlipped to YES causes the orientation of
// descendant CALayers' contents (such as IOSurfaces) to be consistent with
// what happens in a layer subtree that is attached to a flipped NSView.
// Setting it to NO would cause the surfaces in individual leaf layers to
// render upside down (rather than just flipping the entire layer tree upside
// down).
AutoCATransaction transaction;
CALayer* layer = [CALayer layer];
layer.position = CGPointZero;
layer.bounds = CGRectZero;
layer.anchorPoint = CGPointZero;
layer.contentsGravity = kCAGravityTopLeft;
layer.masksToBounds = YES;
layer.geometryFlipped = YES;
return layer;
}
NativeLayerRootCA::NativeLayerRootCA(CALayer* aLayer)
: mMutex("NativeLayerRootCA"),
mOnscreenRepresentation(aLayer),
mOffscreenRepresentation(MakeOffscreenRootCALayer()) {}
NativeLayerRootCA::~NativeLayerRootCA() {
MOZ_RELEASE_ASSERT(
mSublayers.IsEmpty(),
"Please clear all layers before destroying the layer root.");
}
already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayer(
const IntSize& aSize, bool aIsOpaque,
SurfacePoolHandle* aSurfacePoolHandle) {
RefPtr<NativeLayer> layer = new NativeLayerCA(
aSize, aIsOpaque, aSurfacePoolHandle->AsSurfacePoolHandleCA());
return layer.forget();
}
already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayerForExternalTexture(
bool aIsOpaque) {
RefPtr<NativeLayer> layer = new NativeLayerCA(aIsOpaque);
return layer.forget();
}
already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayerForColor(
gfx::DeviceColor aColor) {
RefPtr<NativeLayer> layer = new NativeLayerCA(aColor);
return layer.forget();
}
void NativeLayerRootCA::AppendLayer(NativeLayer* aLayer) {
MutexAutoLock lock(mMutex);
RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA();
MOZ_RELEASE_ASSERT(layerCA);
mSublayers.AppendElement(layerCA);
layerCA->SetBackingScale(mBackingScale);
layerCA->SetRootWindowIsFullscreen(mWindowIsFullscreen);
ForAllRepresentations(
[&](Representation& r) { r.mMutatedLayerStructure = true; });
}
void NativeLayerRootCA::RemoveLayer(NativeLayer* aLayer) {
MutexAutoLock lock(mMutex);
RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA();
MOZ_RELEASE_ASSERT(layerCA);
mSublayers.RemoveElement(layerCA);
ForAllRepresentations(
[&](Representation& r) { r.mMutatedLayerStructure = true; });
}
void NativeLayerRootCA::SetLayers(
const nsTArray<RefPtr<NativeLayer>>& aLayers) {
MutexAutoLock lock(mMutex);
// Ideally, we'd just be able to do mSublayers = std::move(aLayers).
// However, aLayers has a different type: it carries NativeLayer objects,
// whereas mSublayers carries NativeLayerCA objects, so we have to downcast
// all the elements first. There's one other reason to look at all the
// elements in aLayers first: We need to make sure any new layers know about
// our current backing scale.
nsTArray<RefPtr<NativeLayerCA>> layersCA(aLayers.Length());
for (auto& layer : aLayers) {
RefPtr<NativeLayerCA> layerCA = layer->AsNativeLayerCA();
MOZ_RELEASE_ASSERT(layerCA);
layerCA->SetBackingScale(mBackingScale);
layerCA->SetRootWindowIsFullscreen(mWindowIsFullscreen);
layersCA.AppendElement(std::move(layerCA));
}
if (layersCA != mSublayers) {
mSublayers = std::move(layersCA);
ForAllRepresentations(
[&](Representation& r) { r.mMutatedLayerStructure = true; });
}
}
void NativeLayerRootCA::SetBackingScale(float aBackingScale) {
MutexAutoLock lock(mMutex);
mBackingScale = aBackingScale;
for (auto layer : mSublayers) {
layer->SetBackingScale(aBackingScale);
}
}
float NativeLayerRootCA::BackingScale() {
MutexAutoLock lock(mMutex);
return mBackingScale;
}
void NativeLayerRootCA::SuspendOffMainThreadCommits() {
MutexAutoLock lock(mMutex);
mOffMainThreadCommitsSuspended = true;
}
bool NativeLayerRootCA::UnsuspendOffMainThreadCommits() {
MutexAutoLock lock(mMutex);
mOffMainThreadCommitsSuspended = false;
return mCommitPending;
}
bool NativeLayerRootCA::AreOffMainThreadCommitsSuspended() {
MutexAutoLock lock(mMutex);
return mOffMainThreadCommitsSuspended;
}
bool NativeLayerRootCA::CommitToScreen() {
{
MutexAutoLock lock(mMutex);
if (!NS_IsMainThread() && mOffMainThreadCommitsSuspended) {
mCommitPending = true;
return false;
}
mOnscreenRepresentation.Commit(WhichRepresentation::ONSCREEN, mSublayers,
mWindowIsFullscreen);
mCommitPending = false;
if (StaticPrefs::gfx_webrender_debug_dump_native_layer_tree_to_file()) {
static uint32_t sFrameID = 0;
uint32_t frameID = sFrameID++;
NSString* dirPath =
[NSString stringWithFormat:@"%@/Desktop/nativelayerdumps-%d",
NSHomeDirectory(), getpid()];
if ([NSFileManager.defaultManager createDirectoryAtPath:dirPath
withIntermediateDirectories:YES
attributes:nil
error:nullptr]) {
NSString* filename =
[NSString stringWithFormat:@"frame-%d.html", frameID];
NSString* filePath = [dirPath stringByAppendingPathComponent:filename];
DumpLayerTreeToFile([filePath UTF8String]);
} else {
NSLog(@"Failed to create directory %@", dirPath);
}
}
// Decide if we are going to emit telemetry about video low power on this
// commit.
static const int32_t TELEMETRY_COMMIT_PERIOD =
StaticPrefs::gfx_core_animation_low_power_telemetry_frames_AtStartup();
mTelemetryCommitCount =
(mTelemetryCommitCount + 1) % TELEMETRY_COMMIT_PERIOD;
if (mTelemetryCommitCount == 0) {
// Figure out if we are hitting video low power mode.
VideoLowPowerType videoLowPower = CheckVideoLowPower(lock);
EmitTelemetryForVideoLowPower(videoLowPower);
}
}
return true;
}
UniquePtr<NativeLayerRootSnapshotter> NativeLayerRootCA::CreateSnapshotter() {
#ifdef XP_MACOSX
MutexAutoLock lock(mMutex);
MOZ_RELEASE_ASSERT(!mWeakSnapshotter,
"No NativeLayerRootSnapshotter for this NativeLayerRoot "
"should exist when this is called");
auto cr = NativeLayerRootSnapshotterCA::Create(
this, mOffscreenRepresentation.mRootCALayer);
if (cr) {
mWeakSnapshotter = cr.get();
}
return cr;
#else
return nullptr;
#endif
}
#ifdef XP_MACOSX
void NativeLayerRootCA::OnNativeLayerRootSnapshotterDestroyed(
NativeLayerRootSnapshotterCA* aNativeLayerRootSnapshotter) {
MutexAutoLock lock(mMutex);
MOZ_RELEASE_ASSERT(mWeakSnapshotter == aNativeLayerRootSnapshotter);
mWeakSnapshotter = nullptr;
}
#endif
void NativeLayerRootCA::CommitOffscreen() {
MutexAutoLock lock(mMutex);
mOffscreenRepresentation.Commit(WhichRepresentation::OFFSCREEN, mSublayers,
mWindowIsFullscreen);
}
template <typename F>
void NativeLayerRootCA::ForAllRepresentations(F aFn) {
aFn(mOnscreenRepresentation);
aFn(mOffscreenRepresentation);
}
NativeLayerRootCA::Representation::Representation(CALayer* aRootCALayer)
: mRootCALayer([aRootCALayer retain]) {}
NativeLayerRootCA::Representation::~Representation() {
if (mMutatedLayerStructure) {
// Clear the root layer's sublayers. At this point the window is usually
// closed, so this transaction does not cause any screen updates.
AutoCATransaction transaction;
mRootCALayer.sublayers = @[];
}
[mRootCALayer release];
}
void NativeLayerRootCA::Representation::Commit(
WhichRepresentation aRepresentation,
const nsTArray<RefPtr<NativeLayerCA>>& aSublayers,
bool aWindowIsFullscreen) {
bool mustRebuild = mMutatedLayerStructure;
if (!mustRebuild) {
// Check which type of update we need to do, if any.
NativeLayerCA::UpdateType updateRequired = NativeLayerCA::UpdateType::None;
for (auto layer : aSublayers) {
// Use the ordering of our UpdateType enums to build a maximal update
// type.
updateRequired =
std::max(updateRequired, layer->HasUpdate(aRepresentation));
if (updateRequired == NativeLayerCA::UpdateType::All) {
break;
}
}
if (updateRequired == NativeLayerCA::UpdateType::None) {
// Nothing more needed, so early exit.
return;
}
if (updateRequired == NativeLayerCA::UpdateType::OnlyVideo) {
bool allUpdatesSucceeded = std::all_of(
aSublayers.begin(), aSublayers.end(),
[=](const RefPtr<NativeLayerCA>& layer) {
return layer->ApplyChanges(aRepresentation,
NativeLayerCA::UpdateType::OnlyVideo);
});
if (allUpdatesSucceeded) {
// Nothing more needed, so early exit;
return;
}
}
}
// We're going to do a full update now, which requires a transaction. Update
// all of the sublayers. Afterwards, only continue processing the sublayers
// which have an extent.
AutoCATransaction transaction;
nsTArray<NativeLayerCA*> sublayersWithExtent;
for (auto layer : aSublayers) {
mustRebuild |= layer->WillUpdateAffectLayers(aRepresentation);
layer->ApplyChanges(aRepresentation, NativeLayerCA::UpdateType::All);
CALayer* caLayer = layer->UnderlyingCALayer(aRepresentation);
if (!caLayer.masksToBounds || !CGRectIsEmpty(caLayer.bounds)) {
// This layer has an extent. If it didn't before, we need to rebuild.
mustRebuild |= !layer->HasExtent();
layer->SetHasExtent(true);
sublayersWithExtent.AppendElement(layer);
} else {
// This layer has no extent. If it did before, we need to rebuild.
mustRebuild |= layer->HasExtent();
layer->SetHasExtent(false);
}
// One other reason we may need to rebuild is if the caLayer is not part of
// the root layer's sublayers. This might happen if the caLayer was rebuilt.
// We construct this check in a way that maximizes the boolean
// short-circuit, because we don't want to call containsObject unless
// absolutely necessary.
mustRebuild =
mustRebuild || ![mRootCALayer.sublayers containsObject:caLayer];
}
if (mustRebuild) {
uint32_t sublayersCount = sublayersWithExtent.Length();
NSMutableArray<CALayer*>* sublayers =
[NSMutableArray arrayWithCapacity:sublayersCount];
for (auto layer : sublayersWithExtent) {
[sublayers addObject:layer->UnderlyingCALayer(aRepresentation)];
}
mRootCALayer.sublayers = sublayers;
}
mMutatedLayerStructure = false;
}
#ifdef XP_MACOSX
/* static */ UniquePtr<NativeLayerRootSnapshotterCA>
NativeLayerRootSnapshotterCA::Create(NativeLayerRootCA* aLayerRoot,
CALayer* aRootCALayer) {
if (NS_IsMainThread()) {
// Disallow creating snapshotters on the main thread.
// On the main thread, any explicit CATransaction / NSAnimationContext is
// nested within a global implicit transaction. This makes it impossible to
// apply CALayer mutations synchronously such that they become visible to
// CARenderer. As a result, the snapshotter would not capture the right
// output on the main thread.
return nullptr;
}
nsCString failureUnused;
RefPtr<gl::GLContext> gl = gl::GLContextProvider::CreateHeadless(
{gl::CreateContextFlags::ALLOW_OFFLINE_RENDERER |
gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE},
&failureUnused);
if (!gl) {
return nullptr;
}
return UniquePtr<NativeLayerRootSnapshotterCA>(
new NativeLayerRootSnapshotterCA(aLayerRoot, std::move(gl),
aRootCALayer));
}
#endif
void NativeLayerRootCA::DumpLayerTreeToFile(const char* aPath) {
MutexAutoLock lock(mMutex);
NSLog(@"Dumping NativeLayer contents to %s", aPath);
std::ofstream fileOutput(aPath);
if (fileOutput.fail()) {
NSLog(@"Opening %s for writing failed.", aPath);
}
// Make sure floating point values use a period for the decimal separator.
fileOutput.imbue(std::locale("C"));
fileOutput << "<html>\n";
for (const auto& layer : mSublayers) {
layer->DumpLayer(fileOutput);
}
fileOutput << "</html>\n";
fileOutput.close();
}
void NativeLayerRootCA::SetWindowIsFullscreen(bool aFullscreen) {
MutexAutoLock lock(mMutex);
if (mWindowIsFullscreen != aFullscreen) {
mWindowIsFullscreen = aFullscreen;
for (auto layer : mSublayers) {
layer->SetRootWindowIsFullscreen(mWindowIsFullscreen);
}
}
}
/* static */ bool IsCGColorOpaqueBlack(CGColorRef aColor) {
if (CGColorEqualToColor(aColor, CGColorGetConstantColor(kCGColorBlack))) {
return true;
}
size_t componentCount = CGColorGetNumberOfComponents(aColor);
if (componentCount == 0) {
// This will happen if aColor is kCGColorClear. It's not opaque black.
return false;
}
const CGFloat* components = CGColorGetComponents(aColor);
for (size_t c = 0; c < componentCount - 1; ++c) {
if (components[c] > 0.0f) {
return false;
}
}
return components[componentCount - 1] >= 1.0f;
}
VideoLowPowerType NativeLayerRootCA::CheckVideoLowPower(
const MutexAutoLock& aProofOfLock) {
// This deteremines whether the current layer contents qualify for the
// macOS Core Animation video low power mode. Those requirements are
// summarized at
// and we verify them by checking:
// 1) There must be exactly one video showing.
// 2) The topmost CALayer must be a AVSampleBufferDisplayLayer.
// 3) The video layer must be showing a buffer encoded in one of the
// kCVPixelFormatType_420YpCbCr pixel formats.
// 4) The layer below that must cover the entire screen and have a black
// background color.
// 5) The window must be fullscreen.
// This function checks these requirements empirically. If one of the checks
// fail, we either return immediately or do additional processing to
// determine more detail.
uint32_t videoLayerCount = 0;
NativeLayerCA* topLayer = nullptr;
CALayer* topCALayer = nil;
CALayer* secondCALayer = nil;
bool topLayerIsVideo = false;
for (auto layer : mSublayers) {
// Only layers with extent are contributing to our sublayers.
if (layer->HasExtent()) {
topLayer = layer;
secondCALayer = topCALayer;
topCALayer = topLayer->UnderlyingCALayer(WhichRepresentation::ONSCREEN);
topLayerIsVideo = topLayer->IsVideo(aProofOfLock);
if (topLayerIsVideo) {
++videoLayerCount;
}
}
}
if (videoLayerCount == 0) {
return VideoLowPowerType::NotVideo;
}
// Most importantly, check if the window is fullscreen. If the user is
// watching video in a window, then all of the other enums are irrelevant to
// achieving the low power mode.
if (!mWindowIsFullscreen) {
return VideoLowPowerType::FailWindowed;
}
if (videoLayerCount > 1) {
return VideoLowPowerType::FailMultipleVideo;
}
if (!topLayerIsVideo) {
return VideoLowPowerType::FailOverlaid;
}
if (!secondCALayer || !IsCGColorOpaqueBlack(secondCALayer.backgroundColor) ||
!CGRectContainsRect(secondCALayer.frame,
secondCALayer.superlayer.bounds)) {
return VideoLowPowerType::FailBacking;
}
CALayer* topContentCALayer = topCALayer.sublayers[0];
if (![topContentCALayer isKindOfClass:[AVSampleBufferDisplayLayer class]]) {
// We didn't create a AVSampleBufferDisplayLayer for the top video layer.
// Try to figure out why by following some of the logic in
// NativeLayerCA::ShouldSpecializeVideo.
if (!StaticPrefs::gfx_core_animation_specialize_video()) {
return VideoLowPowerType::FailPref;
}
// The only remaining reason is that the surface wasn't eligible. We
// assert this instead of if-ing it, to ensure that we always have a
// return value from this clause.
#ifdef DEBUG
MOZ_ASSERT(topLayer->mTextureHost);
MacIOSurface* macIOSurface = topLayer->mTextureHost->GetSurface();
CFTypeRefPtr<IOSurfaceRef> surface = macIOSurface->GetIOSurfaceRef();
OSType pixelFormat = IOSurfaceGetPixelFormat(surface.get());
MOZ_ASSERT(
!(pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange ||
pixelFormat == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange ||
pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange ||
pixelFormat == kCVPixelFormatType_420YpCbCr10BiPlanarFullRange));
#endif
return VideoLowPowerType::FailSurface;
}
AVSampleBufferDisplayLayer* topVideoLayer =
(AVSampleBufferDisplayLayer*)topContentCALayer;
if (topVideoLayer.status != AVQueuedSampleBufferRenderingStatusRendering) {
return VideoLowPowerType::FailEnqueue;
}
// As best we can tell, we're eligible for video low power mode. Hurrah!
return VideoLowPowerType::LowPower;
}
#ifdef XP_MACOSX
NativeLayerRootSnapshotterCA::NativeLayerRootSnapshotterCA(
NativeLayerRootCA* aLayerRoot, RefPtr<GLContext>&& aGL,
CALayer* aRootCALayer)
: mLayerRoot(aLayerRoot), mGL(aGL) {
AutoCATransaction transaction;
mRenderer = [[CARenderer
rendererWithCGLContext:gl::GLContextCGL::Cast(mGL)->GetCGLContext()
options:nil] retain];
mRenderer.layer = aRootCALayer;
}
NativeLayerRootSnapshotterCA::~NativeLayerRootSnapshotterCA() {
mLayerRoot->OnNativeLayerRootSnapshotterDestroyed(this);
[mRenderer release];
}
already_AddRefed<profiler_screenshots::RenderSource>
NativeLayerRootSnapshotterCA::GetWindowContents(const IntSize& aWindowSize) {
UpdateSnapshot(aWindowSize);
return do_AddRef(mSnapshot);
}
void NativeLayerRootSnapshotterCA::UpdateSnapshot(const IntSize& aSize) {
CGRect bounds = CGRectMake(0, 0, aSize.width, aSize.height);
{
// Set the correct bounds and scale on the renderer and its root layer.
// CARenderer always renders at unit scale, i.e. the coordinates on the root
// layer must map 1:1 to render target pixels. But the coordinates on our
// content layers are in "points", where 1 point maps to 2 device pixels on
// HiDPI. So in order to render at the full device pixel resolution, we set
// a scale transform on the root offscreen layer.
AutoCATransaction transaction;
mRenderer.layer.bounds = bounds;
float scale = mLayerRoot->BackingScale();
mRenderer.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1);
mRenderer.bounds = bounds;
}
mLayerRoot->CommitOffscreen();
mGL->MakeCurrent();
bool needToRedrawEverything = false;
if (!mSnapshot || mSnapshot->Size() != aSize) {
mSnapshot = nullptr;
auto fb = gl::MozFramebuffer::Create(mGL, aSize, 0, false);
if (!fb) {
return;
}
mSnapshot = new RenderSourceNLRS(std::move(fb));
needToRedrawEverything = true;
}
const gl::ScopedBindFramebuffer bindFB(mGL, mSnapshot->FB().mFB);
mGL->fViewport(0.0, 0.0, aSize.width, aSize.height);
// These legacy OpenGL function calls are part of CARenderer's API contract,
// see CARenderer.h. The size passed to glOrtho must be the device pixel size
// of the render target, otherwise CARenderer will produce incorrect results.
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, aSize.width, 0.0, aSize.height, -1, 1);
float mediaTime = CACurrentMediaTime();
[mRenderer beginFrameAtTime:mediaTime timeStamp:nullptr];
if (needToRedrawEverything) {
[mRenderer addUpdateRect:bounds];
}
if (!CGRectIsEmpty([mRenderer updateBounds])) {
// CARenderer assumes the layer tree is opaque. It only ever paints over
// existing content, it never erases anything. However, our layer tree is
// not necessarily opaque. So we manually erase the area that's going to be
// redrawn. This ensures correct rendering in the transparent areas.
//
// Since we erase the bounds of the update area, this will erase more than
// necessary if the update area is not a single rectangle. Unfortunately we
// cannot get the precise update region from CARenderer, we can only get the
// bounds.
CGRect updateBounds = [mRenderer updateBounds];
gl::ScopedGLState scopedScissorTestState(mGL, LOCAL_GL_SCISSOR_TEST, true);
gl::ScopedScissorRect scissor(
mGL, updateBounds.origin.x, updateBounds.origin.y,
updateBounds.size.width, updateBounds.size.height);
mGL->fClearColor(0.0, 0.0, 0.0, 0.0);
mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
// We erased the update region's bounds. Make sure the entire update bounds
// get repainted.
[mRenderer addUpdateRect:updateBounds];
}
[mRenderer render];
[mRenderer endFrame];
}
bool NativeLayerRootSnapshotterCA::ReadbackPixels(
const IntSize& aReadbackSize, SurfaceFormat aReadbackFormat,
const Range<uint8_t>& aReadbackBuffer) {
if (aReadbackFormat != SurfaceFormat::B8G8R8A8) {
return false;
}
UpdateSnapshot(aReadbackSize);
if (!mSnapshot) {
return false;
}
const gl::ScopedBindFramebuffer bindFB(mGL, mSnapshot->FB().mFB);
gl::ScopedPackState safePackState(mGL);
mGL->fReadPixels(0.0f, 0.0f, aReadbackSize.width, aReadbackSize.height,
LOCAL_GL_BGRA, LOCAL_GL_UNSIGNED_BYTE, &aReadbackBuffer[0]);
return true;
}
already_AddRefed<profiler_screenshots::DownscaleTarget>
NativeLayerRootSnapshotterCA::CreateDownscaleTarget(const IntSize& aSize) {
auto fb = gl::MozFramebuffer::Create(mGL, aSize, 0, false);
if (!fb) {
return nullptr;
}
RefPtr<profiler_screenshots::DownscaleTarget> dt =
new DownscaleTargetNLRS(mGL, std::move(fb));
return dt.forget();
}
already_AddRefed<profiler_screenshots::AsyncReadbackBuffer>
NativeLayerRootSnapshotterCA::CreateAsyncReadbackBuffer(const IntSize& aSize) {
size_t bufferByteCount = aSize.width * aSize.height * 4;
GLuint bufferHandle = 0;
mGL->fGenBuffers(1, &bufferHandle);
gl::ScopedPackState scopedPackState(mGL);
mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, bufferHandle);
mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
mGL->fBufferData(LOCAL_GL_PIXEL_PACK_BUFFER, bufferByteCount, nullptr,
LOCAL_GL_STREAM_READ);
return MakeAndAddRef<AsyncReadbackBufferNLRS>(mGL, aSize, bufferHandle);
}
#endif
NativeLayerCA::NativeLayerCA(const IntSize& aSize, bool aIsOpaque,
SurfacePoolHandleCA* aSurfacePoolHandle)
: mMutex("NativeLayerCA"),
mSurfacePoolHandle(aSurfacePoolHandle),
mSize(aSize),
mIsOpaque(aIsOpaque) {
MOZ_RELEASE_ASSERT(mSurfacePoolHandle,
"Need a non-null surface pool handle.");
}
NativeLayerCA::NativeLayerCA(bool aIsOpaque)
: mMutex("NativeLayerCA"),
mSurfacePoolHandle(nullptr),
mIsOpaque(aIsOpaque) {
#ifdef NIGHTLY_BUILD
if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
NSLog(@"VIDEO_LOG: NativeLayerCA: %p is being created to host an external "
@"image, which may force a video layer rebuild.",
this);
}
#endif
}
CGColorRef CGColorCreateForDeviceColor(gfx::DeviceColor aColor) {
if (StaticPrefs::gfx_color_management_native_srgb()) {
return CGColorCreateSRGB(aColor.r, aColor.g, aColor.b, aColor.a);
}
return CGColorCreateGenericRGB(aColor.r, aColor.g, aColor.b, aColor.a);
}
NativeLayerCA::NativeLayerCA(gfx::DeviceColor aColor)
: mMutex("NativeLayerCA"),
mSurfacePoolHandle(nullptr),
mIsOpaque(aColor.a >= 1.0f) {
MOZ_ASSERT(aColor.a > 0.0f, "Can't handle a fully transparent backdrop.");
mColor.AssignUnderCreateRule(CGColorCreateForDeviceColor(aColor));
}
NativeLayerCA::~NativeLayerCA() {
#ifdef NIGHTLY_BUILD
if (mHasEverAttachExternalImage &&
StaticPrefs::gfx_core_animation_specialize_video_log()) {
NSLog(@"VIDEO_LOG: ~NativeLayerCA: %p is being destroyed after hosting "
@"an external image.",
this);
}
#endif
if (mInProgressLockedIOSurface) {
mInProgressLockedIOSurface->Unlock(false);
mInProgressLockedIOSurface = nullptr;
}
if (mInProgressSurface) {
IOSurfaceDecrementUseCount(mInProgressSurface->mSurface.get());
mSurfacePoolHandle->ReturnSurfaceToPool(mInProgressSurface->mSurface);
}
if (mFrontSurface) {
mSurfacePoolHandle->ReturnSurfaceToPool(mFrontSurface->mSurface);
}
for (const auto& surf : mSurfaces) {
mSurfacePoolHandle->ReturnSurfaceToPool(surf.mEntry.mSurface);
}
}
void NativeLayerCA::AttachExternalImage(wr::RenderTextureHost* aExternalImage) {
MutexAutoLock lock(mMutex);
#ifdef NIGHTLY_BUILD
mHasEverAttachExternalImage = true;
MOZ_RELEASE_ASSERT(!mHasEverNotifySurfaceReady,
"Shouldn't change layer type to external.");
#endif
wr::RenderMacIOSurfaceTextureHost* texture =
aExternalImage->AsRenderMacIOSurfaceTextureHost();
MOZ_ASSERT(texture);
mTextureHost = texture;
if (!mTextureHost) {
gfxCriticalNoteOnce << "ExternalImage is not RenderMacIOSurfaceTextureHost";
return;
}
// Determine if TextureHost is a video surface.
mIsTextureHostVideo = gfx::Info(mTextureHost->GetFormat())->isYuv;
gfx::IntSize oldSize = mSize;
mSize = texture->GetSize(0);
bool changedSizeAndDisplayRect = (mSize != oldSize);
mDisplayRect = IntRect(IntPoint{}, mSize);
bool oldSpecializeVideo = mSpecializeVideo;
mSpecializeVideo = ShouldSpecializeVideo(lock);
bool changedSpecializeVideo = (mSpecializeVideo != oldSpecializeVideo);
#ifdef NIGHTLY_BUILD
if (changedSpecializeVideo &&
StaticPrefs::gfx_core_animation_specialize_video_log()) {
NSLog(
@"VIDEO_LOG: AttachExternalImage: %p is forcing a video layer rebuild.",
this);
}
#endif
bool oldIsDRM = mIsDRM;
mIsDRM = aExternalImage->IsFromDRMSource();
bool changedIsDRM = (mIsDRM != oldIsDRM);
ForAllRepresentations([&](Representation& r) {
r.mMutatedFrontSurface = true;
r.mMutatedDisplayRect |= changedSizeAndDisplayRect;
r.mMutatedSize |= changedSizeAndDisplayRect;
r.mMutatedSpecializeVideo |= changedSpecializeVideo;
r.mMutatedIsDRM |= changedIsDRM;
});
}
bool NativeLayerCA::IsVideo(const MutexAutoLock& aProofOfLock) {
// If we have a texture host, we've checked to see if it's providing video.
// And if we don't have a texture host, it isn't video, so we just check
// the value we've computed.
return mIsTextureHostVideo;
}
bool NativeLayerCA::ShouldSpecializeVideo(const MutexAutoLock& aProofOfLock) {
if (!IsVideo(aProofOfLock)) {
// Only videos are eligible.
return false;
}
MOZ_ASSERT(mTextureHost);
// DRM video is supported in macOS 10.15 and beyond, and such video must use
// a specialized video layer.
if (mTextureHost->IsFromDRMSource()) {
return true;
}
// Beyond this point, we need to know about the format of the video.
MacIOSurface* macIOSurface = mTextureHost->GetSurface();
if (macIOSurface->GetYUVColorSpace() == gfx::YUVColorSpace::BT2020) {
// BT2020 is a signifier of HDR color space, whether or not the bit depth
// is expanded to cover that color space. This video needs a specialized
// video layer.
return true;
}
if (macIOSurface->GetColorDepth() == gfx::ColorDepth::COLOR_10) {
// 10-bit videos require specialized video layers.
return true;
}
// Beyond this point, we return true if-and-only-if we think we can achieve
// the power-saving "detached mode" of the macOS compositor.
if (!StaticPrefs::gfx_core_animation_specialize_video()) {
// Pref must be set.
return false;
}
// It will only detach if we're fullscreen.
return mRootWindowIsFullscreen;
}
void NativeLayerCA::SetRootWindowIsFullscreen(bool aFullscreen) {
if (mRootWindowIsFullscreen == aFullscreen) {
return;
}
MutexAutoLock lock(mMutex);
mRootWindowIsFullscreen = aFullscreen;
bool oldSpecializeVideo = mSpecializeVideo;
mSpecializeVideo = ShouldSpecializeVideo(lock);
bool changedSpecializeVideo = (mSpecializeVideo != oldSpecializeVideo);
if (changedSpecializeVideo) {
#ifdef NIGHTLY_BUILD
if (StaticPrefs::gfx_core_animation_specialize_video_log()) {
NSLog(@"VIDEO_LOG: SetRootWindowIsFullscreen: %p is forcing a video "
@"layer rebuild.",
this);
}
#endif
ForAllRepresentations(
[&](Representation& r) { r.mMutatedSpecializeVideo = true; });
}
}
void NativeLayerCA::SetSurfaceIsFlipped(bool aIsFlipped) {
MutexAutoLock lock(mMutex);
if (aIsFlipped != mSurfaceIsFlipped) {
mSurfaceIsFlipped = aIsFlipped;
ForAllRepresentations(
[&](Representation& r) { r.mMutatedSurfaceIsFlipped = true; });
}
}
bool NativeLayerCA::SurfaceIsFlipped() {
MutexAutoLock lock(mMutex);
return mSurfaceIsFlipped;
}
IntSize NativeLayerCA::GetSize() {
MutexAutoLock lock(mMutex);
return mSize;
}
void NativeLayerCA::SetPosition(const IntPoint& aPosition) {
MutexAutoLock lock(mMutex);
if (aPosition != mPosition) {
mPosition = aPosition;
ForAllRepresentations(
[&](Representation& r) { r.mMutatedPosition = true; });
}
}
IntPoint NativeLayerCA::GetPosition() {
MutexAutoLock lock(mMutex);
return mPosition;
}
void NativeLayerCA::SetTransform(const Matrix4x4& aTransform) {
MutexAutoLock lock(mMutex);
MOZ_ASSERT(aTransform.IsRectilinear());
if (aTransform != mTransform) {
mTransform = aTransform;
ForAllRepresentations(
[&](Representation& r) { r.mMutatedTransform = true; });
}
}
void NativeLayerCA::SetSamplingFilter(gfx::SamplingFilter aSamplingFilter) {
MutexAutoLock lock(mMutex);
if (aSamplingFilter != mSamplingFilter) {
mSamplingFilter = aSamplingFilter;
ForAllRepresentations(
[&](Representation& r) { r.mMutatedSamplingFilter = true; });
}
}
Matrix4x4 NativeLayerCA::GetTransform() {
MutexAutoLock lock(mMutex);
return mTransform;
}
IntRect NativeLayerCA::GetRect() {
MutexAutoLock lock(mMutex);
return IntRect(mPosition, mSize);
}
void NativeLayerCA::SetBackingScale(float aBackingScale) {
MutexAutoLock lock(mMutex);
if (aBackingScale != mBackingScale) {
mBackingScale = aBackingScale;
ForAllRepresentations(
[&](Representation& r) { r.mMutatedBackingScale = true; });
}
}
bool NativeLayerCA::IsOpaque() {
// mIsOpaque is const, so no need for a lock.
return mIsOpaque;
}
void NativeLayerCA::SetClipRect(const Maybe<gfx::IntRect>& aClipRect) {
MutexAutoLock lock(mMutex);
if (aClipRect != mClipRect) {
mClipRect = aClipRect;
ForAllRepresentations(
[&](Representation& r) { r.mMutatedClipRect = true; });
}
}
Maybe<gfx::IntRect> NativeLayerCA::ClipRect() {
MutexAutoLock lock(mMutex);
return mClipRect;
}
void NativeLayerCA::DumpLayer(std::ostream& aOutputStream) {
MutexAutoLock lock(mMutex);
Maybe<CGRect> scaledClipRect = CalculateClipGeometry(
mSize, mPosition, mTransform, mDisplayRect, mClipRect, mBackingScale);
CGRect useClipRect;
if (scaledClipRect.isSome()) {
useClipRect = *scaledClipRect;
} else {
useClipRect = CGRectZero;
}
aOutputStream << "<div style=\"";
aOutputStream << "position: absolute; ";
aOutputStream << "left: " << useClipRect.origin.x << "px; ";
aOutputStream << "top: " << useClipRect.origin.y << "px; ";
aOutputStream << "width: " << useClipRect.size.width << "px; ";
aOutputStream << "height: " << useClipRect.size.height << "px; ";
if (scaledClipRect.isSome()) {
aOutputStream << "overflow: hidden; ";
}
if (mColor) {
const CGFloat* components = CGColorGetComponents(mColor.get());
aOutputStream << "background: rgb(" << components[0] * 255.0f << " "
<< components[1] * 255.0f << " " << components[2] * 255.0f
<< "); opacity: " << components[3] << "; ";
// That's all we need for color layers. We don't need to specify an image.
aOutputStream << "\"/></div>\n";
return;
}
aOutputStream << "\">";
auto size = gfx::Size(mSize) / mBackingScale;
aOutputStream << "<img style=\"";
aOutputStream << "width: " << size.width << "px; ";
aOutputStream << "height: " << size.height << "px; ";
if (mSamplingFilter == gfx::SamplingFilter::POINT) {
aOutputStream << "image-rendering: crisp-edges; ";
}
Matrix4x4 transform = mTransform;
transform.PreTranslate(mPosition.x, mPosition.y, 0);
transform.PostTranslate((-useClipRect.origin.x * mBackingScale),
(-useClipRect.origin.y * mBackingScale), 0);
if (mSurfaceIsFlipped) {
transform.PreTranslate(0, mSize.height, 0).PreScale(1, -1, 1);
}
if (!transform.IsIdentity()) {
const auto& m = transform;
aOutputStream << "transform-origin: top left; ";
aOutputStream << "transform: matrix3d(";
aOutputStream << m._11 << ", " << m._12 << ", " << m._13 << ", " << m._14
<< ", ";
aOutputStream << m._21 << ", " << m._22 << ", " << m._23 << ", " << m._24
<< ", ";
aOutputStream << m._31 << ", " << m._32 << ", " << m._33 << ", " << m._34
<< ", ";
aOutputStream << m._41 / mBackingScale << ", " << m._42 / mBackingScale
<< ", " << m._43 << ", " << m._44;
aOutputStream << "); ";
}
aOutputStream << "\" ";
CFTypeRefPtr<IOSurfaceRef> surface;
if (mFrontSurface) {
surface = mFrontSurface->mSurface;
aOutputStream << "alt=\"regular surface 0x" << std::hex
<< int(IOSurfaceGetID(surface.get())) << "\" ";
} else if (mTextureHost) {
surface = mTextureHost->GetSurface()->GetIOSurfaceRef();
aOutputStream << "alt=\"TextureHost surface 0x" << std::hex
<< int(IOSurfaceGetID(surface.get())) << "\" ";
} else {
aOutputStream << "alt=\"no surface 0x\" ";
}
aOutputStream << "src=\"";
if (surface) {
// Attempt to render the surface as a PNG. Skia can do this for RGB
// surfaces.
RefPtr<MacIOSurface> surf = new MacIOSurface(surface);
if (surf->Lock(true)) {
SurfaceFormat format = surf->GetFormat();
if (format == SurfaceFormat::B8G8R8A8 ||
format == SurfaceFormat::B8G8R8X8) {
RefPtr<gfx::DrawTarget> dt =
surf->GetAsDrawTargetLocked(gfx::BackendType::SKIA);
if (dt) {
RefPtr<gfx::SourceSurface> sourceSurf = dt->Snapshot();
nsCString dataUrl;
gfxUtils::EncodeSourceSurface(sourceSurf, ImageType::PNG, u""_ns,
gfxUtils::eDataURIEncode, nullptr,
&dataUrl);
aOutputStream << dataUrl.get();
}
}
surf->Unlock(true);
}
}
aOutputStream << "\"/></div>\n";
}
gfx::IntRect NativeLayerCA::CurrentSurfaceDisplayRect() {
MutexAutoLock lock(mMutex);
return mDisplayRect;
}
NativeLayerCA::Representation::Representation()
: mMutatedPosition(true),
mMutatedTransform(true),
mMutatedDisplayRect(true),
mMutatedClipRect(true),
mMutatedBackingScale(true),
mMutatedSize(true),
mMutatedSurfaceIsFlipped(true),
mMutatedFrontSurface(true),
mMutatedSamplingFilter(true),
mMutatedSpecializeVideo(true),
mMutatedIsDRM(true) {}
NativeLayerCA::Representation::~Representation() {
[mContentCALayer release];
[mOpaquenessTintLayer release];
[mWrappingCALayer release];
}
void NativeLayerCA::InvalidateRegionThroughoutSwapchain(
const MutexAutoLock& aProofOfLock, const IntRegion& aRegion) {
IntRegion r = aRegion;
if (mInProgressSurface) {
mInProgressSurface->mInvalidRegion.OrWith(r);
}
if (mFrontSurface) {
mFrontSurface->mInvalidRegion.OrWith(r);
}
for (auto& surf : mSurfaces) {
surf.mEntry.mInvalidRegion.OrWith(r);
}
}
bool NativeLayerCA::NextSurface(const MutexAutoLock& aProofOfLock) {
if (mSize.IsEmpty()) {
gfxCriticalError()
<< "NextSurface returning false because of invalid mSize ("
<< mSize.width << ", " << mSize.height << ").";
return false;
}
MOZ_RELEASE_ASSERT(!mInProgressSurface,
"ERROR: Do not call NextSurface twice in sequence. Call "
"NotifySurfaceReady before the "
"next call to NextSurface.");
Maybe<SurfaceWithInvalidRegion> surf =
GetUnusedSurfaceAndCleanUp(aProofOfLock);
if (!surf) {
CFTypeRefPtr<IOSurfaceRef> newSurf =
mSurfacePoolHandle->ObtainSurfaceFromPool(mSize);
MOZ_RELEASE_ASSERT(
newSurf, "NextSurface IOSurfaceCreate failed to create the surface.");
surf = Some(SurfaceWithInvalidRegion{newSurf, IntRect({}, mSize)});
}
mInProgressSurface = std::move(surf);
IOSurfaceIncrementUseCount(mInProgressSurface->mSurface.get());
return true;
}
template <typename F>
void NativeLayerCA::HandlePartialUpdate(const MutexAutoLock& aProofOfLock,
const IntRect& aDisplayRect,
const IntRegion& aUpdateRegion,
F&& aCopyFn) {
MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aUpdateRegion.GetBounds()),
"The update region should be within the surface bounds.");
MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aDisplayRect),
"The display rect should be within the surface bounds.");
MOZ_RELEASE_ASSERT(!mInProgressUpdateRegion);
MOZ_RELEASE_ASSERT(!mInProgressDisplayRect);
mInProgressUpdateRegion = Some(aUpdateRegion);
mInProgressDisplayRect = Some(aDisplayRect);
if (mFrontSurface) {
// Copy not-overwritten valid content from mFrontSurface so that valid
// content never gets lost.
gfx::IntRegion copyRegion;
copyRegion.Sub(mInProgressSurface->mInvalidRegion, aUpdateRegion);
copyRegion.SubOut(mFrontSurface->mInvalidRegion);
if (!copyRegion.IsEmpty()) {
// Now copy the valid content, using a caller-provided copy function.
aCopyFn(mFrontSurface->mSurface, copyRegion);
mInProgressSurface->mInvalidRegion.SubOut(copyRegion);
}
}
InvalidateRegionThroughoutSwapchain(aProofOfLock, aUpdateRegion);
}
RefPtr<gfx::DrawTarget> NativeLayerCA::NextSurfaceAsDrawTarget(
const IntRect& aDisplayRect, const IntRegion& aUpdateRegion,
gfx::BackendType aBackendType) {
MutexAutoLock lock(mMutex);
if (!NextSurface(lock)) {
return nullptr;
}
auto surf = MakeRefPtr<MacIOSurface>(mInProgressSurface->mSurface);
if (NS_WARN_IF(!surf->Lock(false))) {
gfxCriticalError() << "NextSurfaceAsDrawTarget lock surface failed.";
return nullptr;
}
mInProgressLockedIOSurface = std::move(surf);
RefPtr<gfx::DrawTarget> dt =
mInProgressLockedIOSurface->GetAsDrawTargetLocked(aBackendType);
HandlePartialUpdate(
lock, aDisplayRect, aUpdateRegion,
[&](CFTypeRefPtr<IOSurfaceRef> validSource,
const gfx::IntRegion& copyRegion) {
RefPtr<MacIOSurface> source = new MacIOSurface(validSource);
if (source->Lock(true)) {
RefPtr<gfx::DrawTarget> sourceDT =
source->GetAsDrawTargetLocked(aBackendType);
RefPtr<gfx::SourceSurface> sourceSurface = sourceDT->Snapshot();
for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
const gfx::IntRect& r = iter.Get();
dt->CopySurface(sourceSurface, r, r.TopLeft());
}
source->Unlock(true);
} else {
gfxCriticalError() << "HandlePartialUpdate lock surface failed.";
}
});
return dt;
}
Maybe<GLuint> NativeLayerCA::NextSurfaceAsFramebuffer(
const IntRect& aDisplayRect, const IntRegion& aUpdateRegion,
bool aNeedsDepth) {
MutexAutoLock lock(mMutex);
MOZ_RELEASE_ASSERT(NextSurface(lock),
"NextSurfaceAsFramebuffer needs a surface.");
Maybe<GLuint> fbo = mSurfacePoolHandle->GetFramebufferForSurface(
mInProgressSurface->mSurface, aNeedsDepth);
MOZ_RELEASE_ASSERT(fbo, "GetFramebufferForSurface failed.");
HandlePartialUpdate(
lock, aDisplayRect, aUpdateRegion,
[&](CFTypeRefPtr<IOSurfaceRef> validSource,
const gfx::IntRegion& copyRegion) {
// Copy copyRegion from validSource to fbo.
MOZ_RELEASE_ASSERT(mSurfacePoolHandle->gl());
mSurfacePoolHandle->gl()->MakeCurrent();
Maybe<GLuint> sourceFBO =
mSurfacePoolHandle->GetFramebufferForSurface(validSource, false);
MOZ_RELEASE_ASSERT(
sourceFBO,
"GetFramebufferForSurface failed during HandlePartialUpdate.");
for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
gfx::IntRect r = iter.Get();
if (mSurfaceIsFlipped) {
r.y = mSize.height - r.YMost();
}
mSurfacePoolHandle->gl()->BlitHelper()->BlitFramebufferToFramebuffer(
*sourceFBO, *