diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/layers/SurfacePoolCA.h | 287 |
1 files changed, 287 insertions, 0 deletions
diff --git a/gfx/layers/SurfacePoolCA.h b/gfx/layers/SurfacePoolCA.h new file mode 100644 index 0000000000..b97e7b45ed --- /dev/null +++ b/gfx/layers/SurfacePoolCA.h @@ -0,0 +1,287 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef mozilla_layers_SurfacePoolCA_h +#define mozilla_layers_SurfacePoolCA_h + +#include <IOSurface/IOSurface.h> + +#include <deque> +#include <unordered_map> + +#include "mozilla/Atomics.h" +#include "mozilla/DataMutex.h" + +#include "mozilla/layers/SurfacePool.h" +#include "CFTypeRefPtr.h" +#include "nsISupportsImpl.h" + +namespace mozilla { + +namespace gl { +class MozFramebuffer; +} // namespace gl + +namespace layers { + +class SurfacePoolHandleCA; +struct SurfacePoolCAWrapperForGL; + +// An implementation of SurfacePool for IOSurfaces and GL framebuffers. +// The goal of having this pool is to avoid creating and destroying IOSurfaces +// and framebuffers frequently, because doing so is expensive. +// SurfacePoolCA is threadsafe. All its data is wrapped inside LockedPool, and +// each access to LockedPool is guarded with a lock through DataMutex. +// +// The pool satisfies the following requirements: +// - It can be shared across windows, even across windows with different +// GLContexts. +// - The number of unused surfaces that are available for recycling is capped +// to a fixed value per pool, regardless of how many windows use that pool. +// - When all windows are closed (all handles are gone), no surfaces are kept +// alive (the pool is destroyed). +// - There is an explicit way of deleting GL resources for a GLContext so that +// it can happen at a deterministic time on the right thread. +// - Additionally, once a GLContext is no longer being used in any window +// (really: any pool handle), all surface-associated GL resources of that +// context are destroyed. +// - For every IOSurface, only one set of GL resources is in existence at any +// given time. We don't want there to be framebuffers in two different +// GLContexts for one surface. +// - We do not want to recycle an IOSurface that currently has GL resources of +// context A for a pool handle that uses context B. +// - We need to delay IOSurface recycling until the window server is done with +// the surface (`!IOSurfaceIsInUse(surf)`) +class SurfacePoolCA final : public SurfacePool { + public: + // Get a handle for a new window. aGL can be nullptr. + RefPtr<SurfacePoolHandle> GetHandleForGL(gl::GLContext* aGL) override; + + // Destroy all GL resources associated with aGL managed by this pool. + void DestroyGLResourcesForContext(gl::GLContext* aGL) override; + + private: + friend struct SurfacePoolCAWrapperForGL; + friend class SurfacePoolHandleCA; + friend RefPtr<SurfacePool> SurfacePool::Create(size_t aPoolSizeLimit); + + explicit SurfacePoolCA(size_t aPoolSizeLimit); + ~SurfacePoolCA() override; + + // Get an existing surface of aSize from the pool or create a new surface. + // The returned surface is guaranteed not to be in use by the window server. + CFTypeRefPtr<IOSurfaceRef> ObtainSurfaceFromPool(const gfx::IntSize& aSize, + gl::GLContext* aGL); + + // Place a surface that was previously obtained from this pool back into the + // pool. aSurface may or may not be in use by the window server. + void ReturnSurfaceToPool(CFTypeRefPtr<IOSurfaceRef> aSurface); + + // Re-run checks whether the window server still uses IOSurfaces which are + // eligible for recycling. The purpose of the "generation" counter is to + // reduce the number of calls to IOSurfaceIsInUse in a scenario where many + // windows / handles are calling CollectPendingSurfaces in the same frame + // (i.e. multiple simultaneously-animating windows). + uint64_t CollectPendingSurfaces(uint64_t aCheckGenerationsUpTo); + + // Enforce the pool size limit by evicting surfaces as necessary. This should + // happen at the end of the frame so that we can temporarily exceed the limit + // within a frame. + void EnforcePoolSizeLimit(); + + // Get or create the framebuffer for the given surface and GL context. + // The returned framebuffer handle will become invalid once + // DestroyGLResourcesForContext or DecrementGLContextHandleCount are called. + // The framebuffer's depth buffer (if present) may be shared between multiple + // framebuffers! Do not assume anything about the depth buffer's existing + // contents (i.e. clear it at the beginning of the draw), and do not + // interleave drawing commands to different framebuffers in such a way that + // the shared depth buffer could cause trouble. + Maybe<GLuint> GetFramebufferForSurface(CFTypeRefPtr<IOSurfaceRef> aSurface, + gl::GLContext* aGL, + bool aNeedsDepthBuffer); + + // Called by the destructor of SurfacePoolCAWrapperForGL so that we can clear + // our weak reference to it and delete GL resources. + void OnWrapperDestroyed(gl::GLContext* aGL, + SurfacePoolCAWrapperForGL* aWrapper); + + // The actual pool implementation lives in LockedPool, which is accessed in + // a thread-safe manner. + struct LockedPool { + explicit LockedPool(size_t aPoolSizeLimit); + LockedPool(LockedPool&&) = default; + ~LockedPool(); + + RefPtr<SurfacePoolCAWrapperForGL> GetWrapperForGL(SurfacePoolCA* aPool, + gl::GLContext* aGL); + void DestroyGLResourcesForContext(gl::GLContext* aGL); + + CFTypeRefPtr<IOSurfaceRef> ObtainSurfaceFromPool(const gfx::IntSize& aSize, + gl::GLContext* aGL); + void ReturnSurfaceToPool(CFTypeRefPtr<IOSurfaceRef> aSurface); + uint64_t CollectPendingSurfaces(uint64_t aCheckGenerationsUpTo); + void EnforcePoolSizeLimit(); + Maybe<GLuint> GetFramebufferForSurface(CFTypeRefPtr<IOSurfaceRef> aSurface, + gl::GLContext* aGL, + bool aNeedsDepthBuffer); + void OnWrapperDestroyed(gl::GLContext* aGL, + SurfacePoolCAWrapperForGL* aWrapper); + uint64_t EstimateTotalMemory(); + + uint64_t mCollectionGeneration = 0; + + protected: + struct GLResourcesForSurface { + RefPtr<gl::GLContext> mGLContext; // non-null + UniquePtr<gl::MozFramebuffer> mFramebuffer; // non-null + }; + + struct SurfacePoolEntry { + gfx::IntSize mSize; + CFTypeRefPtr<IOSurfaceRef> mIOSurface; // non-null + Maybe<GLResourcesForSurface> mGLResources; + }; + + struct PendingSurfaceEntry { + SurfacePoolEntry mEntry; + // The value of LockedPool::mCollectionGeneration at the time + // IOSurfaceIsInUse was last called for mEntry.mIOSurface. + uint64_t mPreviousCheckGeneration; + // The number of times an IOSurfaceIsInUse check has been performed. + uint64_t mCheckCount; + }; + + template <typename F> + void MutateEntryStorage(const char* aMutationType, + const gfx::IntSize& aSize, F aFn); + + template <typename F> + void ForEachEntry(F aFn); + + bool CanRecycleSurfaceForRequest(const SurfacePoolEntry& aEntry, + const gfx::IntSize& aSize, + gl::GLContext* aGL); + + RefPtr<gl::DepthAndStencilBuffer> GetDepthBufferForSharing( + gl::GLContext* aGL, const gfx::IntSize& aSize); + UniquePtr<gl::MozFramebuffer> CreateFramebufferForTexture( + gl::GLContext* aGL, const gfx::IntSize& aSize, GLuint aTexture, + bool aNeedsDepthBuffer); + + // Every IOSurface that is managed by the pool is wrapped in a + // SurfacePoolEntry object. Every entry is stored in one of three buckets at + // any given time: mInUseEntries, mPendingEntries, or mAvailableEntries. All + // mutations to these buckets are performed via calls to + // MutateEntryStorage(). Entries can move between the buckets in the + // following ways: + // + // [new] + // | Create + // v + // +----------------------------------------------------------------+ + // | mInUseEntries | + // +------+------------------------------+--------------------------+ + // | ^ | Start waiting for + // | | Recycle v + // | | +-----------------------------+ + // | | | mPendingEntries | + // | | +--+--------------------+-----+ + // | Retain | | Stop waiting for | + // v | v | + // +-------------------+-------------------------+ | + // | mAvailableEntries | | + // +-----------------------------+---------------+ | + // | Evict | Eject + // v v + // [destroyed] [destroyed] + // + // Each arrow corresponds to one invocation of MutateEntryStorage() with the + // arrow's label passed as the aMutationType string parameter. + + // Stores the entries for surfaces that are in use by NativeLayerCA, i.e. an + // entry is inside mInUseEntries between calls to ObtainSurfaceFromPool() + // and ReturnSurfaceToPool(). + std::unordered_map<CFTypeRefPtr<IOSurfaceRef>, SurfacePoolEntry> + mInUseEntries; + + // Stores entries which are no longer in use by NativeLayerCA but are still + // in use by the window server, i.e. for which + // IOSurfaceIsInUse(pendingSurfaceEntry.mEntry.mIOSurface.get()) still + // returns true. These entries are checked once per frame inside + // CollectPendingSurfaces(), and returned to mAvailableEntries once the + // window server is done. + nsTArray<PendingSurfaceEntry> mPendingEntries; + + // Stores entries which are available for recycling. These entries are not + // in use by a NativeLayerCA or by the window server. + nsTArray<SurfacePoolEntry> mAvailableEntries; + + // Keeps weak references to SurfacePoolCAWrapperForGL instances. + // For each GLContext* value (including nullptr), only one wrapper can + // exist at any time. The wrapper keeps a strong reference to us and + // notifies us when it gets destroyed. At that point we can call + // DestroyGLResourcesForContext because we know no other SurfaceHandles for + // that context exist. + std::unordered_map<gl::GLContext*, SurfacePoolCAWrapperForGL*> mWrappers; + size_t mPoolSizeLimit = 0; + + struct DepthBufferEntry { + RefPtr<gl::GLContext> mGLContext; + gfx::IntSize mSize; + WeakPtr<gl::DepthAndStencilBuffer> mBuffer; + }; + + nsTArray<DepthBufferEntry> mDepthBuffers; + }; + + DataMutex<LockedPool> mPool; +}; + +// One process-wide instance per (SurfacePoolCA*, GLContext*) pair. +// Keeps the SurfacePool alive, and the SurfacePool has a weak reference to the +// wrapper so that it can ensure that there's only one wrapper for it per +// GLContext* at any time. +struct SurfacePoolCAWrapperForGL { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SurfacePoolCAWrapperForGL); + + const RefPtr<SurfacePoolCA> mPool; // non-null + const RefPtr<gl::GLContext> mGL; // can be null + + SurfacePoolCAWrapperForGL(SurfacePoolCA* aPool, gl::GLContext* aGL) + : mPool(aPool), mGL(aGL) {} + + protected: + ~SurfacePoolCAWrapperForGL() { mPool->OnWrapperDestroyed(mGL, this); } +}; + +// A surface pool handle that is stored on NativeLayerCA and keeps the +// SurfacePool alive. +class SurfacePoolHandleCA final : public SurfacePoolHandle { + public: + SurfacePoolHandleCA* AsSurfacePoolHandleCA() override { return this; } + const auto& gl() { return mPoolWrapper->mGL; } + CFTypeRefPtr<IOSurfaceRef> ObtainSurfaceFromPool(const gfx::IntSize& aSize); + void ReturnSurfaceToPool(CFTypeRefPtr<IOSurfaceRef> aSurface); + Maybe<GLuint> GetFramebufferForSurface(CFTypeRefPtr<IOSurfaceRef> aSurface, + bool aNeedsDepthBuffer); + RefPtr<SurfacePool> Pool() override { return mPoolWrapper->mPool; } + void OnBeginFrame() override; + void OnEndFrame() override; + + private: + friend class SurfacePoolCA; + SurfacePoolHandleCA(RefPtr<SurfacePoolCAWrapperForGL>&& aPoolWrapper, + uint64_t aCurrentCollectionGeneration); + ~SurfacePoolHandleCA() override; + + const RefPtr<SurfacePoolCAWrapperForGL> mPoolWrapper; + DataMutex<uint64_t> mPreviousFrameCollectionGeneration; +}; + +} // namespace layers +} // namespace mozilla + +#endif // mozilla_layers_SurfacePoolCA_h |