зеркало из https://github.com/mozilla/gecko-dev.git
437 строки
16 KiB
Plaintext
437 строки
16 KiB
Plaintext
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nullptr; 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/SurfacePoolCA.h"
|
|
|
|
#import <CoreVideo/CVPixelBuffer.h>
|
|
|
|
#include <algorithm>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
|
|
#include "mozilla/StaticMutex.h"
|
|
#include "mozilla/StaticPrefs_gfx.h"
|
|
|
|
#include "GLContextCGL.h"
|
|
#include "MozFramebuffer.h"
|
|
|
|
namespace mozilla {
|
|
namespace layers {
|
|
|
|
using gfx::IntPoint;
|
|
using gfx::IntSize;
|
|
using gfx::IntRect;
|
|
using gfx::IntRegion;
|
|
using gl::GLContext;
|
|
using gl::GLContextCGL;
|
|
|
|
/* static */ RefPtr<SurfacePool> SurfacePool::Create(size_t aPoolSizeLimit) {
|
|
return new SurfacePoolCA(aPoolSizeLimit);
|
|
}
|
|
|
|
// SurfacePoolCA::LockedPool
|
|
|
|
SurfacePoolCA::LockedPool::LockedPool(size_t aPoolSizeLimit) : mPoolSizeLimit(aPoolSizeLimit) {}
|
|
|
|
SurfacePoolCA::LockedPool::~LockedPool() {
|
|
MOZ_RELEASE_ASSERT(mWrappers.empty(),
|
|
"Any outstanding wrappers should have kept the surface pool alive");
|
|
MOZ_RELEASE_ASSERT(mInUseEntries.empty(),
|
|
"Leak! No more surfaces should be in use at this point.");
|
|
// Remove all entries in mPendingEntries and mAvailableEntries.
|
|
MutateEntryStorage("Clear", {}, [&]() {
|
|
mPendingEntries.Clear();
|
|
mAvailableEntries.Clear();
|
|
});
|
|
}
|
|
|
|
RefPtr<SurfacePoolCAWrapperForGL> SurfacePoolCA::LockedPool::GetWrapperForGL(SurfacePoolCA* aPool,
|
|
GLContext* aGL) {
|
|
auto& wrapper = mWrappers[aGL];
|
|
if (!wrapper) {
|
|
wrapper = new SurfacePoolCAWrapperForGL(aPool, aGL);
|
|
}
|
|
return wrapper;
|
|
}
|
|
|
|
void SurfacePoolCA::LockedPool::DestroyGLResourcesForContext(GLContext* aGL) {
|
|
ForEachEntry([&](SurfacePoolEntry& entry) {
|
|
if (entry.mGLResources && entry.mGLResources->mGLContext == aGL) {
|
|
entry.mGLResources = {};
|
|
}
|
|
});
|
|
mDepthBuffers.RemoveElementsBy(
|
|
[&](const DepthBufferEntry& entry) { return entry.mGLContext == aGL; });
|
|
}
|
|
|
|
template <typename F>
|
|
void SurfacePoolCA::LockedPool::MutateEntryStorage(const char* aMutationType,
|
|
const gfx::IntSize& aSize, F aFn) {
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
size_t inUseCountBefore = mInUseEntries.size();
|
|
size_t pendingCountBefore = mPendingEntries.Length();
|
|
size_t availableCountBefore = mAvailableEntries.Length();
|
|
TimeStamp before = TimeStamp::NowUnfuzzed();
|
|
#endif
|
|
|
|
aFn();
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
if (profiler_thread_is_being_profiled()) {
|
|
profiler_add_text_marker(
|
|
"SurfacePool",
|
|
nsPrintfCString("%d -> %d in use | %d -> %d waiting for | %d -> %d available | %s %dx%d | "
|
|
"%dMB total memory",
|
|
int(inUseCountBefore), int(mInUseEntries.size()), int(pendingCountBefore),
|
|
int(mPendingEntries.Length()), int(availableCountBefore),
|
|
int(mAvailableEntries.Length()), aMutationType, aSize.width, aSize.height,
|
|
int(EstimateTotalMemory() / 1000 / 1000)),
|
|
JS::ProfilingCategoryPair::GRAPHICS, before, TimeStamp::NowUnfuzzed());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
template <typename F>
|
|
void SurfacePoolCA::LockedPool::ForEachEntry(F aFn) {
|
|
for (auto& iter : mInUseEntries) {
|
|
aFn(iter.second);
|
|
}
|
|
for (auto& entry : mPendingEntries) {
|
|
aFn(entry.mEntry);
|
|
}
|
|
for (auto& entry : mAvailableEntries) {
|
|
aFn(entry);
|
|
}
|
|
}
|
|
|
|
uint64_t SurfacePoolCA::LockedPool::EstimateTotalMemory() {
|
|
std::unordered_set<const gl::DepthAndStencilBuffer*> depthAndStencilBuffers;
|
|
uint64_t memBytes = 0;
|
|
|
|
ForEachEntry([&](const SurfacePoolEntry& entry) {
|
|
auto size = entry.mSize;
|
|
memBytes += size.width * 4 * size.height;
|
|
if (entry.mGLResources) {
|
|
const auto& fb = *entry.mGLResources->mFramebuffer;
|
|
if (const auto& buffer = fb.GetDepthAndStencilBuffer()) {
|
|
depthAndStencilBuffers.insert(buffer.get());
|
|
}
|
|
}
|
|
});
|
|
|
|
for (const auto& buffer : depthAndStencilBuffers) {
|
|
memBytes += buffer->EstimateMemory();
|
|
}
|
|
|
|
return memBytes;
|
|
}
|
|
|
|
bool SurfacePoolCA::LockedPool::CanRecycleSurfaceForRequest(const SurfacePoolEntry& aEntry,
|
|
const IntSize& aSize, GLContext* aGL) {
|
|
if (aEntry.mSize != aSize) {
|
|
return false;
|
|
}
|
|
if (aEntry.mGLResources) {
|
|
return aEntry.mGLResources->mGLContext == aGL;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
CFTypeRefPtr<IOSurfaceRef> SurfacePoolCA::LockedPool::ObtainSurfaceFromPool(const IntSize& aSize,
|
|
GLContext* aGL) {
|
|
// Do a linear scan through mAvailableEntries to find an eligible suface, going from oldest to
|
|
// newest. The size of this array is limited, so the linear scan is fast.
|
|
auto iterToRecycle = std::find_if(mAvailableEntries.begin(), mAvailableEntries.end(),
|
|
[&](const SurfacePoolEntry& aEntry) {
|
|
return CanRecycleSurfaceForRequest(aEntry, aSize, aGL);
|
|
});
|
|
if (iterToRecycle != mAvailableEntries.end()) {
|
|
CFTypeRefPtr<IOSurfaceRef> surface = iterToRecycle->mIOSurface;
|
|
// Move the entry from mAvailableEntries to mInUseEntries.
|
|
MutateEntryStorage("Recycle", aSize, [&]() {
|
|
mInUseEntries.insert({surface, std::move(*iterToRecycle)});
|
|
mAvailableEntries.RemoveElementAt(iterToRecycle);
|
|
});
|
|
return surface;
|
|
}
|
|
|
|
AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING("IOSurface creation", GRAPHICS_TileAllocation,
|
|
nsPrintfCString("%dx%d", aSize.width, aSize.height));
|
|
CFTypeRefPtr<IOSurfaceRef> surface =
|
|
CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule(IOSurfaceCreate((__bridge CFDictionaryRef) @{
|
|
(__bridge NSString*)kIOSurfaceWidth : @(aSize.width),
|
|
(__bridge NSString*)kIOSurfaceHeight : @(aSize.height),
|
|
(__bridge NSString*)kIOSurfacePixelFormat : @(kCVPixelFormatType_32BGRA),
|
|
(__bridge NSString*)kIOSurfaceBytesPerElement : @(4),
|
|
}));
|
|
if (surface) {
|
|
// Create a new entry in mInUseEntries.
|
|
MutateEntryStorage("Create", aSize, [&]() {
|
|
mInUseEntries.insert({surface, SurfacePoolEntry{aSize, surface, {}}});
|
|
});
|
|
}
|
|
return surface;
|
|
}
|
|
|
|
void SurfacePoolCA::LockedPool::ReturnSurfaceToPool(CFTypeRefPtr<IOSurfaceRef> aSurface) {
|
|
auto inUseEntryIter = mInUseEntries.find(aSurface);
|
|
MOZ_RELEASE_ASSERT(inUseEntryIter != mInUseEntries.end());
|
|
if (IOSurfaceIsInUse(aSurface.get())) {
|
|
// Move the entry from mInUseEntries to mPendingEntries.
|
|
MutateEntryStorage("Start waiting for", IntSize(inUseEntryIter->second.mSize), [&]() {
|
|
mPendingEntries.AppendElement(
|
|
PendingSurfaceEntry{std::move(inUseEntryIter->second), mCollectionGeneration, 0});
|
|
mInUseEntries.erase(inUseEntryIter);
|
|
});
|
|
} else {
|
|
// Move the entry from mInUseEntries to mAvailableEntries.
|
|
MutateEntryStorage("Retain", IntSize(inUseEntryIter->second.mSize), [&]() {
|
|
mAvailableEntries.AppendElement(std::move(inUseEntryIter->second));
|
|
mInUseEntries.erase(inUseEntryIter);
|
|
});
|
|
}
|
|
}
|
|
|
|
void SurfacePoolCA::LockedPool::EnforcePoolSizeLimit() {
|
|
// Enforce the pool size limit, removing least-recently-used entries as necessary.
|
|
while (mAvailableEntries.Length() > mPoolSizeLimit) {
|
|
MutateEntryStorage("Evict", IntSize(mAvailableEntries[0].mSize),
|
|
[&]() { mAvailableEntries.RemoveElementAt(0); });
|
|
}
|
|
}
|
|
|
|
uint64_t SurfacePoolCA::LockedPool::CollectPendingSurfaces(uint64_t aCheckGenerationsUpTo) {
|
|
mCollectionGeneration++;
|
|
|
|
// Loop from back to front, potentially deleting items as we iterate.
|
|
// mPendingEntries is used as a set; the order of its items is not meaningful.
|
|
size_t i = mPendingEntries.Length();
|
|
while (i) {
|
|
i -= 1;
|
|
auto& pendingSurf = mPendingEntries[i];
|
|
if (pendingSurf.mPreviousCheckGeneration > aCheckGenerationsUpTo) {
|
|
continue;
|
|
}
|
|
// Check if the window server is still using the surface. As long as it is doing that, we cannot
|
|
// move the surface to mAvailableSurfaces because anything we draw to it could reach the screen
|
|
// in a place where we don't expect it.
|
|
if (IOSurfaceIsInUse(pendingSurf.mEntry.mIOSurface.get())) {
|
|
// The surface is still in use. Update mPreviousCheckGeneration and mCheckCount.
|
|
pendingSurf.mPreviousCheckGeneration = mCollectionGeneration;
|
|
pendingSurf.mCheckCount++;
|
|
if (pendingSurf.mCheckCount >= 30) {
|
|
// The window server has been holding on to this surface for an unreasonably long time. This
|
|
// is known to happen sometimes, for example in occluded windows or after a GPU switch. In
|
|
// that case, release our references to the surface so that it's Not Our Problem anymore.
|
|
// Remove the entry from mPendingEntries.
|
|
MutateEntryStorage("Eject", IntSize(pendingSurf.mEntry.mSize),
|
|
[&]() { mPendingEntries.RemoveElementAt(i); });
|
|
}
|
|
} else {
|
|
// The surface has become unused!
|
|
// Move the entry from mPendingEntries to mAvailableEntries.
|
|
MutateEntryStorage("Stop waiting for", IntSize(pendingSurf.mEntry.mSize), [&]() {
|
|
mAvailableEntries.AppendElement(std::move(pendingSurf.mEntry));
|
|
mPendingEntries.RemoveElementAt(i);
|
|
});
|
|
}
|
|
}
|
|
return mCollectionGeneration;
|
|
}
|
|
|
|
void SurfacePoolCA::LockedPool::OnWrapperDestroyed(gl::GLContext* aGL,
|
|
SurfacePoolCAWrapperForGL* aWrapper) {
|
|
if (aGL) {
|
|
DestroyGLResourcesForContext(aGL);
|
|
}
|
|
|
|
auto iter = mWrappers.find(aGL);
|
|
MOZ_RELEASE_ASSERT(iter != mWrappers.end());
|
|
MOZ_RELEASE_ASSERT(iter->second == aWrapper, "Only one SurfacePoolCAWrapperForGL object should "
|
|
"exist for each GLContext* at any time");
|
|
mWrappers.erase(iter);
|
|
}
|
|
|
|
Maybe<GLuint> SurfacePoolCA::LockedPool::GetFramebufferForSurface(
|
|
CFTypeRefPtr<IOSurfaceRef> aSurface, GLContext* aGL, bool aNeedsDepthBuffer) {
|
|
MOZ_RELEASE_ASSERT(aGL);
|
|
|
|
auto inUseEntryIter = mInUseEntries.find(aSurface);
|
|
MOZ_RELEASE_ASSERT(inUseEntryIter != mInUseEntries.end());
|
|
|
|
SurfacePoolEntry& entry = inUseEntryIter->second;
|
|
if (entry.mGLResources) {
|
|
// We have an existing framebuffer.
|
|
MOZ_RELEASE_ASSERT(entry.mGLResources->mGLContext == aGL,
|
|
"Recycled surface that still had GL resources from a different GL context. "
|
|
"This shouldn't happen.");
|
|
if (!aNeedsDepthBuffer || entry.mGLResources->mFramebuffer->HasDepth()) {
|
|
return Some(entry.mGLResources->mFramebuffer->mFB);
|
|
}
|
|
}
|
|
|
|
// No usable existing framebuffer, we need to create one.
|
|
|
|
#ifdef MOZ_GECKO_PROFILER
|
|
AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING(
|
|
"Framebuffer creation", GRAPHICS_TileAllocation,
|
|
nsPrintfCString("%dx%d", entry.mSize.width, entry.mSize.height));
|
|
#endif
|
|
|
|
RefPtr<GLContextCGL> cgl = GLContextCGL::Cast(aGL);
|
|
MOZ_RELEASE_ASSERT(cgl, "Unexpected GLContext type");
|
|
|
|
if (!aGL->MakeCurrent()) {
|
|
// Context may have been destroyed.
|
|
return {};
|
|
}
|
|
|
|
GLuint tex = aGL->CreateTexture();
|
|
{
|
|
const gl::ScopedBindTexture bindTex(aGL, tex, LOCAL_GL_TEXTURE_RECTANGLE_ARB);
|
|
CGLTexImageIOSurface2D(cgl->GetCGLContext(), LOCAL_GL_TEXTURE_RECTANGLE_ARB, LOCAL_GL_RGBA,
|
|
entry.mSize.width, entry.mSize.height, LOCAL_GL_BGRA,
|
|
LOCAL_GL_UNSIGNED_INT_8_8_8_8_REV, entry.mIOSurface.get(), 0);
|
|
}
|
|
|
|
auto fb = CreateFramebufferForTexture(aGL, entry.mSize, tex, aNeedsDepthBuffer);
|
|
if (!fb) {
|
|
// Framebuffer completeness check may have failed.
|
|
return {};
|
|
}
|
|
|
|
GLuint fbo = fb->mFB;
|
|
entry.mGLResources = Some(GLResourcesForSurface{aGL, std::move(fb)});
|
|
return Some(fbo);
|
|
}
|
|
|
|
RefPtr<gl::DepthAndStencilBuffer> SurfacePoolCA::LockedPool::GetDepthBufferForSharing(
|
|
GLContext* aGL, const IntSize& aSize) {
|
|
// Clean out entries for which the weak pointer has become null.
|
|
mDepthBuffers.RemoveElementsBy([&](const DepthBufferEntry& entry) { return !entry.mBuffer; });
|
|
|
|
for (const auto& entry : mDepthBuffers) {
|
|
if (entry.mGLContext == aGL && entry.mSize == aSize) {
|
|
return entry.mBuffer.get();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UniquePtr<gl::MozFramebuffer> SurfacePoolCA::LockedPool::CreateFramebufferForTexture(
|
|
GLContext* aGL, const IntSize& aSize, GLuint aTexture, bool aNeedsDepthBuffer) {
|
|
if (aNeedsDepthBuffer) {
|
|
// Try to find an existing depth buffer of aSize in aGL and create a framebuffer that shares it.
|
|
if (auto buffer = GetDepthBufferForSharing(aGL, aSize)) {
|
|
return gl::MozFramebuffer::CreateForBackingWithSharedDepthAndStencil(
|
|
aSize, 0, LOCAL_GL_TEXTURE_RECTANGLE_ARB, aTexture, buffer);
|
|
}
|
|
}
|
|
|
|
// No depth buffer needed or we didn't find one. Create a framebuffer with a new depth buffer and
|
|
// store a weak pointer to the new depth buffer in mDepthBuffers.
|
|
UniquePtr<gl::MozFramebuffer> fb = gl::MozFramebuffer::CreateForBacking(
|
|
aGL, aSize, 0, aNeedsDepthBuffer, LOCAL_GL_TEXTURE_RECTANGLE_ARB, aTexture);
|
|
if (fb && fb->GetDepthAndStencilBuffer()) {
|
|
mDepthBuffers.AppendElement(DepthBufferEntry{aGL, aSize, fb->GetDepthAndStencilBuffer().get()});
|
|
}
|
|
|
|
return fb;
|
|
}
|
|
|
|
// SurfacePoolHandleCA
|
|
|
|
SurfacePoolHandleCA::SurfacePoolHandleCA(RefPtr<SurfacePoolCAWrapperForGL>&& aPoolWrapper,
|
|
uint64_t aCurrentCollectionGeneration)
|
|
: mPoolWrapper(aPoolWrapper),
|
|
mPreviousFrameCollectionGeneration(
|
|
"SurfacePoolHandleCA::mPreviousFrameCollectionGeneration") {
|
|
auto generation = mPreviousFrameCollectionGeneration.Lock();
|
|
*generation = aCurrentCollectionGeneration;
|
|
}
|
|
|
|
SurfacePoolHandleCA::~SurfacePoolHandleCA() {}
|
|
|
|
void SurfacePoolHandleCA::OnBeginFrame() {
|
|
auto generation = mPreviousFrameCollectionGeneration.Lock();
|
|
*generation = mPoolWrapper->mPool->CollectPendingSurfaces(*generation);
|
|
}
|
|
|
|
void SurfacePoolHandleCA::OnEndFrame() { mPoolWrapper->mPool->EnforcePoolSizeLimit(); }
|
|
|
|
CFTypeRefPtr<IOSurfaceRef> SurfacePoolHandleCA::ObtainSurfaceFromPool(const IntSize& aSize) {
|
|
return mPoolWrapper->mPool->ObtainSurfaceFromPool(aSize, mPoolWrapper->mGL);
|
|
}
|
|
|
|
void SurfacePoolHandleCA::ReturnSurfaceToPool(CFTypeRefPtr<IOSurfaceRef> aSurface) {
|
|
mPoolWrapper->mPool->ReturnSurfaceToPool(aSurface);
|
|
}
|
|
|
|
Maybe<GLuint> SurfacePoolHandleCA::GetFramebufferForSurface(CFTypeRefPtr<IOSurfaceRef> aSurface,
|
|
bool aNeedsDepthBuffer) {
|
|
return mPoolWrapper->mPool->GetFramebufferForSurface(aSurface, mPoolWrapper->mGL,
|
|
aNeedsDepthBuffer);
|
|
}
|
|
|
|
// SurfacePoolCA
|
|
|
|
SurfacePoolCA::SurfacePoolCA(size_t aPoolSizeLimit)
|
|
: mPool(LockedPool(aPoolSizeLimit), "SurfacePoolCA::mPool") {}
|
|
|
|
SurfacePoolCA::~SurfacePoolCA() {}
|
|
|
|
RefPtr<SurfacePoolHandle> SurfacePoolCA::GetHandleForGL(GLContext* aGL) {
|
|
RefPtr<SurfacePoolCAWrapperForGL> wrapper;
|
|
uint64_t collectionGeneration = 0;
|
|
{
|
|
auto pool = mPool.Lock();
|
|
wrapper = pool->GetWrapperForGL(this, aGL);
|
|
collectionGeneration = pool->mCollectionGeneration;
|
|
}
|
|
|
|
// Run the SurfacePoolHandleCA constructor outside of the lock so that the
|
|
// mPool lock and the handle's lock are always ordered the same way.
|
|
return new SurfacePoolHandleCA(std::move(wrapper), collectionGeneration);
|
|
}
|
|
|
|
void SurfacePoolCA::DestroyGLResourcesForContext(GLContext* aGL) {
|
|
auto pool = mPool.Lock();
|
|
pool->DestroyGLResourcesForContext(aGL);
|
|
}
|
|
|
|
CFTypeRefPtr<IOSurfaceRef> SurfacePoolCA::ObtainSurfaceFromPool(const IntSize& aSize,
|
|
GLContext* aGL) {
|
|
auto pool = mPool.Lock();
|
|
return pool->ObtainSurfaceFromPool(aSize, aGL);
|
|
}
|
|
|
|
void SurfacePoolCA::ReturnSurfaceToPool(CFTypeRefPtr<IOSurfaceRef> aSurface) {
|
|
auto pool = mPool.Lock();
|
|
pool->ReturnSurfaceToPool(aSurface);
|
|
}
|
|
|
|
uint64_t SurfacePoolCA::CollectPendingSurfaces(uint64_t aCheckGenerationsUpTo) {
|
|
auto pool = mPool.Lock();
|
|
return pool->CollectPendingSurfaces(aCheckGenerationsUpTo);
|
|
}
|
|
void SurfacePoolCA::EnforcePoolSizeLimit() {
|
|
auto pool = mPool.Lock();
|
|
pool->EnforcePoolSizeLimit();
|
|
}
|
|
|
|
Maybe<GLuint> SurfacePoolCA::GetFramebufferForSurface(CFTypeRefPtr<IOSurfaceRef> aSurface,
|
|
GLContext* aGL, bool aNeedsDepthBuffer) {
|
|
auto pool = mPool.Lock();
|
|
return pool->GetFramebufferForSurface(aSurface, aGL, aNeedsDepthBuffer);
|
|
}
|
|
|
|
void SurfacePoolCA::OnWrapperDestroyed(gl::GLContext* aGL, SurfacePoolCAWrapperForGL* aWrapper) {
|
|
auto pool = mPool.Lock();
|
|
return pool->OnWrapperDestroyed(aGL, aWrapper);
|
|
}
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|