/* -*- Mode: C++; tab-width: 20; 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/. */ #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Logging.h" #include "mozilla/gfx/MacIOSurface.h" #include "mozilla/layers/NativeLayerMacSurfaceHandler.h" #include "mozilla/layers/SurfacePoolCA.h" #include "GLBlitHelper.h" #ifdef XP_MACOSX # include "GLContextCGL.h" #else # include "GLContextEAGL.h" #endif #include "nsRegion.h" namespace mozilla { namespace layers { using gfx::DataSourceSurface; using gfx::IntPoint; using gfx::IntRect; using gfx::IntRegion; using gfx::IntSize; using gfx::Matrix4x4; using gfx::SurfaceFormat; using gl::GLContext; #ifdef XP_MACOSX using gl::GLContextCGL; #endif NativeLayerMacSurfaceHandler::NativeLayerMacSurfaceHandler( const gfx::IntSize& aSize, SurfacePoolHandleCA* aSurfacePoolHandle) : mSize(aSize), mSurfacePoolHandle(aSurfacePoolHandle) { MOZ_RELEASE_ASSERT(mSurfacePoolHandle, "Need a non-null surface pool handle."); } NativeLayerMacSurfaceHandler::~NativeLayerMacSurfaceHandler() { if (mInProgressLockedIOSurface) { mInProgressLockedIOSurface->Unlock(false); mInProgressLockedIOSurface = nullptr; } if (mInProgressSurface) { IOSurfaceDecrementUseCount(mInProgressSurface->mSurface.get()); mSurfacePoolHandle->ReturnSurfaceToPool(mInProgressSurface->mSurface); } if (mFrontSurface) { mSurfacePoolHandle->ReturnSurfaceToPool(mFrontSurface->mSurface); } for (const auto& surf : mSurfaces) { mSurfacePoolHandle->ReturnSurfaceToPool(surf.mEntry.mSurface); } } bool NativeLayerMacSurfaceHandler::NextSurface() { if (mSize.IsEmpty()) { gfxCriticalError() << "NextSurface returning false because of invalid mSize (" << mSize.width << ", " << mSize.height << ")."; return false; } MOZ_RELEASE_ASSERT(!mInProgressSurface, "ERROR: Do not call NextSurface twice in sequence. Call " "NotifySurfaceReady before the " "next call to NextSurface."); Maybe surf = GetUnusedSurfaceAndCleanUp(); if (!surf) { CFTypeRefPtr newSurf = mSurfacePoolHandle->ObtainSurfaceFromPool(mSize); MOZ_RELEASE_ASSERT( newSurf, "NextSurface IOSurfaceCreate failed to create the surface."); surf = Some(SurfaceWithInvalidRegion{newSurf, IntRect({}, mSize)}); } mInProgressSurface = std::move(surf); IOSurfaceIncrementUseCount(mInProgressSurface->mSurface.get()); return true; } void NativeLayerMacSurfaceHandler::InvalidateRegionThroughoutSwapchain( const IntRegion& aRegion) { IntRegion r = aRegion; if (mInProgressSurface) { mInProgressSurface->mInvalidRegion.OrWith(r); } if (mFrontSurface) { mFrontSurface->mInvalidRegion.OrWith(r); } for (auto& surf : mSurfaces) { surf.mEntry.mInvalidRegion.OrWith(r); } } template void NativeLayerMacSurfaceHandler::HandlePartialUpdate( const IntRect& aDisplayRect, const IntRegion& aUpdateRegion, F&& aCopyFn) { MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aUpdateRegion.GetBounds()), "The update region should be within the surface bounds."); MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aDisplayRect), "The display rect should be within the surface bounds."); MOZ_RELEASE_ASSERT(!mInProgressUpdateRegion); MOZ_RELEASE_ASSERT(!mInProgressDisplayRect); mInProgressUpdateRegion = Some(aUpdateRegion); mInProgressDisplayRect = Some(aDisplayRect); if (mFrontSurface) { // Copy not-overwritten valid content from mFrontSurface so that valid // content never gets lost. gfx::IntRegion copyRegion; copyRegion.Sub(mInProgressSurface->mInvalidRegion, aUpdateRegion); copyRegion.SubOut(mFrontSurface->mInvalidRegion); if (!copyRegion.IsEmpty()) { // Now copy the valid content, using a caller-provided copy function. aCopyFn(mFrontSurface->mSurface, copyRegion); mInProgressSurface->mInvalidRegion.SubOut(copyRegion); } } InvalidateRegionThroughoutSwapchain(aUpdateRegion); } Maybe NativeLayerMacSurfaceHandler::GetUnusedSurfaceAndCleanUp() { std::vector usedSurfaces; Maybe unusedSurface; // Separate mSurfaces into used and unused surfaces. for (auto& surf : mSurfaces) { if (IOSurfaceIsInUse(surf.mEntry.mSurface.get())) { surf.mCheckCount++; if (surf.mCheckCount < 10) { usedSurfaces.push_back(std::move(surf)); } else { // 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 doesn't look like // we're trying to keep it alive. mSurfacePoolHandle->ReturnSurfaceToPool( std::move(surf.mEntry.mSurface)); } } else { if (unusedSurface) { // Multiple surfaces are unused. Keep the most recent one and release // any earlier ones. The most recent one requires the least amount of // copying during partial repaints. mSurfacePoolHandle->ReturnSurfaceToPool( std::move(unusedSurface->mSurface)); } unusedSurface = Some(std::move(surf.mEntry)); } } // Put the used surfaces back into mSurfaces. mSurfaces = std::move(usedSurfaces); return unusedSurface; } RefPtr NativeLayerMacSurfaceHandler::NextSurfaceAsDrawTarget( const IntRect& aDisplayRect, const IntRegion& aUpdateRegion, gfx::BackendType aBackendType) { if (!NextSurface()) { return nullptr; } auto surf = MakeRefPtr(mInProgressSurface->mSurface); if (NS_WARN_IF(!surf->Lock(false))) { gfxCriticalError() << "NextSurfaceAsDrawTarget lock surface failed."; return nullptr; } mInProgressLockedIOSurface = std::move(surf); RefPtr dt = mInProgressLockedIOSurface->GetAsDrawTargetLocked(aBackendType); HandlePartialUpdate( aDisplayRect, aUpdateRegion, [&](CFTypeRefPtr validSource, const gfx::IntRegion& copyRegion) { RefPtr source = new MacIOSurface(validSource); if (source->Lock(true)) { RefPtr sourceDT = source->GetAsDrawTargetLocked(aBackendType); RefPtr sourceSurface = sourceDT->Snapshot(); for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) { const gfx::IntRect& r = iter.Get(); dt->CopySurface(sourceSurface, r, r.TopLeft()); } source->Unlock(true); } else { gfxCriticalError() << "HandlePartialUpdate lock surface failed."; } }); return dt; } Maybe NativeLayerMacSurfaceHandler::NextSurfaceAsFramebuffer( const IntRect& aDisplayRect, const IntRegion& aUpdateRegion, bool aNeedsDepth) { bool gotNextSurface = NextSurface(); MOZ_RELEASE_ASSERT(gotNextSurface, "NextSurfaceAsFramebuffer needs a surface."); Maybe fbo = mSurfacePoolHandle->GetFramebufferForSurface( mInProgressSurface->mSurface, aNeedsDepth); MOZ_RELEASE_ASSERT(fbo, "GetFramebufferForSurface failed."); HandlePartialUpdate( aDisplayRect, aUpdateRegion, [&](CFTypeRefPtr validSource, const gfx::IntRegion& copyRegion) { // Copy copyRegion from validSource to fbo. MOZ_RELEASE_ASSERT(mSurfacePoolHandle->gl()); mSurfacePoolHandle->gl()->MakeCurrent(); Maybe sourceFBO = mSurfacePoolHandle->GetFramebufferForSurface(validSource, false); MOZ_RELEASE_ASSERT( sourceFBO, "GetFramebufferForSurface failed during HandlePartialUpdate."); for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) { gfx::IntRect r = iter.Get(); if (mSurfaceIsFlipped) { r.y = mSize.height - r.YMost(); } mSurfacePoolHandle->gl()->BlitHelper()->BlitFramebufferToFramebuffer( *sourceFBO, *fbo, r, r, LOCAL_GL_NEAREST); } }); return fbo; } bool NativeLayerMacSurfaceHandler::NotifySurfaceReady() { MOZ_RELEASE_ASSERT( mInProgressSurface, "NotifySurfaceReady called without preceding call to NextSurface"); if (mInProgressLockedIOSurface) { mInProgressLockedIOSurface->Unlock(false); mInProgressLockedIOSurface = nullptr; } if (mFrontSurface) { mSurfaces.push_back({*mFrontSurface, 0}); mFrontSurface = Nothing(); } MOZ_RELEASE_ASSERT(mInProgressUpdateRegion); IOSurfaceDecrementUseCount(mInProgressSurface->mSurface.get()); mFrontSurface = std::move(mInProgressSurface); mFrontSurface->mInvalidRegion.SubOut(mInProgressUpdateRegion.extract()); bool mutatedDisplayRect = false; MOZ_RELEASE_ASSERT(mInProgressDisplayRect); if (!mDisplayRect.IsEqualInterior(*mInProgressDisplayRect)) { mutatedDisplayRect = true; mDisplayRect = *mInProgressDisplayRect; } mInProgressDisplayRect = Nothing(); return mutatedDisplayRect; } void NativeLayerMacSurfaceHandler::DiscardBackbuffers() { for (const auto& surf : mSurfaces) { mSurfacePoolHandle->ReturnSurfaceToPool(surf.mEntry.mSurface); } mSurfaces.clear(); } } // namespace layers } // namespace mozilla