Bug 1918778 - Correct ownership/recycling issues with MacIOSurface. r=bradwerth
This patch corrects a few different issues related to recycling MacIOSurface objects. 1) When recycling a surface, we must check that the cached surfaces match all of the requested parameters, not just the size. If we do not, we should just flush the whole cache immediately since they should all be created with the same parameters. 2) Allocations can fail, and we should check for failing to get a surface from the allocator and fall back if so. 3) Locking can fail, and we should check that return value at all of the call sites. This may help resolve a number of otherwise difficult to understand crash signatures. It may also solve display corruption issues in rare cases where the parameters that changed were roughly equivalent such that everything appears to work, but they differ enough to change the presentation. Differential Revision: https://phabricator.services.mozilla.com/D222205
This commit is contained in:
@@ -127,7 +127,7 @@ size_t CreatePlaneDictionary(CFTypeRefPtr<CFMutableDictionaryRef>& aDict,
|
||||
}
|
||||
|
||||
// Helper function to set common color IOSurface properties.
|
||||
void SetIOSurfaceCommonProperties(
|
||||
static void SetIOSurfaceCommonProperties(
|
||||
CFTypeRefPtr<IOSurfaceRef> surfaceRef,
|
||||
MacIOSurface::YUVColorSpace aColorSpace,
|
||||
MacIOSurface::TransferFunction aTransferFunction) {
|
||||
@@ -394,11 +394,16 @@ void MacIOSurface::DecrementUseCount() {
|
||||
::IOSurfaceDecrementUseCount(mIOSurfaceRef.get());
|
||||
}
|
||||
|
||||
void MacIOSurface::Lock(bool aReadOnly) {
|
||||
bool MacIOSurface::Lock(bool aReadOnly) {
|
||||
MOZ_RELEASE_ASSERT(!mIsLocked, "double MacIOSurface lock");
|
||||
::IOSurfaceLock(mIOSurfaceRef.get(), aReadOnly ? kIOSurfaceLockReadOnly : 0,
|
||||
nullptr);
|
||||
kern_return_t rv = ::IOSurfaceLock(
|
||||
mIOSurfaceRef.get(), aReadOnly ? kIOSurfaceLockReadOnly : 0, nullptr);
|
||||
if (NS_WARN_IF(rv != KERN_SUCCESS)) {
|
||||
gfxCriticalNoteOnce << "MacIOSurface::Lock failed " << gfx::hexa(rv);
|
||||
return false;
|
||||
}
|
||||
mIsLocked = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacIOSurface::Unlock(bool aReadOnly) {
|
||||
@@ -420,14 +425,22 @@ static void MacIOSurfaceBufferDeallocator(void* aClosure) {
|
||||
}
|
||||
|
||||
already_AddRefed<SourceSurface> MacIOSurface::GetAsSurface() {
|
||||
Lock();
|
||||
if (NS_WARN_IF(!Lock())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t bytesPerRow = GetBytesPerRow();
|
||||
size_t ioWidth = GetDevicePixelWidth();
|
||||
size_t ioHeight = GetDevicePixelHeight();
|
||||
|
||||
unsigned char* ioData = (unsigned char*)GetBaseAddress();
|
||||
auto* dataCpy =
|
||||
new unsigned char[bytesPerRow * ioHeight / sizeof(unsigned char)];
|
||||
auto* dataCpy = new (
|
||||
fallible) unsigned char[bytesPerRow * ioHeight / sizeof(unsigned char)];
|
||||
if (NS_WARN_IF(!dataCpy)) {
|
||||
Unlock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < ioHeight; i++) {
|
||||
memcpy(dataCpy + i * bytesPerRow, ioData + i * bytesPerRow, ioWidth * 4);
|
||||
}
|
||||
@@ -502,6 +515,68 @@ ColorDepth MacIOSurface::GetColorDepth() const {
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ Maybe<OSType> MacIOSurface::ChoosePixelFormat(
|
||||
ChromaSubsampling aChromaSubsampling, ColorRange aColorRange,
|
||||
ColorDepth aColorDepth) {
|
||||
switch (aChromaSubsampling) {
|
||||
case ChromaSubsampling::FULL:
|
||||
if (aColorDepth == ColorDepth::COLOR_10) {
|
||||
switch (aColorRange) {
|
||||
case ColorRange::LIMITED:
|
||||
return Some(kCVPixelFormatType_422YpCbCr10BiPlanarVideoRange);
|
||||
case ColorRange::FULL:
|
||||
return Some(kCVPixelFormatType_422YpCbCr10BiPlanarFullRange);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ChromaSubsampling::HALF_WIDTH:
|
||||
switch (aColorDepth) {
|
||||
case ColorDepth::COLOR_8:
|
||||
switch (aColorRange) {
|
||||
case ColorRange::LIMITED:
|
||||
return Some(kCVPixelFormatType_422YpCbCr8_yuvs);
|
||||
case ColorRange::FULL:
|
||||
return Some(kCVPixelFormatType_422YpCbCr8FullRange);
|
||||
}
|
||||
break;
|
||||
case ColorDepth::COLOR_10:
|
||||
switch (aColorRange) {
|
||||
case ColorRange::LIMITED:
|
||||
return Some(kCVPixelFormatType_422YpCbCr10BiPlanarVideoRange);
|
||||
case ColorRange::FULL:
|
||||
return Some(kCVPixelFormatType_422YpCbCr10BiPlanarFullRange);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ChromaSubsampling::HALF_WIDTH_AND_HEIGHT:
|
||||
switch (aColorDepth) {
|
||||
case ColorDepth::COLOR_8:
|
||||
switch (aColorRange) {
|
||||
case ColorRange::LIMITED:
|
||||
return Some(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange);
|
||||
case ColorRange::FULL:
|
||||
return Some(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange);
|
||||
}
|
||||
break;
|
||||
case ColorDepth::COLOR_10:
|
||||
case ColorDepth::COLOR_12:
|
||||
case ColorDepth::COLOR_16:
|
||||
switch (aColorRange) {
|
||||
case ColorRange::LIMITED:
|
||||
return Some(kCVPixelFormatType_420YpCbCr10BiPlanarVideoRange);
|
||||
case ColorRange::FULL:
|
||||
return Some(kCVPixelFormatType_420YpCbCr10BiPlanarFullRange);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
bool MacIOSurface::BindTexImage(mozilla::gl::GLContext* aGL, size_t aPlane,
|
||||
mozilla::gfx::SurfaceFormat* aOutReadFormat) {
|
||||
#ifdef XP_MACOSX
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# include <dlfcn.h>
|
||||
|
||||
# include "mozilla/gfx/Types.h"
|
||||
# include "mozilla/Maybe.h"
|
||||
# include "CFTypeRefPtr.h"
|
||||
|
||||
namespace mozilla {
|
||||
@@ -99,7 +100,7 @@ class MacIOSurface final
|
||||
size_t GetDevicePixelHeight(size_t plane = 0) const;
|
||||
size_t GetBytesPerRow(size_t plane = 0) const;
|
||||
size_t GetAllocSize() const;
|
||||
void Lock(bool aReadOnly = true);
|
||||
bool Lock(bool aReadOnly = true);
|
||||
void Unlock(bool aReadOnly = true);
|
||||
bool IsLocked() const { return mIsLocked; }
|
||||
void IncrementUseCount();
|
||||
@@ -149,6 +150,11 @@ class MacIOSurface final
|
||||
|
||||
static size_t GetMaxWidth();
|
||||
static size_t GetMaxHeight();
|
||||
static mozilla::Maybe<OSType> ChoosePixelFormat(
|
||||
mozilla::gfx::ChromaSubsampling aChromaSubsampling,
|
||||
mozilla::gfx::ColorRange aColorRange,
|
||||
mozilla::gfx::ColorDepth aColorDepth);
|
||||
|
||||
CFTypeRefPtr<IOSurfaceRef> GetIOSurfaceRef() { return mIOSurfaceRef; }
|
||||
|
||||
void SetColorSpace(mozilla::gfx::ColorSpace2) const;
|
||||
|
||||
@@ -158,7 +158,10 @@ static nsresult CopyFromLockedMacIOSurface(MacIOSurface* aSurface,
|
||||
|
||||
already_AddRefed<SourceSurface> CreateSourceSurfaceFromMacIOSurface(
|
||||
MacIOSurface* aSurface, gfx::DataSourceSurface* aDataSurface) {
|
||||
aSurface->Lock();
|
||||
if (NS_WARN_IF(!aSurface->Lock())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto scopeExit = MakeScopeExit([&]() { aSurface->Unlock(); });
|
||||
|
||||
size_t ioWidth = aSurface->GetDevicePixelWidth();
|
||||
@@ -206,7 +209,10 @@ nsresult CreateSurfaceDescriptorBufferFromMacIOSurface(
|
||||
MacIOSurface* aSurface, SurfaceDescriptorBuffer& aSdBuffer,
|
||||
Image::BuildSdbFlags aFlags,
|
||||
const std::function<MemoryOrShmem(uint32_t)>& aAllocate) {
|
||||
aSurface->Lock();
|
||||
if (NS_WARN_IF(!aSurface->Lock())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
auto scopeExit = MakeScopeExit([&]() { aSurface->Unlock(); });
|
||||
|
||||
size_t ioWidth = aSurface->GetDevicePixelWidth();
|
||||
|
||||
@@ -91,7 +91,9 @@ bool MacIOSurfaceImage::SetData(ImageContainer* aContainer,
|
||||
ySize, cbcrSize, aData.mChromaSubsampling, aData.mYUVColorSpace,
|
||||
aData.mTransferFunction, aData.mColorRange, aData.mColorDepth);
|
||||
|
||||
surf->Lock(false);
|
||||
if (NS_WARN_IF(!surf) || NS_WARN_IF(!surf->Lock(false))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (surf->GetFormat() == SurfaceFormat::YUY2) {
|
||||
// If the CbCrSize's height is half of the YSize's height, then we'll
|
||||
@@ -264,26 +266,52 @@ already_AddRefed<MacIOSurface> MacIOSurfaceRecycleAllocator::Allocate(
|
||||
gfx::ChromaSubsampling aChromaSubsampling,
|
||||
gfx::YUVColorSpace aYUVColorSpace, gfx::TransferFunction aTransferFunction,
|
||||
gfx::ColorRange aColorRange, gfx::ColorDepth aColorDepth) {
|
||||
nsTArray<CFTypeRefPtr<IOSurfaceRef>> surfaces = std::move(mSurfaces);
|
||||
RefPtr<MacIOSurface> result;
|
||||
for (auto& surf : surfaces) {
|
||||
// If the surface size has changed, then discard any surfaces of the old
|
||||
// size.
|
||||
if (::IOSurfaceGetWidthOfPlane(surf.get(), 0) != (size_t)aYSize.width ||
|
||||
::IOSurfaceGetHeightOfPlane(surf.get(), 0) != (size_t)aYSize.height) {
|
||||
// To avoid checking every property of every surface, we just cache the
|
||||
// parameters used during the last allocation. If any of these have changed,
|
||||
// dump the cached surfaces and update our cached parameters.
|
||||
if (mYSize != aYSize || mCbCrSize != aCbCrSize ||
|
||||
mChromaSubsampling != aChromaSubsampling ||
|
||||
mYUVColorSpace != aYUVColorSpace ||
|
||||
mTransferFunction != aTransferFunction || mColorRange != aColorRange ||
|
||||
mColorDepth != aColorDepth) {
|
||||
mSurfaces.Clear();
|
||||
mYSize = aYSize;
|
||||
mCbCrSize = aCbCrSize;
|
||||
mChromaSubsampling = aChromaSubsampling;
|
||||
mYUVColorSpace = aYUVColorSpace;
|
||||
mTransferFunction = aTransferFunction;
|
||||
mColorRange = aColorRange;
|
||||
mColorDepth = aColorDepth;
|
||||
}
|
||||
|
||||
// Scan for an unused surface, and reuse that if one is available.
|
||||
for (auto& surf : mSurfaces) {
|
||||
if (::IOSurfaceIsInUse(surf.get())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only construct a MacIOSurface object when we find one that isn't
|
||||
// in-use, since the constructor adds a usage ref.
|
||||
if (!result && !::IOSurfaceIsInUse(surf.get())) {
|
||||
result = new MacIOSurface(surf, false, aYUVColorSpace);
|
||||
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
||||
Maybe<OSType> pixelFormat = MacIOSurface::ChoosePixelFormat(
|
||||
aChromaSubsampling, aColorRange, aColorDepth);
|
||||
MOZ_DIAGNOSTIC_ASSERT(pixelFormat.isSome());
|
||||
MOZ_DIAGNOSTIC_ASSERT(::IOSurfaceGetPixelFormat(surf.get()) ==
|
||||
*pixelFormat);
|
||||
MOZ_DIAGNOSTIC_ASSERT(::IOSurfaceGetWidthOfPlane(surf.get(), 0) ==
|
||||
(size_t)aYSize.width);
|
||||
MOZ_DIAGNOSTIC_ASSERT(::IOSurfaceGetHeightOfPlane(surf.get(), 0) ==
|
||||
(size_t)aYSize.height);
|
||||
if (*pixelFormat != kCVPixelFormatType_422YpCbCr8_yuvs &&
|
||||
*pixelFormat != kCVPixelFormatType_422YpCbCr8FullRange) {
|
||||
MOZ_DIAGNOSTIC_ASSERT(::IOSurfaceGetWidthOfPlane(surf.get(), 1) ==
|
||||
(size_t)aCbCrSize.width);
|
||||
MOZ_DIAGNOSTIC_ASSERT(::IOSurfaceGetHeightOfPlane(surf.get(), 1) ==
|
||||
(size_t)aCbCrSize.height);
|
||||
}
|
||||
#endif
|
||||
|
||||
return MakeAndAddRef<MacIOSurface>(surf, false, aYUVColorSpace);
|
||||
}
|
||||
|
||||
mSurfaces.AppendElement(surf);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
// Time to decide if we are creating a single planar or bi-planar surface.
|
||||
// We limit ourselves to macOS's single planar and bi-planar formats for
|
||||
// simplicity reasons, possibly gaining some small memory or performance
|
||||
@@ -293,6 +321,7 @@ already_AddRefed<MacIOSurface> MacIOSurfaceRecycleAllocator::Allocate(
|
||||
// planar format.
|
||||
// 4:2:2 formats with 8 bit color are single planar, otherwise bi-planar.
|
||||
|
||||
RefPtr<MacIOSurface> result;
|
||||
if (aChromaSubsampling == gfx::ChromaSubsampling::HALF_WIDTH &&
|
||||
aColorDepth == gfx::ColorDepth::COLOR_8) {
|
||||
result = MacIOSurface::CreateSinglePlanarSurface(
|
||||
@@ -303,11 +332,10 @@ already_AddRefed<MacIOSurface> MacIOSurfaceRecycleAllocator::Allocate(
|
||||
aTransferFunction, aColorRange, aColorDepth);
|
||||
}
|
||||
|
||||
if (mSurfaces.Length() <
|
||||
StaticPrefs::layers_iosurfaceimage_recycle_limit()) {
|
||||
if (result &&
|
||||
mSurfaces.Length() < StaticPrefs::layers_iosurfaceimage_recycle_limit()) {
|
||||
mSurfaces.AppendElement(result->GetIOSurfaceRef());
|
||||
}
|
||||
}
|
||||
|
||||
return result.forget();
|
||||
}
|
||||
|
||||
@@ -71,6 +71,15 @@ class MacIOSurfaceRecycleAllocator {
|
||||
~MacIOSurfaceRecycleAllocator() = default;
|
||||
|
||||
nsTArray<CFTypeRefPtr<IOSurfaceRef>> mSurfaces;
|
||||
|
||||
// Cached parameters used for allocations stored in mSurfaces.
|
||||
gfx::IntSize mYSize;
|
||||
gfx::IntSize mCbCrSize;
|
||||
gfx::ChromaSubsampling mChromaSubsampling = gfx::ChromaSubsampling::FULL;
|
||||
gfx::YUVColorSpace mYUVColorSpace = gfx::YUVColorSpace::BT709;
|
||||
gfx::TransferFunction mTransferFunction = gfx::TransferFunction::BT709;
|
||||
gfx::ColorRange mColorRange = gfx::ColorRange::FULL;
|
||||
gfx::ColorDepth mColorDepth = gfx::ColorDepth::COLOR_8;
|
||||
};
|
||||
|
||||
} // namespace layers
|
||||
|
||||
@@ -1198,7 +1198,7 @@ void NativeLayerCA::DumpLayer(std::ostream& aOutputStream) {
|
||||
// Attempt to render the surface as a PNG. Skia can do this for RGB
|
||||
// surfaces.
|
||||
RefPtr<MacIOSurface> surf = new MacIOSurface(surface);
|
||||
surf->Lock(true);
|
||||
if (surf->Lock(true)) {
|
||||
SurfaceFormat format = surf->GetFormat();
|
||||
if (format == SurfaceFormat::B8G8R8A8 ||
|
||||
format == SurfaceFormat::B8G8R8X8) {
|
||||
@@ -1215,6 +1215,7 @@ void NativeLayerCA::DumpLayer(std::ostream& aOutputStream) {
|
||||
}
|
||||
surf->Unlock(true);
|
||||
}
|
||||
}
|
||||
|
||||
aOutputStream << "\"/></div>\n";
|
||||
}
|
||||
@@ -1326,8 +1327,13 @@ RefPtr<gfx::DrawTarget> NativeLayerCA::NextSurfaceAsDrawTarget(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mInProgressLockedIOSurface = new MacIOSurface(mInProgressSurface->mSurface);
|
||||
mInProgressLockedIOSurface->Lock(false);
|
||||
auto surf = MakeRefPtr<MacIOSurface>(mInProgressSurface->mSurface);
|
||||
if (NS_WARN_IF(!surf->Lock(false))) {
|
||||
gfxCriticalError() << "NextSurfaceAsDrawTarget lock surface failed.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mInProgressLockedIOSurface = std::move(surf);
|
||||
RefPtr<gfx::DrawTarget> dt =
|
||||
mInProgressLockedIOSurface->GetAsDrawTargetLocked(aBackendType);
|
||||
|
||||
@@ -1336,8 +1342,7 @@ RefPtr<gfx::DrawTarget> NativeLayerCA::NextSurfaceAsDrawTarget(
|
||||
[&](CFTypeRefPtr<IOSurfaceRef> validSource,
|
||||
const gfx::IntRegion& copyRegion) {
|
||||
RefPtr<MacIOSurface> source = new MacIOSurface(validSource);
|
||||
source->Lock(true);
|
||||
{
|
||||
if (source->Lock(true)) {
|
||||
RefPtr<gfx::DrawTarget> sourceDT =
|
||||
source->GetAsDrawTargetLocked(aBackendType);
|
||||
RefPtr<gfx::SourceSurface> sourceSurface = sourceDT->Snapshot();
|
||||
@@ -1346,8 +1351,10 @@ RefPtr<gfx::DrawTarget> NativeLayerCA::NextSurfaceAsDrawTarget(
|
||||
const gfx::IntRect& r = iter.Get();
|
||||
dt->CopySurface(sourceSurface, r, r.TopLeft());
|
||||
}
|
||||
}
|
||||
source->Unlock(true);
|
||||
} else {
|
||||
gfxCriticalError() << "HandlePartialUpdate lock surface failed.";
|
||||
}
|
||||
});
|
||||
|
||||
return dt;
|
||||
|
||||
@@ -73,10 +73,7 @@ void MacIOSurfaceTextureData::FillInfo(TextureData::Info& aInfo) const {
|
||||
aInfo.canExposeMappedData = false;
|
||||
}
|
||||
|
||||
bool MacIOSurfaceTextureData::Lock(OpenMode) {
|
||||
mSurface->Lock(false);
|
||||
return true;
|
||||
}
|
||||
bool MacIOSurfaceTextureData::Lock(OpenMode) { return mSurface->Lock(false); }
|
||||
|
||||
void MacIOSurfaceTextureData::Unlock() { mSurface->Unlock(false); }
|
||||
|
||||
|
||||
@@ -147,7 +147,9 @@ bool RenderMacIOSurfaceTextureHost::MapPlane(RenderCompositor* aCompositor,
|
||||
uint8_t aChannelIndex,
|
||||
PlaneInfo& aPlaneInfo) {
|
||||
if (!aChannelIndex) {
|
||||
mSurface->Lock();
|
||||
if (NS_WARN_IF(!mSurface->Lock())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
aPlaneInfo.mData = mSurface->GetBaseAddressOfPlane(aChannelIndex);
|
||||
aPlaneInfo.mStride = mSurface->GetBytesPerRow(aChannelIndex);
|
||||
|
||||
Reference in New Issue
Block a user