/* -*- 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 #include #include #include #include "mozilla/ProfilerLabels.h" #include "mozilla/ProfilerMarkers.h" #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::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 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 = Nothing(); } }); mDepthBuffers.RemoveElementsBy( [&](const DepthBufferEntry& entry) { return entry.mGLContext == aGL; }); } template void SurfacePoolCA::LockedPool::MutateEntryStorage(const char* aMutationType, const gfx::IntSize& aSize, F aFn) { [[maybe_unused]] size_t inUseCountBefore = mInUseEntries.size(); [[maybe_unused]] size_t pendingCountBefore = mPendingEntries.Length(); [[maybe_unused]] size_t availableCountBefore = mAvailableEntries.Length(); [[maybe_unused]] TimeStamp before = TimeStamp::Now(); aFn(); if (profiler_thread_is_being_profiled_for_markers()) { PROFILER_MARKER_TEXT( "SurfacePool", GRAPHICS, MarkerTiming::IntervalUntilNowFrom(before), 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))); } } template 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 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 SurfacePoolCA::LockedPool::ObtainSurfaceFromPool(const IntSize& aSize, GLContext* aGL) { // Do a linear scan through mAvailableEntries to find an eligible surface, 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 surface = iterToRecycle->mIOSurface; MOZ_RELEASE_ASSERT(surface.get(), "Available surfaces should be non-null."); // 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 surface = CFTypeRefPtr::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) { if (StaticPrefs::gfx_color_management_native_srgb()) { IOSurfaceSetValue(surface.get(), CFSTR("IOSurfaceColorSpace"), kCGColorSpaceSRGB); } // Create a new entry in mInUseEntries. MutateEntryStorage("Create", aSize, [&]() { mInUseEntries.insert({surface, SurfacePoolEntry{aSize, surface, {}}}); }); } return surface; } void SurfacePoolCA::LockedPool::ReturnSurfaceToPool(CFTypeRefPtr 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. MOZ_RELEASE_ASSERT(inUseEntryIter->second.mIOSurface.get(), "In use surfaces should be non-null."); 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. MOZ_RELEASE_ASSERT(pendingSurf.mEntry.mIOSurface.get(), "Pending surfaces should be non-null."); 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 SurfacePoolCA::LockedPool::GetFramebufferForSurface( CFTypeRefPtr 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. AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING( "Framebuffer creation", GRAPHICS_TileAllocation, nsPrintfCString("%dx%d", entry.mSize.width, entry.mSize.height)); RefPtr 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 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 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 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&& 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 SurfacePoolHandleCA::ObtainSurfaceFromPool(const IntSize& aSize) { return mPoolWrapper->mPool->ObtainSurfaceFromPool(aSize, mPoolWrapper->mGL); } void SurfacePoolHandleCA::ReturnSurfaceToPool(CFTypeRefPtr aSurface) { mPoolWrapper->mPool->ReturnSurfaceToPool(aSurface); } Maybe SurfacePoolHandleCA::GetFramebufferForSurface(CFTypeRefPtr 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 SurfacePoolCA::GetHandleForGL(GLContext* aGL) { RefPtr 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 SurfacePoolCA::ObtainSurfaceFromPool(const IntSize& aSize, GLContext* aGL) { auto pool = mPool.Lock(); return pool->ObtainSurfaceFromPool(aSize, aGL); } void SurfacePoolCA::ReturnSurfaceToPool(CFTypeRefPtr 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 SurfacePoolCA::GetFramebufferForSurface(CFTypeRefPtr 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