diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/layers/ImageContainer.cpp | 869 |
1 files changed, 869 insertions, 0 deletions
diff --git a/gfx/layers/ImageContainer.cpp b/gfx/layers/ImageContainer.cpp new file mode 100644 index 0000000000..bd4ad42f94 --- /dev/null +++ b/gfx/layers/ImageContainer.cpp @@ -0,0 +1,869 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* 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 "ImageContainer.h" + +#include <string.h> // for memcpy, memset + +#include "GeckoProfiler.h" +#include "GLImages.h" // for SurfaceTextureImage +#include "YCbCrUtils.h" // for YCbCr conversions +#include "gfx2DGlue.h" +#include "gfxPlatform.h" // for gfxPlatform +#include "gfxUtils.h" // for gfxUtils +#include "libyuv.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/RefPtr.h" // for already_AddRefed +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/CrossProcessMutex.h" // for CrossProcessMutex, etc +#include "mozilla/layers/CompositorTypes.h" +#include "mozilla/layers/ImageBridgeChild.h" // for ImageBridgeChild +#include "mozilla/layers/ImageClient.h" // for ImageClient +#include "mozilla/layers/ImageDataSerializer.h" // for SurfaceDescriptorBuffer +#include "mozilla/layers/LayersMessages.h" +#include "mozilla/layers/SharedPlanarYCbCrImage.h" +#include "mozilla/layers/SharedRGBImage.h" +#include "mozilla/layers/SharedSurfacesChild.h" // for SharedSurfacesAnimation +#include "mozilla/layers/TextureClientRecycleAllocator.h" +#include "nsProxyRelease.h" +#include "nsISupportsUtils.h" // for NS_IF_ADDREF + +#ifdef XP_MACOSX +# include "MacIOSurfaceImage.h" +# include "mozilla/gfx/QuartzSupport.h" +#endif + +#ifdef XP_WIN +# include <d3d10_1.h> + +# include "gfxWindowsPlatform.h" +# include "mozilla/gfx/DeviceManagerDx.h" +# include "mozilla/layers/D3D11YCbCrImage.h" +#endif + +namespace mozilla::layers { + +using namespace mozilla::gfx; +using namespace mozilla::ipc; + +Atomic<int32_t> Image::sSerialCounter(0); + +Atomic<uint32_t> ImageContainer::sGenerationCounter(0); + +static void CopyPlane(uint8_t* aDst, const uint8_t* aSrc, + const gfx::IntSize& aSize, int32_t aStride, + int32_t aSkip); + +RefPtr<PlanarYCbCrImage> ImageFactory::CreatePlanarYCbCrImage( + const gfx::IntSize& aScaleHint, BufferRecycleBin* aRecycleBin) { + return new RecyclingPlanarYCbCrImage(aRecycleBin); +} + +BufferRecycleBin::BufferRecycleBin() + : mLock("mozilla.layers.BufferRecycleBin.mLock") + // This member is only valid when the bin is not empty and will be + // properly initialized in RecycleBuffer, but initializing it here avoids + // static analysis noise. + , + mRecycledBufferSize(0) {} + +void BufferRecycleBin::RecycleBuffer(UniquePtr<uint8_t[]> aBuffer, + uint32_t aSize) { + MutexAutoLock lock(mLock); + + if (!mRecycledBuffers.IsEmpty() && aSize != mRecycledBufferSize) { + mRecycledBuffers.Clear(); + } + mRecycledBufferSize = aSize; + mRecycledBuffers.AppendElement(std::move(aBuffer)); +} + +UniquePtr<uint8_t[]> BufferRecycleBin::GetBuffer(uint32_t aSize) { + MutexAutoLock lock(mLock); + + if (mRecycledBuffers.IsEmpty() || mRecycledBufferSize != aSize) { + return UniquePtr<uint8_t[]>(new (fallible) uint8_t[aSize]); + } + + return mRecycledBuffers.PopLastElement(); +} + +void BufferRecycleBin::ClearRecycledBuffers() { + MutexAutoLock lock(mLock); + if (!mRecycledBuffers.IsEmpty()) { + mRecycledBuffers.Clear(); + } + mRecycledBufferSize = 0; +} + +ImageContainerListener::ImageContainerListener(ImageContainer* aImageContainer) + : mLock("mozilla.layers.ImageContainerListener.mLock"), + mImageContainer(aImageContainer) {} + +ImageContainerListener::~ImageContainerListener() = default; + +void ImageContainerListener::NotifyComposite( + const ImageCompositeNotification& aNotification) { + MutexAutoLock lock(mLock); + if (mImageContainer) { + mImageContainer->NotifyComposite(aNotification); + } +} + +void ImageContainerListener::NotifyDropped(uint32_t aDropped) { + MutexAutoLock lock(mLock); + if (mImageContainer) { + mImageContainer->NotifyDropped(aDropped); + } +} + +void ImageContainerListener::ClearImageContainer() { + MutexAutoLock lock(mLock); + mImageContainer = nullptr; +} + +void ImageContainerListener::DropImageClient() { + MutexAutoLock lock(mLock); + if (mImageContainer) { + mImageContainer->DropImageClient(); + } +} + +already_AddRefed<ImageClient> ImageContainer::GetImageClient() { + RecursiveMutexAutoLock mon(mRecursiveMutex); + EnsureImageClient(); + RefPtr<ImageClient> imageClient = mImageClient; + return imageClient.forget(); +} + +void ImageContainer::DropImageClient() { + RecursiveMutexAutoLock mon(mRecursiveMutex); + if (mImageClient) { + mImageClient->ClearCachedResources(); + mImageClient = nullptr; + } +} + +void ImageContainer::EnsureImageClient() { + // If we're not forcing a new ImageClient, then we can skip this if we don't + // have an existing ImageClient, or if the existing one belongs to an IPC + // actor that is still open. + if (!mIsAsync) { + return; + } + if (mImageClient && + mImageClient->GetForwarder()->GetLayersIPCActor()->IPCOpen()) { + return; + } + + RefPtr<ImageBridgeChild> imageBridge = ImageBridgeChild::GetSingleton(); + if (imageBridge) { + mImageClient = + imageBridge->CreateImageClient(CompositableType::IMAGE, this); + if (mImageClient) { + mAsyncContainerHandle = mImageClient->GetAsyncHandle(); + } else { + // It's okay to drop the async container handle since the ImageBridgeChild + // is going to die anyway. + mAsyncContainerHandle = CompositableHandle(); + } + } +} + +SharedSurfacesAnimation* ImageContainer::EnsureSharedSurfacesAnimation() { + if (!mSharedAnimation) { + mSharedAnimation = new SharedSurfacesAnimation(); + } + return mSharedAnimation; +} + +ImageContainer::ImageContainer(Mode flag) + : mRecursiveMutex("ImageContainer.mRecursiveMutex"), + mGenerationCounter(++sGenerationCounter), + mPaintCount(0), + mDroppedImageCount(0), + mImageFactory(new ImageFactory()), + mRecycleBin(new BufferRecycleBin()), + mIsAsync(flag == ASYNCHRONOUS), + mCurrentProducerID(-1) { + if (flag == ASYNCHRONOUS) { + mNotifyCompositeListener = new ImageContainerListener(this); + EnsureImageClient(); + } +} + +ImageContainer::ImageContainer(const CompositableHandle& aHandle) + : mRecursiveMutex("ImageContainer.mRecursiveMutex"), + mGenerationCounter(++sGenerationCounter), + mPaintCount(0), + mDroppedImageCount(0), + mImageFactory(nullptr), + mRecycleBin(nullptr), + mIsAsync(true), + mAsyncContainerHandle(aHandle), + mCurrentProducerID(-1) { + MOZ_ASSERT(mAsyncContainerHandle); +} + +ImageContainer::~ImageContainer() { + if (mNotifyCompositeListener) { + mNotifyCompositeListener->ClearImageContainer(); + } + if (mAsyncContainerHandle) { + if (RefPtr<ImageBridgeChild> imageBridge = + ImageBridgeChild::GetSingleton()) { + imageBridge->ForgetImageContainer(mAsyncContainerHandle); + } + } + if (mSharedAnimation) { + mSharedAnimation->Destroy(); + } +} + +Maybe<SurfaceDescriptor> Image::GetDesc() { return {}; } + +Maybe<SurfaceDescriptor> Image::GetDescFromTexClient( + TextureClient* const forTc) { + RefPtr<TextureClient> tc = forTc; + if (!forTc) { + tc = GetTextureClient(nullptr); + } + + const auto& tcd = tc->GetInternalData(); + + SurfaceDescriptor ret; + if (!tcd->Serialize(ret)) { + return {}; + } + return Some(ret); +} + +RefPtr<PlanarYCbCrImage> ImageContainer::CreatePlanarYCbCrImage() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EnsureImageClient(); + if (mImageClient && mImageClient->AsImageClientSingle()) { + return new SharedPlanarYCbCrImage(mImageClient); + } + if (mRecycleAllocator) { + return new SharedPlanarYCbCrImage(mRecycleAllocator); + } + return mImageFactory->CreatePlanarYCbCrImage(mScaleHint, mRecycleBin); +} + +RefPtr<SharedRGBImage> ImageContainer::CreateSharedRGBImage() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + EnsureImageClient(); + if (mImageClient && mImageClient->AsImageClientSingle()) { + return new SharedRGBImage(mImageClient); + } + if (mRecycleAllocator) { + return new SharedRGBImage(mRecycleAllocator); + } + return nullptr; +} + +void ImageContainer::SetCurrentImageInternal( + const nsTArray<NonOwningImage>& aImages) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + mGenerationCounter = ++sGenerationCounter; + + if (!aImages.IsEmpty()) { + NS_ASSERTION(mCurrentImages.IsEmpty() || + mCurrentImages[0].mProducerID != aImages[0].mProducerID || + mCurrentImages[0].mFrameID <= aImages[0].mFrameID, + "frame IDs shouldn't go backwards"); + if (aImages[0].mProducerID != mCurrentProducerID) { + mCurrentProducerID = aImages[0].mProducerID; + } + } + + nsTArray<OwningImage> newImages; + + for (uint32_t i = 0; i < aImages.Length(); ++i) { + NS_ASSERTION(aImages[i].mImage, "image can't be null"); + NS_ASSERTION(!aImages[i].mTimeStamp.IsNull() || aImages.Length() == 1, + "Multiple images require timestamps"); + if (i > 0) { + NS_ASSERTION(aImages[i].mTimeStamp >= aImages[i - 1].mTimeStamp, + "Timestamps must not decrease"); + NS_ASSERTION(aImages[i].mFrameID > aImages[i - 1].mFrameID, + "FrameIDs must increase"); + NS_ASSERTION(aImages[i].mProducerID == aImages[i - 1].mProducerID, + "ProducerIDs must be the same"); + } + OwningImage* img = newImages.AppendElement(); + img->mImage = aImages[i].mImage; + img->mTimeStamp = aImages[i].mTimeStamp; + img->mFrameID = aImages[i].mFrameID; + img->mProducerID = aImages[i].mProducerID; + for (const auto& oldImg : mCurrentImages) { + if (oldImg.mFrameID == img->mFrameID && + oldImg.mProducerID == img->mProducerID) { + img->mComposited = oldImg.mComposited; + break; + } + } + } + + mCurrentImages = std::move(newImages); +} + +void ImageContainer::ClearImagesFromImageBridge() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + SetCurrentImageInternal(nsTArray<NonOwningImage>()); +} + +void ImageContainer::SetCurrentImages(const nsTArray<NonOwningImage>& aImages) { + AUTO_PROFILER_LABEL("ImageContainer::SetCurrentImages", GRAPHICS); + MOZ_ASSERT(!aImages.IsEmpty()); + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (mIsAsync) { + if (RefPtr<ImageBridgeChild> imageBridge = + ImageBridgeChild::GetSingleton()) { + imageBridge->UpdateImageClient(this); + } + } + SetCurrentImageInternal(aImages); +} + +void ImageContainer::ClearAllImages() { + if (mImageClient) { + // Let ImageClient release all TextureClients. This doesn't return + // until ImageBridge has called ClearCurrentImageFromImageBridge. + if (RefPtr<ImageBridgeChild> imageBridge = + ImageBridgeChild::GetSingleton()) { + imageBridge->FlushAllImages(mImageClient, this); + } + return; + } + + RecursiveMutexAutoLock lock(mRecursiveMutex); + SetCurrentImageInternal(nsTArray<NonOwningImage>()); +} + +void ImageContainer::ClearCachedResources() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + if (mImageClient && mImageClient->AsImageClientSingle()) { + if (!mImageClient->HasTextureClientRecycler()) { + return; + } + mImageClient->GetTextureClientRecycler()->ShrinkToMinimumSize(); + return; + } + return mRecycleBin->ClearRecycledBuffers(); +} + +void ImageContainer::SetCurrentImageInTransaction(Image* aImage) { + AutoTArray<NonOwningImage, 1> images; + images.AppendElement(NonOwningImage(aImage)); + SetCurrentImagesInTransaction(images); +} + +void ImageContainer::SetCurrentImagesInTransaction( + const nsTArray<NonOwningImage>& aImages) { + NS_ASSERTION(NS_IsMainThread(), "Should be on main thread."); + NS_ASSERTION(!mImageClient, + "Should use async image transfer with ImageBridge."); + + SetCurrentImageInternal(aImages); +} + +bool ImageContainer::IsAsync() const { return mIsAsync; } + +CompositableHandle ImageContainer::GetAsyncContainerHandle() { + NS_ASSERTION(IsAsync(), + "Shared image ID is only relevant to async ImageContainers"); + NS_ASSERTION(mAsyncContainerHandle, "Should have a shared image ID"); + RecursiveMutexAutoLock mon(mRecursiveMutex); + EnsureImageClient(); + return mAsyncContainerHandle; +} + +bool ImageContainer::HasCurrentImage() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + return !mCurrentImages.IsEmpty(); +} + +void ImageContainer::GetCurrentImages(nsTArray<OwningImage>* aImages, + uint32_t* aGenerationCounter) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + *aImages = mCurrentImages.Clone(); + if (aGenerationCounter) { + *aGenerationCounter = mGenerationCounter; + } +} + +gfx::IntSize ImageContainer::GetCurrentSize() { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + if (mCurrentImages.IsEmpty()) { + return gfx::IntSize(0, 0); + } + + return mCurrentImages[0].mImage->GetSize(); +} + +void ImageContainer::NotifyComposite( + const ImageCompositeNotification& aNotification) { + RecursiveMutexAutoLock lock(mRecursiveMutex); + + // An image composition notification is sent the first time a particular + // image is composited by an ImageHost. Thus, every time we receive such + // a notification, a new image has been painted. + ++mPaintCount; + + if (aNotification.producerID() == mCurrentProducerID) { + for (auto& img : mCurrentImages) { + if (img.mFrameID == aNotification.frameID()) { + img.mComposited = true; + } + } + } + + if (!aNotification.imageTimeStamp().IsNull()) { + mPaintDelay = aNotification.firstCompositeTimeStamp() - + aNotification.imageTimeStamp(); + } +} + +void ImageContainer::NotifyDropped(uint32_t aDropped) { + mDroppedImageCount += aDropped; +} + +void ImageContainer::EnsureRecycleAllocatorForRDD( + KnowsCompositor* aKnowsCompositor) { + MOZ_ASSERT(!mIsAsync); + MOZ_ASSERT(!mImageClient); + MOZ_ASSERT(XRE_IsRDDProcess()); + + if (mRecycleAllocator && + aKnowsCompositor == mRecycleAllocator->GetKnowsCompositor()) { + return; + } + + if (!StaticPrefs::layers_recycle_allocator_rdd_AtStartup()) { + return; + } + + static const uint32_t MAX_POOLED_VIDEO_COUNT = 5; + + mRecycleAllocator = + new layers::TextureClientRecycleAllocator(aKnowsCompositor); + mRecycleAllocator->SetMaxPoolSize(MAX_POOLED_VIDEO_COUNT); +} + +#ifdef XP_WIN +D3D11YCbCrRecycleAllocator* ImageContainer::GetD3D11YCbCrRecycleAllocator( + KnowsCompositor* aKnowsCompositor) { + if (mD3D11YCbCrRecycleAllocator && + aKnowsCompositor == mD3D11YCbCrRecycleAllocator->GetKnowsCompositor()) { + return mD3D11YCbCrRecycleAllocator; + } + + if (!aKnowsCompositor->SupportsD3D11() || + !gfx::DeviceManagerDx::Get()->GetImageDevice()) { + return nullptr; + } + + mD3D11YCbCrRecycleAllocator = + new D3D11YCbCrRecycleAllocator(aKnowsCompositor); + return mD3D11YCbCrRecycleAllocator; +} +#endif + +#ifdef XP_MACOSX +MacIOSurfaceRecycleAllocator* +ImageContainer::GetMacIOSurfaceRecycleAllocator() { + if (!mMacIOSurfaceRecycleAllocator) { + mMacIOSurfaceRecycleAllocator = new MacIOSurfaceRecycleAllocator(); + } + + return mMacIOSurfaceRecycleAllocator; +} +#endif + +PlanarYCbCrImage::PlanarYCbCrImage() + : Image(nullptr, ImageFormat::PLANAR_YCBCR), + mOffscreenFormat(SurfaceFormat::UNKNOWN), + mBufferSize(0) {} + +nsresult PlanarYCbCrImage::BuildSurfaceDescriptorBuffer( + SurfaceDescriptorBuffer& aSdBuffer) { + const PlanarYCbCrData* pdata = GetData(); + MOZ_ASSERT(pdata, "must have PlanarYCbCrData"); + MOZ_ASSERT(pdata->mYSkip == 0 && pdata->mCbSkip == 0 && pdata->mCrSkip == 0, + "YCbCrDescriptor doesn't hold skip values"); + + uint32_t yOffset; + uint32_t cbOffset; + uint32_t crOffset; + ImageDataSerializer::ComputeYCbCrOffsets( + pdata->mYStride, pdata->mYSize.height, pdata->mCbCrStride, + pdata->mCbCrSize.height, yOffset, cbOffset, crOffset); + + aSdBuffer.desc() = YCbCrDescriptor( + pdata->GetPictureRect(), pdata->mYSize, pdata->mYStride, pdata->mCbCrSize, + pdata->mCbCrStride, yOffset, cbOffset, crOffset, pdata->mStereoMode, + pdata->mColorDepth, pdata->mYUVColorSpace, pdata->mColorRange, + /*hasIntermediateBuffer*/ false); + + uint8_t* buffer = nullptr; + const MemoryOrShmem& memOrShmem = aSdBuffer.data(); + switch (memOrShmem.type()) { + case MemoryOrShmem::Tuintptr_t: + buffer = reinterpret_cast<uint8_t*>(memOrShmem.get_uintptr_t()); + break; + case MemoryOrShmem::TShmem: + buffer = memOrShmem.get_Shmem().get<uint8_t>(); + break; + default: + MOZ_ASSERT(false, "Unknown MemoryOrShmem type"); + } + MOZ_ASSERT(buffer, "no valid buffer available to copy image data"); + if (!buffer) { + return NS_ERROR_INVALID_ARG; + } + + CopyPlane(buffer + yOffset, pdata->mYChannel, pdata->mYSize, pdata->mYStride, + pdata->mYSkip); + CopyPlane(buffer + cbOffset, pdata->mCbChannel, pdata->mCbCrSize, + pdata->mCbCrStride, pdata->mCbSkip); + CopyPlane(buffer + crOffset, pdata->mCrChannel, pdata->mCbCrSize, + pdata->mCbCrStride, pdata->mCrSkip); + return NS_OK; +} + +RecyclingPlanarYCbCrImage::~RecyclingPlanarYCbCrImage() { + if (mBuffer) { + mRecycleBin->RecycleBuffer(std::move(mBuffer), mBufferSize); + } +} + +size_t RecyclingPlanarYCbCrImage::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + // Ignoring: + // - mData - just wraps mBuffer + // - Surfaces should be reported under gfx-surfaces-*: + // - mSourceSurface + // - Base class: + // - mImplData is not used + // Not owned: + // - mRecycleBin + size_t size = aMallocSizeOf(mBuffer.get()); + + // Could add in the future: + // - mBackendData (from base class) + + return size; +} + +UniquePtr<uint8_t[]> RecyclingPlanarYCbCrImage::AllocateBuffer(uint32_t aSize) { + return mRecycleBin->GetBuffer(aSize); +} + +static void CopyPlane(uint8_t* aDst, const uint8_t* aSrc, + const gfx::IntSize& aSize, int32_t aStride, + int32_t aSkip) { + int32_t height = aSize.height; + int32_t width = aSize.width; + + MOZ_RELEASE_ASSERT(width <= aStride); + + if (!aSkip) { + // Fast path: planar input. + memcpy(aDst, aSrc, height * aStride); + } else { + for (int y = 0; y < height; ++y) { + const uint8_t* src = aSrc; + uint8_t* dst = aDst; + // Slow path + for (int x = 0; x < width; ++x) { + *dst++ = *src++; + src += aSkip; + } + aSrc += aStride; + aDst += aStride; + } + } +} + +bool RecyclingPlanarYCbCrImage::CopyData(const Data& aData) { + // update buffer size + // Use uint32_t throughout to match AllocateBuffer's param and mBufferSize + const auto checkedSize = + CheckedInt<uint32_t>(aData.mCbCrStride) * aData.mCbCrSize.height * 2 + + CheckedInt<uint32_t>(aData.mYStride) * aData.mYSize.height; + + if (!checkedSize.isValid()) return false; + + const auto size = checkedSize.value(); + + // get new buffer + mBuffer = AllocateBuffer(size); + if (!mBuffer) return false; + + // update buffer size + mBufferSize = size; + + mData = aData; + mData.mYChannel = mBuffer.get(); + mData.mCbChannel = mData.mYChannel + mData.mYStride * mData.mYSize.height; + mData.mCrChannel = + mData.mCbChannel + mData.mCbCrStride * mData.mCbCrSize.height; + mData.mYSkip = mData.mCbSkip = mData.mCrSkip = 0; + + CopyPlane(mData.mYChannel, aData.mYChannel, aData.mYSize, aData.mYStride, + aData.mYSkip); + CopyPlane(mData.mCbChannel, aData.mCbChannel, aData.mCbCrSize, + aData.mCbCrStride, aData.mCbSkip); + CopyPlane(mData.mCrChannel, aData.mCrChannel, aData.mCbCrSize, + aData.mCbCrStride, aData.mCrSkip); + + mSize = aData.mPicSize; + mOrigin = gfx::IntPoint(aData.mPicX, aData.mPicY); + return true; +} + +gfxImageFormat PlanarYCbCrImage::GetOffscreenFormat() const { + return mOffscreenFormat == SurfaceFormat::UNKNOWN ? gfxVars::OffscreenFormat() + : mOffscreenFormat; +} + +bool PlanarYCbCrImage::AdoptData(const Data& aData) { + mData = aData; + mSize = aData.mPicSize; + mOrigin = gfx::IntPoint(aData.mPicX, aData.mPicY); + return true; +} + +already_AddRefed<gfx::SourceSurface> PlanarYCbCrImage::GetAsSourceSurface() { + if (mSourceSurface) { + RefPtr<gfx::SourceSurface> surface(mSourceSurface); + return surface.forget(); + } + + gfx::IntSize size(mSize); + gfx::SurfaceFormat format = + gfx::ImageFormatToSurfaceFormat(GetOffscreenFormat()); + gfx::GetYCbCrToRGBDestFormatAndSize(mData, format, size); + if (mSize.width > PlanarYCbCrImage::MAX_DIMENSION || + mSize.height > PlanarYCbCrImage::MAX_DIMENSION) { + NS_ERROR("Illegal image dest width or height"); + return nullptr; + } + + RefPtr<gfx::DataSourceSurface> surface = + gfx::Factory::CreateDataSourceSurface(size, format); + if (NS_WARN_IF(!surface)) { + return nullptr; + } + + DataSourceSurface::ScopedMap mapping(surface, DataSourceSurface::WRITE); + if (NS_WARN_IF(!mapping.IsMapped())) { + return nullptr; + } + + gfx::ConvertYCbCrToRGB(mData, format, size, mapping.GetData(), + mapping.GetStride()); + + mSourceSurface = surface; + + return surface.forget(); +} + +PlanarYCbCrImage::~PlanarYCbCrImage() { + NS_ReleaseOnMainThread("PlanarYCbCrImage::mSourceSurface", + mSourceSurface.forget()); +} + +NVImage::NVImage() : Image(nullptr, ImageFormat::NV_IMAGE), mBufferSize(0) {} + +NVImage::~NVImage() { + NS_ReleaseOnMainThread("NVImage::mSourceSurface", mSourceSurface.forget()); +} + +IntSize NVImage::GetSize() const { return mSize; } + +IntRect NVImage::GetPictureRect() const { return mData.GetPictureRect(); } + +already_AddRefed<SourceSurface> NVImage::GetAsSourceSurface() { + if (mSourceSurface) { + RefPtr<gfx::SourceSurface> surface(mSourceSurface); + return surface.forget(); + } + + // Convert the current NV12 or NV21 data to YUV420P so that we can follow the + // logics in PlanarYCbCrImage::GetAsSourceSurface(). + const int bufferLength = mData.mYSize.height * mData.mYStride + + mData.mCbCrSize.height * mData.mCbCrSize.width * 2; + UniquePtr<uint8_t[]> buffer(new uint8_t[bufferLength]); + + Data aData = mData; + aData.mCbCrStride = aData.mCbCrSize.width; + aData.mCbSkip = 0; + aData.mCrSkip = 0; + aData.mYChannel = buffer.get(); + aData.mCbChannel = aData.mYChannel + aData.mYSize.height * aData.mYStride; + aData.mCrChannel = + aData.mCbChannel + aData.mCbCrSize.height * aData.mCbCrStride; + + if (mData.mCbChannel < mData.mCrChannel) { // NV12 + libyuv::NV12ToI420(mData.mYChannel, mData.mYStride, mData.mCbChannel, + mData.mCbCrStride, aData.mYChannel, aData.mYStride, + aData.mCbChannel, aData.mCbCrStride, aData.mCrChannel, + aData.mCbCrStride, aData.mYSize.width, + aData.mYSize.height); + } else { // NV21 + libyuv::NV21ToI420(mData.mYChannel, mData.mYStride, mData.mCrChannel, + mData.mCbCrStride, aData.mYChannel, aData.mYStride, + aData.mCbChannel, aData.mCbCrStride, aData.mCrChannel, + aData.mCbCrStride, aData.mYSize.width, + aData.mYSize.height); + } + + // The logics in PlanarYCbCrImage::GetAsSourceSurface(). + gfx::IntSize size(mSize); + gfx::SurfaceFormat format = gfx::ImageFormatToSurfaceFormat( + gfxPlatform::GetPlatform()->GetOffscreenFormat()); + gfx::GetYCbCrToRGBDestFormatAndSize(aData, format, size); + if (mSize.width > PlanarYCbCrImage::MAX_DIMENSION || + mSize.height > PlanarYCbCrImage::MAX_DIMENSION) { + NS_ERROR("Illegal image dest width or height"); + return nullptr; + } + + RefPtr<gfx::DataSourceSurface> surface = + gfx::Factory::CreateDataSourceSurface(size, format); + if (NS_WARN_IF(!surface)) { + return nullptr; + } + + DataSourceSurface::ScopedMap mapping(surface, DataSourceSurface::WRITE); + if (NS_WARN_IF(!mapping.IsMapped())) { + return nullptr; + } + + gfx::ConvertYCbCrToRGB(aData, format, size, mapping.GetData(), + mapping.GetStride()); + + mSourceSurface = surface; + + return surface.forget(); +} + +bool NVImage::IsValid() const { return !!mBufferSize; } + +uint32_t NVImage::GetBufferSize() const { return mBufferSize; } + +NVImage* NVImage::AsNVImage() { return this; }; + +bool NVImage::SetData(const Data& aData) { + MOZ_ASSERT(aData.mCbSkip == 1 && aData.mCrSkip == 1); + MOZ_ASSERT((int)std::abs(aData.mCbChannel - aData.mCrChannel) == 1); + + // Calculate buffer size + // Use uint32_t throughout to match AllocateBuffer's param and mBufferSize + const auto checkedSize = + CheckedInt<uint32_t>(aData.mYSize.height) * aData.mYStride + + CheckedInt<uint32_t>(aData.mCbCrSize.height) * aData.mCbCrStride; + + if (!checkedSize.isValid()) return false; + + const auto size = checkedSize.value(); + + // Allocate a new buffer. + mBuffer = AllocateBuffer(size); + if (!mBuffer) { + return false; + } + + // Update mBufferSize. + mBufferSize = size; + + // Update mData. + mData = aData; + mData.mYChannel = mBuffer.get(); + mData.mCbChannel = mData.mYChannel + (aData.mCbChannel - aData.mYChannel); + mData.mCrChannel = mData.mYChannel + (aData.mCrChannel - aData.mYChannel); + + // Update mSize. + mSize = aData.mPicSize; + + // Copy the input data into mBuffer. + // This copies the y-channel and the interleaving CbCr-channel. + memcpy(mData.mYChannel, aData.mYChannel, mBufferSize); + + return true; +} + +const NVImage::Data* NVImage::GetData() const { return &mData; } + +UniquePtr<uint8_t[]> NVImage::AllocateBuffer(uint32_t aSize) { + UniquePtr<uint8_t[]> buffer(new uint8_t[aSize]); + return buffer; +} + +SourceSurfaceImage::SourceSurfaceImage(const gfx::IntSize& aSize, + gfx::SourceSurface* aSourceSurface) + : Image(nullptr, ImageFormat::CAIRO_SURFACE), + mSize(aSize), + mSourceSurface(aSourceSurface), + mTextureFlags(TextureFlags::DEFAULT) {} + +SourceSurfaceImage::SourceSurfaceImage(gfx::SourceSurface* aSourceSurface) + : Image(nullptr, ImageFormat::CAIRO_SURFACE), + mSize(aSourceSurface->GetSize()), + mSourceSurface(aSourceSurface), + mTextureFlags(TextureFlags::DEFAULT) {} + +SourceSurfaceImage::~SourceSurfaceImage() { + NS_ReleaseOnMainThread("SourceSurfaceImage::mSourceSurface", + mSourceSurface.forget()); +} + +TextureClient* SourceSurfaceImage::GetTextureClient( + KnowsCompositor* aKnowsCompositor) { + if (!aKnowsCompositor) { + return nullptr; + } + + auto entry = mTextureClients.LookupForAdd(aKnowsCompositor->GetSerial()); + if (entry) { + return entry.Data(); + } + + RefPtr<TextureClient> textureClient; + RefPtr<SourceSurface> surface = GetAsSourceSurface(); + MOZ_ASSERT(surface); + if (surface) { + // gfx::BackendType::NONE means default to content backend + textureClient = TextureClient::CreateFromSurface( + aKnowsCompositor, surface, BackendSelector::Content, mTextureFlags, + ALLOC_DEFAULT); + } + if (textureClient) { + textureClient->SyncWithObject(aKnowsCompositor->GetSyncObject()); + entry.OrInsert([&textureClient]() { return textureClient; }); + return textureClient; + } + + // Remove the speculatively added entry. + entry.OrRemove(); + return nullptr; +} + +ImageContainer::ProducerID ImageContainer::AllocateProducerID() { + // Callable on all threads. + static Atomic<ImageContainer::ProducerID> sProducerID(0u); + return ++sProducerID; +} + +} // namespace mozilla::layers |