gecko-dev/gfx/layers/ImageContainer.cpp
Andrew Osmond 9fdad4e863 Bug 1433351 - Add nsBaseHashtable::EntryPtr::OrRemove method to abort nsBaseHashtable::LookupForAdd on miss. r=froydnj
In SourceSurfaceImage::GetTextureClient, we use LookupForAdd. This is
because we typically will create a new TextureClient if there isn't
already one created. This creation can fail because the size is too big,
or we don't have the memory available for it. Unfortunately LookupForAdd
is an infallible operation; it is expected we will always add something
to the hashtable if we don't find an entry. This patch adds an OrRemove
method to cover the corner case where we are unable to complete the
insertion.
2018-03-28 12:58:49 -04:00

882 lines
24 KiB
C++

/* -*- 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 "GLImages.h" // for SurfaceTextureImage
#include "gfx2DGlue.h"
#include "gfxPlatform.h" // for gfxPlatform
#include "gfxUtils.h" // for gfxUtils
#include "libyuv.h"
#include "mozilla/RefPtr.h" // for already_AddRefed
#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/LayersMessages.h"
#include "mozilla/layers/SharedPlanarYCbCrImage.h"
#include "mozilla/layers/SharedRGBImage.h"
#include "mozilla/layers/TextureClientRecycleAllocator.h"
#include "mozilla/gfx/gfxVars.h"
#include "nsISupportsUtils.h" // for NS_IF_ADDREF
#include "YCbCrUtils.h" // for YCbCr conversions
#include "gfx2DGlue.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/CheckedInt.h"
#ifdef XP_MACOSX
#include "mozilla/gfx/QuartzSupport.h"
#endif
#ifdef XP_WIN
#include "gfxWindowsPlatform.h"
#include <d3d10_1.h>
#include "mozilla/gfx/DeviceManagerDx.h"
#include "mozilla/layers/D3D11YCbCrImage.h"
#endif
namespace mozilla {
namespace layers {
using namespace mozilla::ipc;
using namespace android;
using namespace mozilla::gfx;
Atomic<int32_t> Image::sSerialCounter(0);
Atomic<uint32_t> ImageContainer::sGenerationCounter(0);
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(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]);
}
uint32_t last = mRecycledBuffers.Length() - 1;
UniquePtr<uint8_t[]> result = Move(mRecycledBuffers[last]);
mRecycledBuffers.RemoveElementAt(last);
return result;
}
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()
{
}
void
ImageContainerListener::NotifyComposite(const ImageCompositeNotification& aNotification)
{
MutexAutoLock lock(mLock);
if (mImageContainer) {
mImageContainer->NotifyComposite(aNotification);
}
}
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();
}
}
}
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);
}
}
}
RefPtr<PlanarYCbCrImage>
ImageContainer::CreatePlanarYCbCrImage()
{
RecursiveMutexAutoLock lock(mRecursiveMutex);
EnsureImageClient();
if (mImageClient && mImageClient->AsImageClientSingle()) {
return new SharedPlanarYCbCrImage(mImageClient);
}
return mImageFactory->CreatePlanarYCbCrImage(mScaleHint, mRecycleBin);
}
RefPtr<SharedRGBImage>
ImageContainer::CreateSharedRGBImage()
{
RecursiveMutexAutoLock lock(mRecursiveMutex);
EnsureImageClient();
if (!mImageClient || !mImageClient->AsImageClientSingle()) {
return nullptr;
}
return new SharedRGBImage(mImageClient);
}
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) {
mFrameIDsNotYetComposited.Clear();
mCurrentProducerID = aImages[0].mProducerID;
} else if (!aImages[0].mTimeStamp.IsNull()) {
// Check for expired frames
for (auto& img : mCurrentImages) {
if (img.mProducerID != aImages[0].mProducerID ||
img.mTimeStamp.IsNull() ||
img.mTimeStamp >= aImages[0].mTimeStamp) {
break;
}
if (!img.mComposited && !img.mTimeStamp.IsNull() &&
img.mFrameID != aImages[0].mFrameID) {
mFrameIDsNotYetComposited.AppendElement(img.mFrameID);
}
}
// Remove really old frames, assuming they'll never be composited.
const uint32_t maxFrames = 100;
if (mFrameIDsNotYetComposited.Length() > maxFrames) {
uint32_t dropFrames = mFrameIDsNotYetComposited.Length() - maxFrames;
mDroppedImageCount += dropFrames;
mFrameIDsNotYetComposited.RemoveElementsAt(0, dropFrames);
}
}
}
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 (auto& oldImg : mCurrentImages) {
if (oldImg.mFrameID == img->mFrameID &&
oldImg.mProducerID == img->mProducerID) {
img->mComposited = oldImg.mComposited;
break;
}
}
}
mCurrentImages.SwapElements(newImages);
}
void
ImageContainer::ClearImagesFromImageBridge()
{
RecursiveMutexAutoLock lock(mRecursiveMutex);
SetCurrentImageInternal(nsTArray<NonOwningImage>());
}
void
ImageContainer::SetCurrentImages(const nsTArray<NonOwningImage>& aImages)
{
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;
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) {
uint32_t i;
for (i = 0; i < mFrameIDsNotYetComposited.Length(); ++i) {
if (mFrameIDsNotYetComposited[i] <= aNotification.frameID()) {
if (mFrameIDsNotYetComposited[i] < aNotification.frameID()) {
++mDroppedImageCount;
}
} else {
break;
}
}
mFrameIDsNotYetComposited.RemoveElementsAt(0, i);
for (auto& img : mCurrentImages) {
if (img.mFrameID == aNotification.frameID()) {
img.mComposited = true;
}
}
}
if (!aNotification.imageTimeStamp().IsNull()) {
mPaintDelay = aNotification.firstCompositeTimeStamp() -
aNotification.imageTimeStamp();
}
}
#ifdef XP_WIN
D3D11YCbCrRecycleAllocator*
ImageContainer::GetD3D11YCbCrRecycleAllocator(KnowsCompositor* aAllocator)
{
if (mD3D11YCbCrRecycleAllocator &&
aAllocator == mD3D11YCbCrRecycleAllocator->GetAllocator()) {
return mD3D11YCbCrRecycleAllocator;
}
RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetContentDevice();
if (!device) {
device = gfx::DeviceManagerDx::Get()->GetCompositorDevice();
}
if (!device || !aAllocator->SupportsD3D11()) {
return nullptr;
}
RefPtr<ID3D10Multithread> multi;
HRESULT hr =
device->QueryInterface((ID3D10Multithread**)getter_AddRefs(multi));
if (FAILED(hr) || !multi) {
gfxWarning() << "Multithread safety interface not supported. " << hr;
return nullptr;
}
multi->SetMultithreadProtected(TRUE);
mD3D11YCbCrRecycleAllocator =
new D3D11YCbCrRecycleAllocator(aAllocator, device);
return mD3D11YCbCrRecycleAllocator;
}
#endif
PlanarYCbCrImage::PlanarYCbCrImage()
: Image(nullptr, ImageFormat::PLANAR_YCBCR)
, mOffscreenFormat(SurfaceFormat::UNKNOWN)
, mBufferSize(0)
{
}
RecyclingPlanarYCbCrImage::~RecyclingPlanarYCbCrImage()
{
if (mBuffer) {
mRecycleBin->RecycleBuffer(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();
}
NVImage::NVImage()
: Image(nullptr, ImageFormat::NV_IMAGE)
, mBufferSize(0)
{
}
NVImage::~NVImage() = default;
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;
auto *buffer = new uint8_t[bufferLength];
Data aData = mData;
aData.mCbCrStride = aData.mCbCrSize.width;
aData.mCbSkip = 0;
aData.mCrSkip = 0;
aData.mYChannel = buffer;
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;
// Release the temporary buffer.
delete[] buffer;
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() = default;
TextureClient*
SourceSurfaceImage::GetTextureClient(KnowsCompositor* aForwarder)
{
if (!aForwarder) {
return nullptr;
}
auto entry = mTextureClients.LookupForAdd(aForwarder->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(aForwarder,
surface,
BackendSelector::Content,
mTextureFlags,
ALLOC_DEFAULT);
}
if (textureClient) {
textureClient->SyncWithObject(aForwarder->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 layers
} // namespace mozilla