Skip to content

Commit

Permalink
Bug 1592044 - Reduce the frequency of IOSurface and framebuffer creat…
Browse files Browse the repository at this point in the history
…ion and destruction with the help of a surface pool. r=jgilbert

There are multiple SurfacePools: Main thread painting and the non-WebRender compositors create a new pool per window, and WebRender creates one shared pool across all windows. The non-WebRender users set the pool size limit to zero, i.e. no recycling across paints. This preserves the pre-existing behavior.
WebRender's pool size is configurable with the gfx.webrender.compositor.surface-pool-size pref.
Every window holds on to a SurfacePoolHandle. A SurfacePoolHandle has an owning reference to the pool, via a surface pool wrapper. Once all handles are gone, the surface pool goes away, too.
The SurfacePool holds on to IOSurfaces and MozFramebuffers. Both are created on demand, independently, but are associated with each other.
A given NativeLayer uses only one surface pool handle during its lifetime. The native layer no longer influences which GLContext its framebuffers are created for; the GL context is now managed by the surface pool handle.
As a result, a NativeLayer can no longer change which GLContext its framebuffers are created by.
So in the future, if we ever need to migrate a window frome one GLContext to another, we will need to recreate the NativeLayers inside it. I think that's ok.

Differential Revision: https://phabricator.services.mozilla.com/D54859
  • Loading branch information
