Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nullptr; 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
#import <AppKit/NSAnimationContext.h>
9
#import <AppKit/NSColor.h>
10
#import <OpenGL/gl.h>
11
#import <QuartzCore/QuartzCore.h>
12
13
#include <utility>
14
#include <algorithm>
15
16
#include "gfxUtils.h"
17
#include "GLBlitHelper.h"
18
#include "GLContextCGL.h"
19
#include "GLContextProvider.h"
20
#include "MozFramebuffer.h"
21
#include "mozilla/layers/SurfacePoolCA.h"
22
#include "ScopedGLHelpers.h"
23
24
@interface CALayer (PrivateSetContentsOpaque)
25
- (void)setContentsOpaque:(BOOL)opaque;
26
@end
27
28
namespace mozilla {
29
namespace layers {
30
31
using gfx::IntPoint;
32
using gfx::IntSize;
33
using gfx::IntRect;
34
using gfx::IntRegion;
35
using gfx::SurfaceFormat;
36
using gl::GLContext;
37
using gl::GLContextCGL;
38
39
// Needs to be on the stack whenever CALayer mutations are performed.
40
// (Mutating CALayers outside of a transaction can result in permanently stuck rendering, because
41
// such mutations create an implicit transaction which never auto-commits if the current thread does
42
// not have a native runloop.)
43
// Uses NSAnimationContext, which wraps CATransaction with additional off-main-thread protection,
44
// see bug 1585523.
45
struct MOZ_STACK_CLASS AutoCATransaction final {
46
AutoCATransaction() {
47
[NSAnimationContext beginGrouping];
48
// By default, mutating a CALayer property triggers an animation which smoothly transitions the
49
// property to the new value. We don't need these animations, and this call turns them off:
50
[CATransaction setDisableActions:YES];
51
}
52
~AutoCATransaction() { [NSAnimationContext endGrouping]; }
53
};
54
55
/* static */ already_AddRefed<NativeLayerRootCA> NativeLayerRootCA::CreateForCALayer(
56
CALayer* aLayer) {
57
RefPtr<NativeLayerRootCA> layerRoot = new NativeLayerRootCA(aLayer);
58
return layerRoot.forget();
59
}
60
61
// Returns an autoreleased CALayer* object.
62
static CALayer* MakeOffscreenRootCALayer() {
63
// This layer should behave similarly to the backing layer of a flipped NSView.
64
// It will never be rendered on the screen and it will never be attached to an NSView's layer;
65
// instead, it will be the root layer of a "local" CAContext.
66
// Setting geometryFlipped to YES causes the orientation of descendant CALayers' contents (such as
67
// IOSurfaces) to be consistent with what happens in a layer subtree that is attached to a flipped
68
// NSView. Setting it to NO would cause the surfaces in individual leaf layers to render upside
69
// down (rather than just flipping the entire layer tree upside down).
70
AutoCATransaction transaction;
71
CALayer* layer = [CALayer layer];
72
layer.position = NSZeroPoint;
73
layer.bounds = NSZeroRect;
74
layer.anchorPoint = NSZeroPoint;
75
layer.contentsGravity = kCAGravityTopLeft;
76
layer.masksToBounds = YES;
77
layer.geometryFlipped = YES;
78
return layer;
79
}
80
81
NativeLayerRootCA::NativeLayerRootCA(CALayer* aLayer)
82
: mMutex("NativeLayerRootCA"),
83
mOnscreenRepresentation(aLayer),
84
mOffscreenRepresentation(MakeOffscreenRootCALayer()) {}
85
86
NativeLayerRootCA::~NativeLayerRootCA() {
87
MOZ_RELEASE_ASSERT(mSublayers.IsEmpty(),
88
"Please clear all layers before destroying the layer root.");
89
}
90
91
already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayer(
92
const IntSize& aSize, bool aIsOpaque, SurfacePoolHandle* aSurfacePoolHandle) {
93
RefPtr<NativeLayer> layer =
94
new NativeLayerCA(aSize, aIsOpaque, aSurfacePoolHandle->AsSurfacePoolHandleCA());
95
return layer.forget();
96
}
97
98
void NativeLayerRootCA::AppendLayer(NativeLayer* aLayer) {
99
MutexAutoLock lock(mMutex);
100
101
RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA();
102
MOZ_RELEASE_ASSERT(layerCA);
103
104
mSublayers.AppendElement(layerCA);
105
layerCA->SetBackingScale(mBackingScale);
106
ForAllRepresentations([&](Representation& r) { r.mMutated = true; });
107
}
108
109
void NativeLayerRootCA::RemoveLayer(NativeLayer* aLayer) {
110
MutexAutoLock lock(mMutex);
111
112
RefPtr<NativeLayerCA> layerCA = aLayer->AsNativeLayerCA();
113
MOZ_RELEASE_ASSERT(layerCA);
114
115
mSublayers.RemoveElement(layerCA);
116
ForAllRepresentations([&](Representation& r) { r.mMutated = true; });
117
}
118
119
void NativeLayerRootCA::SetLayers(const nsTArray<RefPtr<NativeLayer>>& aLayers) {
120
MutexAutoLock lock(mMutex);
121
122
// Ideally, we'd just be able to do mSublayers = std::move(aLayers).
123
// However, aLayers has a different type: it carries NativeLayer objects, whereas mSublayers
124
// carries NativeLayerCA objects, so we have to downcast all the elements first. There's one other
125
// reason to look at all the elements in aLayers first: We need to make sure any new layers know
126
// about our current backing scale.
127
128
nsTArray<RefPtr<NativeLayerCA>> layersCA(aLayers.Length());
129
for (auto& layer : aLayers) {
130
RefPtr<NativeLayerCA> layerCA = layer->AsNativeLayerCA();
131
MOZ_RELEASE_ASSERT(layerCA);
132
layerCA->SetBackingScale(mBackingScale);
133
layersCA.AppendElement(std::move(layerCA));
134
}
135
136
if (layersCA != mSublayers) {
137
mSublayers = std::move(layersCA);
138
ForAllRepresentations([&](Representation& r) { r.mMutated = true; });
139
}
140
}
141
142
void NativeLayerRootCA::SetBackingScale(float aBackingScale) {
143
MutexAutoLock lock(mMutex);
144
145
mBackingScale = aBackingScale;
146
for (auto layer : mSublayers) {
147
layer->SetBackingScale(aBackingScale);
148
}
149
}
150
151
float NativeLayerRootCA::BackingScale() {
152
MutexAutoLock lock(mMutex);
153
return mBackingScale;
154
}
155
156
void NativeLayerRootCA::SuspendOffMainThreadCommits() {
157
MutexAutoLock lock(mMutex);
158
mOffMainThreadCommitsSuspended = true;
159
}
160
161
bool NativeLayerRootCA::UnsuspendOffMainThreadCommits() {
162
MutexAutoLock lock(mMutex);
163
mOffMainThreadCommitsSuspended = false;
164
return mCommitPending;
165
}
166
167
bool NativeLayerRootCA::AreOffMainThreadCommitsSuspended() {
168
MutexAutoLock lock(mMutex);
169
return mOffMainThreadCommitsSuspended;
170
}
171
172
bool NativeLayerRootCA::CommitToScreen() {
173
MutexAutoLock lock(mMutex);
174
175
if (!NS_IsMainThread() && mOffMainThreadCommitsSuspended) {
176
mCommitPending = true;
177
return false;
178
}
179
180
mOnscreenRepresentation.Commit(WhichRepresentation::ONSCREEN, mSublayers);
181
182
mCommitPending = false;
183
184
return true;
185
}
186
187
UniquePtr<NativeLayerRootSnapshotter> NativeLayerRootCA::CreateSnapshotter() {
188
MutexAutoLock lock(mMutex);
189
MOZ_RELEASE_ASSERT(
190
!mWeakSnapshotter,
191
"No NativeLayerRootSnapshotter for this NativeLayerRoot should exist when this is called");
192
193
auto cr = NativeLayerRootSnapshotterCA::Create(this, mOffscreenRepresentation.mRootCALayer);
194
if (cr) {
195
mWeakSnapshotter = cr.get();
196
}
197
return cr;
198
}
199
200
void NativeLayerRootCA::OnNativeLayerRootSnapshotterDestroyed(
201
NativeLayerRootSnapshotterCA* aNativeLayerRootSnapshotter) {
202
MutexAutoLock lock(mMutex);
203
MOZ_RELEASE_ASSERT(mWeakSnapshotter == aNativeLayerRootSnapshotter);
204
mWeakSnapshotter = nullptr;
205
}
206
207
void NativeLayerRootCA::CommitOffscreen() {
208
MutexAutoLock lock(mMutex);
209
mOffscreenRepresentation.Commit(WhichRepresentation::OFFSCREEN, mSublayers);
210
}
211
212
template <typename F>
213
void NativeLayerRootCA::ForAllRepresentations(F aFn) {
214
aFn(mOnscreenRepresentation);
215
aFn(mOffscreenRepresentation);
216
}
217
218
NativeLayerRootCA::Representation::Representation(CALayer* aRootCALayer)
219
: mRootCALayer([aRootCALayer retain]) {}
220
221
NativeLayerRootCA::Representation::~Representation() {
222
if (mMutated) {
223
// Clear the root layer's sublayers. At this point the window is usually closed, so this
224
// transaction does not cause any screen updates.
225
AutoCATransaction transaction;
226
mRootCALayer.sublayers = @[];
227
}
228
229
[mRootCALayer release];
230
}
231
232
void NativeLayerRootCA::Representation::Commit(WhichRepresentation aRepresentation,
233
const nsTArray<RefPtr<NativeLayerCA>>& aSublayers) {
234
AutoCATransaction transaction;
235
236
// Call ApplyChanges on our sublayers first, and then update the root layer's
237
// list of sublayers. The order is important because we need layer->UnderlyingCALayer()
238
// to be non-null, and the underlying CALayer gets lazily initialized in ApplyChanges().
239
for (auto layer : aSublayers) {
240
layer->ApplyChanges(aRepresentation);
241
}
242
243
if (mMutated) {
244
NSMutableArray<CALayer*>* sublayers = [NSMutableArray arrayWithCapacity:aSublayers.Length()];
245
for (auto layer : aSublayers) {
246
[sublayers addObject:layer->UnderlyingCALayer(aRepresentation)];
247
}
248
mRootCALayer.sublayers = sublayers;
249
mMutated = false;
250
}
251
}
252
253
/* static */ UniquePtr<NativeLayerRootSnapshotterCA> NativeLayerRootSnapshotterCA::Create(
254
NativeLayerRootCA* aLayerRoot, CALayer* aRootCALayer) {
255
if (NS_IsMainThread()) {
256
// Disallow creating snapshotters on the main thread.
257
// On the main thread, any explicit CATransaction / NSAnimationContext is nested within a global
258
// implicit transaction. This makes it impossible to apply CALayer mutations synchronously such
259
// that they become visible to CARenderer. As a result, the snapshotter would not capture
260
// the right output on the main thread.
261
return nullptr;
262
}
263
264
nsCString failureUnused;
265
RefPtr<gl::GLContext> gl =
266
gl::GLContextProvider::CreateHeadless(gl::CreateContextFlags::ALLOW_OFFLINE_RENDERER |
267
gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE,
268
&failureUnused);
269
if (!gl) {
270
return nullptr;
271
}
272
273
return UniquePtr<NativeLayerRootSnapshotterCA>(
274
new NativeLayerRootSnapshotterCA(aLayerRoot, std::move(gl), aRootCALayer));
275
}
276
277
NativeLayerRootSnapshotterCA::NativeLayerRootSnapshotterCA(NativeLayerRootCA* aLayerRoot,
278
RefPtr<GLContext>&& aGL,
279
CALayer* aRootCALayer)
280
: mLayerRoot(aLayerRoot), mGL(aGL) {
281
AutoCATransaction transaction;
282
mRenderer = [[CARenderer rendererWithCGLContext:gl::GLContextCGL::Cast(mGL)->GetCGLContext()
283
options:nil] retain];
284
mRenderer.layer = aRootCALayer;
285
}
286
287
NativeLayerRootSnapshotterCA::~NativeLayerRootSnapshotterCA() {
288
mLayerRoot->OnNativeLayerRootSnapshotterDestroyed(this);
289
[mRenderer release];
290
}
291
292
bool NativeLayerRootSnapshotterCA::ReadbackPixels(const IntSize& aReadbackSize,
293
SurfaceFormat aReadbackFormat,
294
const Range<uint8_t>& aReadbackBuffer) {
295
if (aReadbackFormat != SurfaceFormat::B8G8R8A8) {
296
return false;
297
}
298
299
CGRect bounds = CGRectMake(0, 0, aReadbackSize.width, aReadbackSize.height);
300
301
{
302
// Set the correct bounds and scale on the renderer and its root layer. CARenderer always
303
// renders at unit scale, i.e. the coordinates on the root layer must map 1:1 to render target
304
// pixels. But the coordinates on our content layers are in "points", where 1 point maps to 2
305
// device pixels on HiDPI. So in order to render at the full device pixel resolution, we set a
306
// scale transform on the root offscreen layer.
307
AutoCATransaction transaction;
308
mRenderer.layer.bounds = bounds;
309
float scale = mLayerRoot->BackingScale();
310
mRenderer.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1);
311
mRenderer.bounds = bounds;
312
}
313
314
mLayerRoot->CommitOffscreen();
315
316
mGL->MakeCurrent();
317
318
bool needToRedrawEverything = false;
319
if (!mFB || mFB->mSize != aReadbackSize) {
320
mFB = gl::MozFramebuffer::Create(mGL, aReadbackSize, 0, false);
321
if (!mFB) {
322
return false;
323
}
324
needToRedrawEverything = true;
325
}
326
327
const gl::ScopedBindFramebuffer bindFB(mGL, mFB->mFB);
328
mGL->fViewport(0.0, 0.0, aReadbackSize.width, aReadbackSize.height);
329
330
// These legacy OpenGL function calls are part of CARenderer's API contract, see CARenderer.h.
331
// The size passed to glOrtho must be the device pixel size of the render target, otherwise
332
// CARenderer will produce incorrect results.
333
glMatrixMode(GL_PROJECTION);
334
glLoadIdentity();
335
glOrtho(0.0, aReadbackSize.width, 0.0, aReadbackSize.height, -1, 1);
336
337
float mediaTime = CACurrentMediaTime();
338
[mRenderer beginFrameAtTime:mediaTime timeStamp:nullptr];
339
if (needToRedrawEverything) {
340
[mRenderer addUpdateRect:bounds];
341
}
342
if (!CGRectIsEmpty([mRenderer updateBounds])) {
343
// CARenderer assumes the layer tree is opaque. It only ever paints over existing content, it
344
// never erases anything. However, our layer tree is not necessarily opaque. So we manually
345
// erase the area that's going to be redrawn. This ensures correct rendering in the transparent
346
// areas.
347
//
348
// Since we erase the bounds of the update area, this will erase more than necessary if the
349
// update area is not a single rectangle. Unfortunately we cannot get the precise update region
350
// from CARenderer, we can only get the bounds.
351
CGRect updateBounds = [mRenderer updateBounds];
352
gl::ScopedGLState scopedScissorTestState(mGL, LOCAL_GL_SCISSOR_TEST, true);
353
gl::ScopedScissorRect scissor(mGL, updateBounds.origin.x, updateBounds.origin.y,
354
updateBounds.size.width, updateBounds.size.height);
355
mGL->fClearColor(0.0, 0.0, 0.0, 0.0);
356
mGL->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
357
// We erased the update region's bounds. Make sure the entire update bounds get repainted.
358
[mRenderer addUpdateRect:updateBounds];
359
}
360
[mRenderer render];
361
[mRenderer endFrame];
362
363
gl::ScopedPackState safePackState(mGL);
364
mGL->fReadPixels(0.0f, 0.0f, aReadbackSize.width, aReadbackSize.height, LOCAL_GL_BGRA,
365
LOCAL_GL_UNSIGNED_BYTE, &aReadbackBuffer[0]);
366
367
return true;
368
}
369
370
NativeLayerCA::NativeLayerCA(const IntSize& aSize, bool aIsOpaque,
371
SurfacePoolHandleCA* aSurfacePoolHandle)
372
: mMutex("NativeLayerCA"),
373
mSurfacePoolHandle(aSurfacePoolHandle),
374
mSize(aSize),
375
mIsOpaque(aIsOpaque) {
376
MOZ_RELEASE_ASSERT(mSurfacePoolHandle, "Need a non-null surface pool handle.");
377
}
378
379
NativeLayerCA::~NativeLayerCA() {
380
if (mInProgressLockedIOSurface) {
381
mInProgressLockedIOSurface->Unlock(false);
382
mInProgressLockedIOSurface = nullptr;
383
}
384
if (mInProgressSurface) {
385
IOSurfaceDecrementUseCount(mInProgressSurface->mSurface.get());
386
mSurfacePoolHandle->ReturnSurfaceToPool(mInProgressSurface->mSurface);
387
}
388
if (mFrontSurface) {
389
mSurfacePoolHandle->ReturnSurfaceToPool(mFrontSurface->mSurface);
390
}
391
for (const auto& surf : mSurfaces) {
392
mSurfacePoolHandle->ReturnSurfaceToPool(surf.mEntry.mSurface);
393
}
394
}
395
396
void NativeLayerCA::SetSurfaceIsFlipped(bool aIsFlipped) {
397
MutexAutoLock lock(mMutex);
398
399
if (aIsFlipped != mSurfaceIsFlipped) {
400
mSurfaceIsFlipped = aIsFlipped;
401
ForAllRepresentations([&](Representation& r) { r.mMutatedSurfaceIsFlipped = true; });
402
}
403
}
404
405
bool NativeLayerCA::SurfaceIsFlipped() {
406
MutexAutoLock lock(mMutex);
407
408
return mSurfaceIsFlipped;
409
}
410
411
IntSize NativeLayerCA::GetSize() {
412
MutexAutoLock lock(mMutex);
413
return mSize;
414
}
415
416
void NativeLayerCA::SetPosition(const IntPoint& aPosition) {
417
MutexAutoLock lock(mMutex);
418
419
if (aPosition != mPosition) {
420
mPosition = aPosition;
421
ForAllRepresentations([&](Representation& r) { r.mMutatedPosition = true; });
422
}
423
}
424
425
IntPoint NativeLayerCA::GetPosition() {
426
MutexAutoLock lock(mMutex);
427
return mPosition;
428
}
429
430
IntRect NativeLayerCA::GetRect() {
431
MutexAutoLock lock(mMutex);
432
return IntRect(mPosition, mSize);
433
}
434
435
void NativeLayerCA::SetValidRect(const gfx::IntRect& aValidRect) {
436
MutexAutoLock lock(mMutex);
437
mValidRect = aValidRect;
438
}
439
440
IntRect NativeLayerCA::GetValidRect() {
441
MutexAutoLock lock(mMutex);
442
return mValidRect;
443
}
444
445
void NativeLayerCA::SetBackingScale(float aBackingScale) {
446
MutexAutoLock lock(mMutex);
447
448
if (aBackingScale != mBackingScale) {
449
mBackingScale = aBackingScale;
450
ForAllRepresentations([&](Representation& r) { r.mMutatedBackingScale = true; });
451
}
452
}
453
454
bool NativeLayerCA::IsOpaque() {
455
MutexAutoLock lock(mMutex);
456
return mIsOpaque;
457
}
458
459
void NativeLayerCA::SetClipRect(const Maybe<gfx::IntRect>& aClipRect) {
460
MutexAutoLock lock(mMutex);
461
462
if (aClipRect != mClipRect) {
463
mClipRect = aClipRect;
464
ForAllRepresentations([&](Representation& r) { r.mMutatedClipRect = true; });
465
}
466
}
467
468
Maybe<gfx::IntRect> NativeLayerCA::ClipRect() {
469
MutexAutoLock lock(mMutex);
470
return mClipRect;
471
}
472
473
NativeLayerCA::Representation::~Representation() {
474
[mContentCALayer release];
475
[mOpaquenessTintLayer release];
476
[mWrappingCALayer release];
477
}
478
479
void NativeLayerCA::InvalidateRegionThroughoutSwapchain(const MutexAutoLock&,
480
const IntRegion& aRegion) {
481
IntRegion r = aRegion;
482
if (mInProgressSurface) {
483
mInProgressSurface->mInvalidRegion.OrWith(r);
484
}
485
if (mFrontSurface) {
486
mFrontSurface->mInvalidRegion.OrWith(r);
487
}
488
for (auto& surf : mSurfaces) {
489
surf.mEntry.mInvalidRegion.OrWith(r);
490
}
491
}
492
493
bool NativeLayerCA::NextSurface(const MutexAutoLock& aLock) {
494
if (mSize.IsEmpty()) {
495
NSLog(@"NextSurface returning false because of invalid mSize (%d, %d).", mSize.width,
496
mSize.height);
497
return false;
498
}
499
500
MOZ_RELEASE_ASSERT(
501
!mInProgressSurface,
502
"ERROR: Do not call NextSurface twice in sequence. Call NotifySurfaceReady before the "
503
"next call to NextSurface.");
504
505
Maybe<SurfaceWithInvalidRegion> surf = GetUnusedSurfaceAndCleanUp(aLock);
506
if (!surf) {
507
CFTypeRefPtr<IOSurfaceRef> newSurf = mSurfacePoolHandle->ObtainSurfaceFromPool(mSize);
508
if (!newSurf) {
509
NSLog(@"NextSurface returning false because IOSurfaceCreate failed to create the surface.");
510
return false;
511
}
512
surf = Some(SurfaceWithInvalidRegion{newSurf, IntRect({}, mSize)});
513
}
514
515
MOZ_RELEASE_ASSERT(surf);
516
mInProgressSurface = std::move(surf);
517
IOSurfaceIncrementUseCount(mInProgressSurface->mSurface.get());
518
return true;
519
}
520
521
template <typename F>
522
void NativeLayerCA::HandlePartialUpdate(const MutexAutoLock& aLock,
523
const gfx::IntRegion& aUpdateRegion, F&& aCopyFn) {
524
MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aUpdateRegion.GetBounds()),
525
"The update region should be within the surface bounds.");
526
527
InvalidateRegionThroughoutSwapchain(aLock, aUpdateRegion);
528
529
gfx::IntRegion copyRegion;
530
copyRegion.Sub(mInProgressSurface->mInvalidRegion, aUpdateRegion);
531
532
// TODO(gw): !!!!! Need to get mac code updated to handle partial valid rect updates.
533
534
if (!copyRegion.IsEmpty()) {
535
// There are parts in mInProgressSurface which are invalid but which are not included in
536
// aUpdateRegion. We will obtain valid content for those parts by copying from a previous
537
// surface.
538
// MOZ_RELEASE_ASSERT(
539
// mFrontSurface,
540
// "The first call to NextSurface* must always update the entire layer. If this "
541
// "is the second call, mFrontSurface will be Some().");
542
543
// // NotifySurfaceReady marks the entirety of mFrontSurface as valid.
544
// MOZ_RELEASE_ASSERT(mFrontSurface->mInvalidRegion.Intersect(copyRegion).IsEmpty(),
545
// "mFrontSurface should have valid content in the entire copy region,
546
// because " "the only invalidation since NotifySurfaceReady was
547
// aUpdateRegion, and " "aUpdateRegion has no overlap with copyRegion.");
548
549
if (mFrontSurface) {
550
// Now copy the valid content, using a caller-provided copy function.
551
aCopyFn(mFrontSurface->mSurface, copyRegion);
552
mInProgressSurface->mInvalidRegion.SubOut(copyRegion);
553
}
554
}
555
556
// MOZ_RELEASE_ASSERT(mInProgressSurface->mInvalidRegion == aUpdateRegion);
557
}
558
559
RefPtr<gfx::DrawTarget> NativeLayerCA::NextSurfaceAsDrawTarget(const gfx::IntRegion& aUpdateRegion,
560
gfx::BackendType aBackendType) {
561
MutexAutoLock lock(mMutex);
562
if (!NextSurface(lock)) {
563
return nullptr;
564
}
565
566
mInProgressLockedIOSurface = new MacIOSurface(mInProgressSurface->mSurface);
567
mInProgressLockedIOSurface->Lock(false);
568
RefPtr<gfx::DrawTarget> dt = mInProgressLockedIOSurface->GetAsDrawTargetLocked(aBackendType);
569
570
HandlePartialUpdate(
571
lock, aUpdateRegion,
572
[&](CFTypeRefPtr<IOSurfaceRef> validSource, const gfx::IntRegion& copyRegion) {
573
RefPtr<MacIOSurface> source = new MacIOSurface(validSource);
574
source->Lock(true);
575
{
576
RefPtr<gfx::DrawTarget> sourceDT = source->GetAsDrawTargetLocked(aBackendType);
577
RefPtr<gfx::SourceSurface> sourceSurface = sourceDT->Snapshot();
578
579
for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
580
const gfx::IntRect& r = iter.Get();
581
dt->CopySurface(sourceSurface, r, r.TopLeft());
582
}
583
}
584
source->Unlock(true);
585
});
586
587
return dt;
588
}
589
590
Maybe<GLuint> NativeLayerCA::NextSurfaceAsFramebuffer(const gfx::IntRegion& aUpdateRegion,
591
bool aNeedsDepth) {
592
MutexAutoLock lock(mMutex);
593
if (!NextSurface(lock)) {
594
return Nothing();
595
}
596
597
Maybe<GLuint> fbo =
598
mSurfacePoolHandle->GetFramebufferForSurface(mInProgressSurface->mSurface, aNeedsDepth);
599
if (!fbo) {
600
return Nothing();
601
}
602
603
HandlePartialUpdate(
604
lock, aUpdateRegion,
605
[&](CFTypeRefPtr<IOSurfaceRef> validSource, const gfx::IntRegion& copyRegion) {
606
// Copy copyRegion from validSource to fbo.
607
MOZ_RELEASE_ASSERT(mSurfacePoolHandle->gl());
608
mSurfacePoolHandle->gl()->MakeCurrent();
609
Maybe<GLuint> sourceFBO = mSurfacePoolHandle->GetFramebufferForSurface(validSource, false);
610
if (!sourceFBO) {
611
return;
612
}
613
for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
614
gfx::IntRect r = iter.Get();
615
if (mSurfaceIsFlipped) {
616
r.y = mSize.height - r.YMost();
617
}
618
mSurfacePoolHandle->gl()->BlitHelper()->BlitFramebufferToFramebuffer(*sourceFBO, *fbo, r,
619
r, LOCAL_GL_NEAREST);
620
}
621
});
622
623
return fbo;
624
}
625
626
void NativeLayerCA::NotifySurfaceReady() {
627
MutexAutoLock lock(mMutex);
628
629
MOZ_RELEASE_ASSERT(mInProgressSurface,
630
"NotifySurfaceReady called without preceding call to NextSurface");
631
632
if (mInProgressLockedIOSurface) {
633
mInProgressLockedIOSurface->Unlock(false);
634
mInProgressLockedIOSurface = nullptr;
635
}
636
637
if (mFrontSurface) {
638
mSurfaces.push_back({*mFrontSurface, 0});
639
mFrontSurface = Nothing();
640
}
641
642
IOSurfaceDecrementUseCount(mInProgressSurface->mSurface.get());
643
mFrontSurface = std::move(mInProgressSurface);
644
mFrontSurface->mInvalidRegion = IntRect();
645
ForAllRepresentations([&](Representation& r) { r.mMutatedFrontSurface = true; });
646
}
647
648
void NativeLayerCA::DiscardBackbuffers() {
649
MutexAutoLock lock(mMutex);
650
651
for (const auto& surf : mSurfaces) {
652
mSurfacePoolHandle->ReturnSurfaceToPool(surf.mEntry.mSurface);
653
}
654
mSurfaces.clear();
655
}
656
657
NativeLayerCA::Representation& NativeLayerCA::GetRepresentation(
658
WhichRepresentation aRepresentation) {
659
switch (aRepresentation) {
660
case WhichRepresentation::ONSCREEN:
661
return mOnscreenRepresentation;
662
case WhichRepresentation::OFFSCREEN:
663
return mOffscreenRepresentation;
664
}
665
}
666
667
template <typename F>
668
void NativeLayerCA::ForAllRepresentations(F aFn) {
669
aFn(mOnscreenRepresentation);
670
aFn(mOffscreenRepresentation);
671
}
672
673
void NativeLayerCA::ApplyChanges(WhichRepresentation aRepresentation) {
674
MutexAutoLock lock(mMutex);
675
GetRepresentation(aRepresentation)
676
.ApplyChanges(mSize, mIsOpaque, mPosition, mClipRect, mBackingScale, mSurfaceIsFlipped,
677
mFrontSurface ? mFrontSurface->mSurface : nullptr);
678
}
679
680
CALayer* NativeLayerCA::UnderlyingCALayer(WhichRepresentation aRepresentation) {
681
MutexAutoLock lock(mMutex);
682
return GetRepresentation(aRepresentation).UnderlyingCALayer();
683
}
684
685
void NativeLayerCA::Representation::ApplyChanges(const IntSize& aSize, bool aIsOpaque,
686
const IntPoint& aPosition,
687
const Maybe<IntRect>& aClipRect,
688
float aBackingScale, bool aSurfaceIsFlipped,
689
CFTypeRefPtr<IOSurfaceRef> aFrontSurface) {
690
if (!mWrappingCALayer) {
691
mWrappingCALayer = [[CALayer layer] retain];
692
mWrappingCALayer.position = NSZeroPoint;
693
mWrappingCALayer.bounds = NSZeroRect;
694
mWrappingCALayer.anchorPoint = NSZeroPoint;
695
mWrappingCALayer.contentsGravity = kCAGravityTopLeft;
696
mContentCALayer = [[CALayer layer] retain];
697
mContentCALayer.position = NSZeroPoint;
698
mContentCALayer.anchorPoint = NSZeroPoint;
699
mContentCALayer.contentsGravity = kCAGravityTopLeft;
700
mContentCALayer.contentsScale = 1;
701
mContentCALayer.bounds = CGRectMake(0, 0, aSize.width, aSize.height);
702
mContentCALayer.opaque = aIsOpaque;
703
if ([mContentCALayer respondsToSelector:@selector(setContentsOpaque:)]) {
704
// The opaque property seems to not be enough when using IOSurface contents.
705
// Additionally, call the private method setContentsOpaque.
706
[mContentCALayer setContentsOpaque:aIsOpaque];
707
}
708
[mWrappingCALayer addSublayer:mContentCALayer];
709
}
710
711
bool shouldTintOpaqueness = StaticPrefs::gfx_core_animation_tint_opaque();
712
if (shouldTintOpaqueness && !mOpaquenessTintLayer) {
713
mOpaquenessTintLayer = [[CALayer layer] retain];
714
mOpaquenessTintLayer.position = mContentCALayer.position;
715
mOpaquenessTintLayer.bounds = mContentCALayer.bounds;
716
mOpaquenessTintLayer.anchorPoint = NSZeroPoint;
717
mOpaquenessTintLayer.contentsGravity = kCAGravityTopLeft;
718
if (aIsOpaque) {
719
mOpaquenessTintLayer.backgroundColor =
720
[[[NSColor greenColor] colorWithAlphaComponent:0.5] CGColor];
721
} else {
722
mOpaquenessTintLayer.backgroundColor =
723
[[[NSColor redColor] colorWithAlphaComponent:0.5] CGColor];
724
}
725
[mWrappingCALayer addSublayer:mOpaquenessTintLayer];
726
} else if (!shouldTintOpaqueness && mOpaquenessTintLayer) {
727
[mOpaquenessTintLayer removeFromSuperlayer];
728
[mOpaquenessTintLayer release];
729
mOpaquenessTintLayer = nullptr;
730
}
731
732
// CALayers have a position and a size, specified through the position and the bounds properties.
733
// layer.bounds.origin must always be (0, 0).
734
// A layer's position affects the layer's entire layer subtree. In other words, each layer's
735
// position is relative to its superlayer's position. We implement the clip rect using
736
// masksToBounds on mWrappingCALayer. So mContentCALayer's position is relative to the clip rect
737
// position.
738
// Note: The Core Animation docs on "Positioning and Sizing Sublayers" say:
739
// Important: Always use integral numbers for the width and height of your layer.
740
// We hope that this refers to integral physical pixels, and not to integral logical coordinates.
741
742
auto globalClipOrigin = aClipRect ? aClipRect->TopLeft() : gfx::IntPoint{};
743
auto globalLayerOrigin = aPosition;
744
auto clipToLayerOffset = globalLayerOrigin - globalClipOrigin;
745
746
if (mMutatedBackingScale) {
747
mContentCALayer.bounds =
748
CGRectMake(0, 0, aSize.width / aBackingScale, aSize.height / aBackingScale);
749
if (mOpaquenessTintLayer) {
750
mOpaquenessTintLayer.bounds = mContentCALayer.bounds;
751
}
752
mContentCALayer.contentsScale = aBackingScale;
753
}
754
755
if (mMutatedBackingScale || mMutatedClipRect) {
756
mWrappingCALayer.position =
757
CGPointMake(globalClipOrigin.x / aBackingScale, globalClipOrigin.y / aBackingScale);
758
if (aClipRect) {
759
mWrappingCALayer.masksToBounds = YES;
760
mWrappingCALayer.bounds =
761
CGRectMake(0, 0, aClipRect->Width() / aBackingScale, aClipRect->Height() / aBackingScale);
762
} else {
763
mWrappingCALayer.masksToBounds = NO;
764
}
765
}
766
767
if (mMutatedBackingScale || mMutatedPosition || mMutatedClipRect) {
768
mContentCALayer.position =
769
CGPointMake(clipToLayerOffset.x / aBackingScale, clipToLayerOffset.y / aBackingScale);
770
if (mOpaquenessTintLayer) {
771
mOpaquenessTintLayer.position = mContentCALayer.position;
772
}
773
}
774
775
if (mMutatedBackingScale || mMutatedSurfaceIsFlipped) {
776
if (aSurfaceIsFlipped) {
777
CGFloat height = aSize.height / aBackingScale;
778
mContentCALayer.affineTransform = CGAffineTransformMake(1.0, 0.0, 0.0, -1.0, 0.0, height);
779
} else {
780
mContentCALayer.affineTransform = CGAffineTransformIdentity;
781
}
782
}
783
784
if (mMutatedFrontSurface) {
785
mContentCALayer.contents = (id)aFrontSurface.get();
786
}
787
788
mMutatedPosition = false;
789
mMutatedBackingScale = false;
790
mMutatedSurfaceIsFlipped = false;
791
mMutatedClipRect = false;
792
mMutatedFrontSurface = false;
793
}
794
795
// Called when mMutex is already being held by the current thread.
796
Maybe<NativeLayerCA::SurfaceWithInvalidRegion> NativeLayerCA::GetUnusedSurfaceAndCleanUp(
797
const MutexAutoLock&) {
798
std::vector<SurfaceWithInvalidRegionAndCheckCount> usedSurfaces;
799
Maybe<SurfaceWithInvalidRegion> unusedSurface;
800
801
// Separate mSurfaces into used and unused surfaces.
802
for (auto& surf : mSurfaces) {
803
if (IOSurfaceIsInUse(surf.mEntry.mSurface.get())) {
804
surf.mCheckCount++;
805
if (surf.mCheckCount < 10) {
806
usedSurfaces.push_back(std::move(surf));
807
} else {
808
// The window server has been holding on to this surface for an unreasonably long time. This
809
// is known to happen sometimes, for example in occluded windows or after a GPU switch. In
810
// that case, release our references to the surface so that it doesn't look like we're
811
// trying to keep it alive.
812
mSurfacePoolHandle->ReturnSurfaceToPool(std::move(surf.mEntry.mSurface));
813
}
814
} else {
815
if (unusedSurface) {
816
// Multiple surfaces are unused. Keep the most recent one and release any earlier ones. The
817
// most recent one requires the least amount of copying during partial repaints.
818
mSurfacePoolHandle->ReturnSurfaceToPool(std::move(unusedSurface->mSurface));
819
}
820
unusedSurface = Some(std::move(surf.mEntry));
821
}
822
}
823
824
// Put the used surfaces back into mSurfaces.
825
mSurfaces = std::move(usedSurfaces);
826
827
return unusedSurface;
828
}
829
830
} // namespace layers
831
} // namespace mozilla