mstange committed Dec 18, 2019
1 parent 910c401 commit c8dfd36
Show file tree
Hide file tree
Showing 22 changed files with 924 additions and 110 deletions.
3 changes: 3 additions & 0 deletions gfx/layers/Compositor.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "mozilla/gfx/Triangle.h" // for Triangle, TexturedTriangle
#include "mozilla/layers/CompositorTypes.h" // for DiagnosticTypes, etc
#include "mozilla/layers/LayersTypes.h" // for LayersBackend
#include "mozilla/layers/SurfacePool.h" // for SurfacePoolHandle
#include "mozilla/layers/TextureSourceProvider.h"
#include "mozilla/widget/CompositorWidget.h"
#include "nsISupportsImpl.h" // for MOZ_COUNT_CTOR, etc
Expand Down Expand Up @@ -512,6 +513,8 @@ class Compositor : public TextureSourceProvider {

virtual void WaitForGPU() {}

virtual RefPtr<SurfacePoolHandle> GetSurfacePoolHandle() { return nullptr; }

/**
* Whether textures created by this compositor can receive partial updates.
*/
Expand Down
37 changes: 16 additions & 21 deletions gfx/layers/NativeLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace layers {

class NativeLayer;
class NativeLayerCA;
class SurfacePoolHandle;

// NativeLayerRoot and NativeLayer allow building up a flat layer "tree" of
// sibling layers. These layers provide a cross-platform abstraction for the
Expand All @@ -33,8 +34,9 @@ class NativeLayerRoot {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(NativeLayerRoot)

virtual already_AddRefed<NativeLayer> CreateLayer(const gfx::IntSize& aSize,
bool aIsOpaque) = 0;
virtual already_AddRefed<NativeLayer> CreateLayer(
const gfx::IntSize& aSize, bool aIsOpaque,
SurfacePoolHandle* aSurfacePoolHandle) = 0;
virtual void AppendLayer(NativeLayer* aLayer) = 0;
virtual void RemoveLayer(NativeLayer* aLayer) = 0;
virtual void SetLayers(const nsTArray<RefPtr<NativeLayer>>& aLayers) = 0;
Expand Down Expand Up @@ -101,31 +103,19 @@ class NativeLayer {
virtual RefPtr<gfx::DrawTarget> NextSurfaceAsDrawTarget(
const gfx::IntRegion& aUpdateRegion, gfx::BackendType aBackendType) = 0;

// Set the GLContext to use for the MozFramebuffer that are returned from
// NextSurfaceAsFramebuffer. If changed to a different value, all
// MozFramebuffers tracked by this layer will be discarded.
// It's a good idea to call SetGLContext(nullptr) before destroying this
// layer so that GL resource destruction happens at a good time and on the
// right thread.
virtual void SetGLContext(gl::GLContext* aGLContext) = 0;
virtual gl::GLContext* GetGLContext() = 0;

// Must only be called if a non-null GLContext is set on this layer.
// Returns a GLuint for a framebuffer that can be used for drawing to the
// surface. The size of the framebuffer will be the same as the size of this
// layer. If aNeedsDepth is true, the framebuffer is created with a depth
// buffer. The caller should draw to the framebuffer, unbind it, and then call
// NotifySurfaceReady(). It can limit its drawing to aUpdateRegion (which is
// in the framebuffer's device space, possibly "upside down" if
// SurfaceIsFlipped()). The framebuffer will be created using the GLContext
// that was set on this layer with a call to SetGLContext. The NativeLayer
// will keep a reference to the MozFramebuffer so that it can reuse the same
// MozFramebuffer whenever it uses the same underlying surface. Calling
// SetGLContext with a different context will release that reference. After a
// call to NextSurface*, NextSurface* must not be called again until after
// NotifySurfaceReady has been called. Can be called on any thread. When used
// from multiple threads, callers need to make sure that they still only call
// NextSurface and NotifySurfaceReady alternatingly and not in any other
// SurfaceIsFlipped()).
// The framebuffer will be created in the GLContext that this layer's
// SurfacePoolHandle was created for.
// After a call to NextSurface*, NextSurface* must not be called again until
// after NotifySurfaceReady has been called. Can be called on any thread. When
// used from multiple threads, callers need to make sure that they still only
// call NextSurface and NotifySurfaceReady alternatingly and not in any other
// order.
// aUpdateRegion must not extend beyond the layer size.
virtual Maybe<GLuint> NextSurfaceAsFramebuffer(
Expand All @@ -136,6 +126,11 @@ class NativeLayer {
// the screen. Resets the invalid region on the surface to the empty region.
virtual void NotifySurfaceReady() = 0;

// If you know that this layer will likely not draw any more frames, then it's
// good to call DiscardBackbuffers in order to save memory and allow other
// layer's to pick up the released surfaces from the pool.
virtual void DiscardBackbuffers() = 0;

protected:
virtual ~NativeLayer() {}
};
Expand Down
20 changes: 10 additions & 10 deletions gfx/layers/NativeLayerCA.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class MozFramebuffer;

namespace layers {

class SurfacePoolHandleCA;

// NativeLayerRootCA is the CoreAnimation implementation of the NativeLayerRoot
// interface. A NativeLayerRootCA is created by the widget around an existing
// CALayer with a call to CreateForCALayer.
Expand All @@ -52,8 +54,9 @@ class NativeLayerRootCA : public NativeLayerRoot {
void SetBackingScale(float aBackingScale);

// Overridden methods
already_AddRefed<NativeLayer> CreateLayer(const gfx::IntSize& aSize,
bool aIsOpaque) override;
already_AddRefed<NativeLayer> CreateLayer(
const gfx::IntSize& aSize, bool aIsOpaque,
SurfacePoolHandle* aSurfacePoolHandle) override;
void AppendLayer(NativeLayer* aLayer) override;
void RemoveLayer(NativeLayer* aLayer) override;
void SetLayers(const nsTArray<RefPtr<NativeLayer>>& aLayers) override;
Expand Down Expand Up @@ -92,11 +95,10 @@ class NativeLayerCA : public NativeLayer {
RefPtr<gfx::DrawTarget> NextSurfaceAsDrawTarget(
const gfx::IntRegion& aUpdateRegion,
gfx::BackendType aBackendType) override;
void SetGLContext(gl::GLContext* aGLContext) override;
gl::GLContext* GetGLContext() override;
Maybe<GLuint> NextSurfaceAsFramebuffer(const gfx::IntRegion& aUpdateRegion,
bool aNeedsDepth) override;
void NotifySurfaceReady() override;
void DiscardBackbuffers() override;
bool IsOpaque() override;
void SetClipRect(const Maybe<gfx::IntRect>& aClipRect) override;
Maybe<gfx::IntRect> ClipRect() override;
Expand All @@ -106,7 +108,8 @@ class NativeLayerCA : public NativeLayer {
protected:
friend class NativeLayerRootCA;

NativeLayerCA(const gfx::IntSize& aSize, bool aIsOpaque);
NativeLayerCA(const gfx::IntSize& aSize, bool aIsOpaque,
SurfacePoolHandleCA* aSurfacePoolHandle);
~NativeLayerCA() override;

// Gets the next surface for drawing from our swap chain and stores it in
Expand Down Expand Up @@ -157,7 +160,7 @@ class NativeLayerCA : public NativeLayer {
// Each IOSurface is initially created inside NextSurface.
// The surface stays alive until the recycling mechanism in NextSurface
// determines it is no longer needed (because the swap chain has grown too
// long) or until the layer is destroyed.
// long) or until DiscardBackbuffers() is called or the layer is destroyed.
// During the surface's lifetime, it will continuously move through the fields
// mInProgressSurface, mReadySurface, mFrontSurface, and back to front through
// the mSurfaces queue:
Expand Down Expand Up @@ -228,10 +231,7 @@ class NativeLayerCA : public NativeLayer {
// Non-null between calls to NextSurfaceAsDrawTarget and NotifySurfaceReady.
RefPtr<MacIOSurface> mInProgressLockedIOSurface;

RefPtr<gl::GLContextCGL> mGLContext;

std::unordered_map<CFTypeRefPtr<IOSurfaceRef>, UniquePtr<gl::MozFramebuffer>>
mFramebuffers;
RefPtr<SurfacePoolHandleCA> mSurfacePoolHandle;

gfx::IntPoint mPosition;
const gfx::IntSize mSize;
Expand Down
112 changes: 48 additions & 64 deletions gfx/layers/NativeLayerCA.mm
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
* 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/. */

#import "mozilla/layers/NativeLayerCA.h"
#include "mozilla/layers/NativeLayerCA.h"

#import <QuartzCore/QuartzCore.h>
#import <CoreVideo/CVPixelBuffer.h>
#import <AppKit/NSColor.h>

#include <utility>
Expand All @@ -15,6 +14,7 @@
#include "GLBlitHelper.h"
#include "GLContextCGL.h"
#include "MozFramebuffer.h"
#include "mozilla/layers/SurfacePoolCA.h"
#include "ScopedGLHelpers.h"

@interface CALayer (PrivateSetContentsOpaque)
Expand All @@ -28,6 +28,8 @@ - (void)setContentsOpaque:(BOOL)opaque;
using gfx::IntSize;
using gfx::IntRect;
using gfx::IntRegion;
using gl::GLContext;
using gl::GLContextCGL;

/* static */ already_AddRefed<NativeLayerRootCA> NativeLayerRootCA::CreateForCALayer(
CALayer* aLayer) {
Expand Down Expand Up @@ -56,8 +58,10 @@ - (void)setContentsOpaque:(BOOL)opaque;
[mRootCALayer release];
}

already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayer(const IntSize& aSize, bool aIsOpaque) {
RefPtr<NativeLayer> layer = new NativeLayerCA(aSize, aIsOpaque);
already_AddRefed<NativeLayer> NativeLayerRootCA::CreateLayer(
const IntSize& aSize, bool aIsOpaque, SurfacePoolHandle* aSurfacePoolHandle) {
RefPtr<NativeLayer> layer =
new NativeLayerCA(aSize, aIsOpaque, aSurfacePoolHandle->AsSurfacePoolHandleCA());
return layer.forget();
}

Expand Down Expand Up @@ -137,8 +141,14 @@ - (void)setContentsOpaque:(BOOL)opaque;
}
}

NativeLayerCA::NativeLayerCA(const IntSize& aSize, bool aIsOpaque)
: mMutex("NativeLayerCA"), mSize(aSize), mIsOpaque(aIsOpaque) {}
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() {
if (mInProgressLockedIOSurface) {
Expand All @@ -147,9 +157,17 @@ - (void)setContentsOpaque:(BOOL)opaque;
}
if (mInProgressSurface) {
IOSurfaceDecrementUseCount(mInProgressSurface->mSurface.get());
mSurfacePoolHandle->ReturnSurfaceToPool(mInProgressSurface->mSurface);
}
if (mReadySurface) {
IOSurfaceDecrementUseCount(mReadySurface->mSurface.get());
mSurfacePoolHandle->ReturnSurfaceToPool(mReadySurface->mSurface);
}
if (mFrontSurface) {
mSurfacePoolHandle->ReturnSurfaceToPool(mFrontSurface->mSurface);
}
for (const auto& surf : mSurfaces) {
mSurfacePoolHandle->ReturnSurfaceToPool(surf.mSurface);
}

[mContentCALayer release];
Expand Down Expand Up @@ -264,13 +282,7 @@ - (void)setContentsOpaque:(BOOL)opaque;
// Remove surf from unusedSurfaces.
unusedSurfaces.pop_back();
} else {
CFTypeRefPtr<IOSurfaceRef> newSurf = CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule(
IOSurfaceCreate((__bridge CFDictionaryRef) @{
(__bridge NSString*)kIOSurfaceWidth : @(mSize.width),
(__bridge NSString*)kIOSurfaceHeight : @(mSize.height),
(__bridge NSString*)kIOSurfacePixelFormat : @(kCVPixelFormatType_32BGRA),
(__bridge NSString*)kIOSurfaceBytesPerElement : @(4),
}));
CFTypeRefPtr<IOSurfaceRef> newSurf = mSurfacePoolHandle->ObtainSurfaceFromPool(mSize);
if (!newSurf) {
NSLog(@"NextSurface returning false because IOSurfaceCreate failed to create the surface.");
return false;
Expand All @@ -280,7 +292,7 @@ - (void)setContentsOpaque:(BOOL)opaque;

// Delete all other unused surfaces.
for (auto unusedSurf : unusedSurfaces) {
mFramebuffers.erase(unusedSurf.mSurface);
mSurfacePoolHandle->ReturnSurfaceToPool(unusedSurf.mSurface);
}
unusedSurfaces.clear();

Expand Down Expand Up @@ -357,76 +369,39 @@ - (void)setContentsOpaque:(BOOL)opaque;
return dt;
}

void NativeLayerCA::SetGLContext(gl::GLContext* aContext) {
MutexAutoLock lock(mMutex);

RefPtr<gl::GLContextCGL> glContextCGL = gl::GLContextCGL::Cast(aContext);
MOZ_RELEASE_ASSERT(glContextCGL, "Unexpected GLContext type");

if (glContextCGL != mGLContext) {
mFramebuffers.clear();
mGLContext = glContextCGL;
}
}

gl::GLContext* NativeLayerCA::GetGLContext() {
MutexAutoLock lock(mMutex);
return mGLContext;
}

Maybe<GLuint> NativeLayerCA::NextSurfaceAsFramebuffer(const gfx::IntRegion& aUpdateRegion,
bool aNeedsDepth) {
MutexAutoLock lock(mMutex);
if (!NextSurface(lock)) {
return Nothing();
}

GLuint fbo = GetOrCreateFramebufferForSurface(lock, mInProgressSurface->mSurface, aNeedsDepth);
Maybe<GLuint> fbo =
mSurfacePoolHandle->GetFramebufferForSurface(mInProgressSurface->mSurface, aNeedsDepth);
if (!fbo) {
return Nothing();
}

HandlePartialUpdate(
lock, aUpdateRegion,
[&](CFTypeRefPtr<IOSurfaceRef> validSource, const gfx::IntRegion& copyRegion) {
// Copy copyRegion from validSource to fbo.
MOZ_RELEASE_ASSERT(mGLContext);
mGLContext->MakeCurrent();
GLuint sourceFBO = GetOrCreateFramebufferForSurface(lock, validSource, false);
MOZ_RELEASE_ASSERT(mSurfacePoolHandle->gl());
mSurfacePoolHandle->gl()->MakeCurrent();
Maybe<GLuint> sourceFBO = mSurfacePoolHandle->GetFramebufferForSurface(validSource, false);
if (!sourceFBO) {
return;
}
for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
gfx::IntRect r = iter.Get();
if (mSurfaceIsFlipped) {
r.y = mSize.height - r.YMost();
}
mGLContext->BlitHelper()->BlitFramebufferToFramebuffer(sourceFBO, fbo, r, r,
LOCAL_GL_NEAREST);
mSurfacePoolHandle->gl()->BlitHelper()->BlitFramebufferToFramebuffer(*sourceFBO, *fbo, r,
r, LOCAL_GL_NEAREST);
}
});

return Some(fbo);
}

GLuint NativeLayerCA::GetOrCreateFramebufferForSurface(const MutexAutoLock&,
CFTypeRefPtr<IOSurfaceRef> aSurface,
bool aNeedsDepth) {
auto fbCursor = mFramebuffers.find(aSurface);
if (fbCursor != mFramebuffers.end()) {
return fbCursor->second->mFB;
}

MOZ_RELEASE_ASSERT(
mGLContext, "Only call NextSurfaceAsFramebuffer when a GLContext is set on this NativeLayer");
mGLContext->MakeCurrent();
GLuint tex = mGLContext->CreateTexture();
{
const gl::ScopedBindTexture bindTex(mGLContext, tex, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
CGLTexImageIOSurface2D(mGLContext->GetCGLContext(), LOCAL_GL_TEXTURE_RECTANGLE_ARB,
LOCAL_GL_RGBA, mSize.width, mSize.height, LOCAL_GL_BGRA,
LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV, aSurface.get(), 0);
}

auto fb = gl::MozFramebuffer::CreateWith(mGLContext, mSize, 0, aNeedsDepth,
LOCAL_GL_TEXTURE_RECTANGLE_ARB, tex);
GLuint fbo = fb->mFB;
mFramebuffers.insert({aSurface, std::move(fb)});

return fbo;
}

Expand All @@ -450,6 +425,15 @@ - (void)setContentsOpaque:(BOOL)opaque;
mReadySurface->mInvalidRegion = IntRect();
}

void NativeLayerCA::DiscardBackbuffers() {
MutexAutoLock lock(mMutex);

for (const auto& surf : mSurfaces) {
mSurfacePoolHandle->ReturnSurfaceToPool(surf.mSurface);
}
mSurfaces.clear();
}

void NativeLayerCA::ApplyChanges() {
MutexAutoLock lock(mMutex);

Expand Down
Loading

0 comments on commit c8dfd36

Please sign in to comment.