diff --git a/dom/canvas/ImageBitmap.cpp b/dom/canvas/ImageBitmap.cpp index 46fb1e9a887b..5d0053c86b40 100644 --- a/dom/canvas/ImageBitmap.cpp +++ b/dom/canvas/ImageBitmap.cpp @@ -916,7 +916,7 @@ ImageBitmap::CreateInternal(nsIGlobalObject* aGlobal, HTMLCanvasElement& aCanvas aCanvasEl.GetCurrentContextType() == CanvasContextType::WebGL2) && aCropRect.isSome()) { // The _surface_ must be a DataSourceSurface. - MOZ_ASSERT(surface->GetType() == SurfaceType::DATA, + MOZ_ASSERT(surface->IsDataSourceSurface(), "The snapshot SourceSurface from WebGL rendering contest is not \ DataSourceSurface."); RefPtr dataSurface = surface->GetDataSurface(); diff --git a/gfx/2d/2D.h b/gfx/2d/2D.h index e5d060250cdd..2e11c1747528 100644 --- a/gfx/2d/2D.h +++ b/gfx/2d/2D.h @@ -352,6 +352,16 @@ public: */ virtual bool IsValid() const { return true; } + /** + * This function will return true if the surface type matches that of a + * DataSourceSurface and if GetDataSurface will return the same object. + */ + bool IsDataSourceSurface() const { + SurfaceType type = GetType(); + return type == SurfaceType::DATA || + type == SurfaceType::DATA_SHARED; + } + /** * This function will get a DataSourceSurface for this surface, a * DataSourceSurface's data can be accessed directly. diff --git a/gfx/2d/DataSourceSurface.cpp b/gfx/2d/DataSourceSurface.cpp index 75d84350642c..42f075e0d9b6 100644 --- a/gfx/2d/DataSourceSurface.cpp +++ b/gfx/2d/DataSourceSurface.cpp @@ -13,7 +13,7 @@ already_AddRefed DataSourceSurface::GetDataSurface() { RefPtr surface = - (GetType() == SurfaceType::DATA) ? this : new DataSourceSurfaceWrapper(this); + IsDataSourceSurface() ? this : new DataSourceSurfaceWrapper(this); return surface.forget(); } diff --git a/gfx/2d/Logging.h b/gfx/2d/Logging.h index 0d9886cff5cf..7ac4ae7f9934 100644 --- a/gfx/2d/Logging.h +++ b/gfx/2d/Logging.h @@ -465,6 +465,9 @@ public: case SurfaceType::TILED: mMessage << "SurfaceType::TILED"; break; + case SurfaceType::DATA_SHARED: + mMessage << "SurfaceType::DATA_SHARED"; + break; default: mMessage << "Invalid SurfaceType (" << (int)aType << ")"; break; diff --git a/gfx/2d/Types.h b/gfx/2d/Types.h index 72cba5f2ccba..915b038f0668 100644 --- a/gfx/2d/Types.h +++ b/gfx/2d/Types.h @@ -29,7 +29,8 @@ enum class SurfaceType : int8_t { DUAL_DT, /* Snapshot of a dual drawtarget */ D2D1_1_IMAGE, /* A D2D 1.1 ID2D1Image SourceSurface */ RECORDING, /* Surface used for recording */ - TILED /* Surface from a tiled DrawTarget */ + TILED, /* Surface from a tiled DrawTarget */ + DATA_SHARED, /* Data surface using shared memory */ }; enum class SurfaceFormat : int8_t { diff --git a/gfx/layers/LayersLogging.cpp b/gfx/layers/LayersLogging.cpp index 6cdbc22c840e..8af482785d88 100644 --- a/gfx/layers/LayersLogging.cpp +++ b/gfx/layers/LayersLogging.cpp @@ -348,6 +348,8 @@ AppendToString(std::stringstream& aStream, gfx::SurfaceType aType, aStream << "SurfaceType::RECORDING"; break; case SurfaceType::TILED: aStream << "SurfaceType::TILED"; break; + case SurfaceType::DATA_SHARED: + aStream << "SurfaceType::DATA_SHARED"; break; default: NS_ERROR("unknown surface type"); aStream << "???"; diff --git a/gfx/layers/SourceSurfaceSharedData.cpp b/gfx/layers/SourceSurfaceSharedData.cpp new file mode 100644 index 000000000000..e0471baea944 --- /dev/null +++ b/gfx/layers/SourceSurfaceSharedData.cpp @@ -0,0 +1,140 @@ +/* -*- 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 "SourceSurfaceSharedData.h" + +#include "mozilla/Likely.h" +#include "mozilla/Types.h" // for decltype + +namespace mozilla { +namespace gfx { + +bool +SourceSurfaceSharedData::Init(const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat) +{ + mSize = aSize; + mStride = aStride; + mFormat = aFormat; + + size_t len = GetAlignedDataLength(); + mBuf = new SharedMemoryBasic(); + if (NS_WARN_IF(!mBuf->Create(len)) || + NS_WARN_IF(!mBuf->Map(len))) { + mBuf = nullptr; + return false; + } + + return true; +} + +void +SourceSurfaceSharedData::GuaranteePersistance() +{ + // Shared memory is not unmapped until we release SourceSurfaceSharedData. +} + +void +SourceSurfaceSharedData::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + size_t& aHeapSizeOut, + size_t& aNonHeapSizeOut) const +{ + if (mBuf) { + aNonHeapSizeOut += GetAlignedDataLength(); + } +} + +uint8_t* +SourceSurfaceSharedData::GetDataInternal() const +{ + mMutex.AssertCurrentThreadOwns(); + + // If we have an old buffer lingering, it is because we get reallocated to + // get a new handle to share, but there were still active mappings. + if (MOZ_UNLIKELY(mOldBuf)) { + MOZ_ASSERT(mMapCount > 0); + MOZ_ASSERT(mFinalized); + return static_cast(mOldBuf->memory()); + } + return static_cast(mBuf->memory()); +} + +nsresult +SourceSurfaceSharedData::ShareToProcess(base::ProcessId aPid, + SharedMemoryBasic::Handle& aHandle) +{ + MutexAutoLock lock(mMutex); + + if (mClosed) { + return NS_ERROR_NOT_AVAILABLE; + } + + bool shared = mBuf->ShareToProcess(aPid, &aHandle); + if (MOZ_UNLIKELY(!shared)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +void +SourceSurfaceSharedData::CloseHandleInternal() +{ + mMutex.AssertCurrentThreadOwns(); + + if (mClosed) { + return; + } + + if (mFinalized && mShared) { + mBuf->CloseHandle(); + mClosed = true; + } +} + +bool +SourceSurfaceSharedData::ReallocHandle() +{ + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mClosed); + MOZ_ASSERT(mFinalized); + + size_t len = GetAlignedDataLength(); + RefPtr buf = new SharedMemoryBasic(); + if (NS_WARN_IF(!buf->Create(len)) || + NS_WARN_IF(!buf->Map(len))) { + return false; + } + + size_t copyLen = GetDataLength(); + memcpy(buf->memory(), mBuf->memory(), copyLen); + buf->Protect(static_cast(buf->memory()), len, RightsRead); + + if (mMapCount > 0 && !mOldBuf) { + mOldBuf = Move(mBuf); + } + mBuf = Move(buf); + mClosed = false; + mShared = false; + return true; +} + +void +SourceSurfaceSharedData::Finalize() +{ + MutexAutoLock lock(mMutex); + MOZ_ASSERT(!mClosed); + MOZ_ASSERT(!mFinalized); + + size_t len = GetAlignedDataLength(); + mBuf->Protect(static_cast(mBuf->memory()), len, RightsRead); + + mFinalized = true; + CloseHandleInternal(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/layers/SourceSurfaceSharedData.h b/gfx/layers/SourceSurfaceSharedData.h new file mode 100644 index 000000000000..9724d881e3f0 --- /dev/null +++ b/gfx/layers/SourceSurfaceSharedData.h @@ -0,0 +1,163 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_SOURCESURFACESHAREDDATA_H_ +#define MOZILLA_GFX_SOURCESURFACESHAREDDATA_H_ + +#include "mozilla/gfx/2D.h" +#include "mozilla/Mutex.h" +#include "mozilla/ipc/SharedMemoryBasic.h" + +namespace mozilla { +namespace gfx { + +/** + * This class is used to wrap shared (as in process) data buffers used by a + * source surface. + */ +class SourceSurfaceSharedData final : public DataSourceSurface +{ + typedef mozilla::ipc::SharedMemoryBasic SharedMemoryBasic; + +public: + MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceSharedData, override) + + SourceSurfaceSharedData() + : mMutex("SourceSurfaceSharedData") + , mStride(0) + , mMapCount(0) + , mFormat(SurfaceFormat::UNKNOWN) + , mClosed(false) + , mFinalized(false) + , mShared(false) + { + } + + bool Init(const IntSize &aSize, + int32_t aStride, + SurfaceFormat aFormat); + + uint8_t* GetData() override + { + MutexAutoLock lock(mMutex); + return GetDataInternal(); + } + + int32_t Stride() override { return mStride; } + + SurfaceType GetType() const override { return SurfaceType::DATA_SHARED; } + IntSize GetSize() const override { return mSize; } + SurfaceFormat GetFormat() const override { return mFormat; } + + void GuaranteePersistance() override; + + void AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, + size_t& aHeapSizeOut, + size_t& aNonHeapSizeOut) const override; + + /** + * Although Map (and Moz2D in general) isn't normally threadsafe, + * we want to allow it for SourceSurfaceSharedData since it should + * always be fine (for reading at least). + * + * This is the same as the base class implementation except using + * mMapCount instead of mIsMapped since that breaks for multithread. + * + * Additionally if a reallocation happened while there were active + * mappings, then we guarantee that GetData will continue to return + * the same data pointer by retaining the old shared buffer until + * the last mapping is freed via Unmap. + */ + bool Map(MapType, MappedSurface *aMappedSurface) override + { + MutexAutoLock lock(mMutex); + ++mMapCount; + aMappedSurface->mData = GetDataInternal(); + aMappedSurface->mStride = mStride; + return true; + } + + void Unmap() override + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mMapCount > 0); + if (--mMapCount == 0) { + mOldBuf = nullptr; + } + } + + /** + * Get a handle to share to another process for this buffer. Returns: + * NS_OK -- success, aHandle is valid. + * NS_ERROR_NOT_AVAILABLE -- handle was closed, need to reallocate. + * NS_ERROR_FAILURE -- failed to create a handle to share. + */ + nsresult ShareToProcess(base::ProcessId aPid, + SharedMemoryBasic::Handle& aHandle); + + /** + * Indicates the buffer is not expected to be shared with any more processes. + * May release the handle if possible (see CloseHandleInternal). */ + void FinishedSharing() + { + MutexAutoLock lock(mMutex); + mShared = true; + CloseHandleInternal(); + } + + /** + * Allocate a new shared memory buffer so that we can get a new handle for + * sharing to new processes. ShareToProcess must have failed with + * NS_ERROR_NOT_AVAILABLE in order for this to be safe to call. Returns true + * if the operation succeeds. If it fails, there is no state change. + */ + bool ReallocHandle(); + + /** + * Indicates we have finished writing to the buffer and it may be marked as + * read only. May release the handle if possible (see CloseHandleInternal). + */ + void Finalize(); + +private: + ~SourceSurfaceSharedData() override + { + MOZ_ASSERT(mMapCount == 0); + } + + uint8_t* GetDataInternal() const; + + size_t GetDataLength() const + { + return static_cast(mStride) * mSize.height; + } + + size_t GetAlignedDataLength() const + { + return mozilla::ipc::SharedMemory::PageAlignedSize(GetDataLength()); + } + + /** + * Attempt to close the handle. Only if the buffer has been both finalized + * and we have completed sharing will it be released. + */ + void CloseHandleInternal(); + + Mutex mMutex; + int32_t mStride; + int32_t mMapCount; + IntSize mSize; + RefPtr mBuf; + RefPtr mOldBuf; + SurfaceFormat mFormat; + bool mClosed : 1; + bool mFinalized : 1; + bool mShared : 1; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* MOZILLA_GFX_SOURCESURFACESHAREDDATA_H_ */ diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build index 9b619ba56525..7df56be0bfce 100644 --- a/gfx/layers/moz.build +++ b/gfx/layers/moz.build @@ -201,6 +201,7 @@ EXPORTS.mozilla.layers += [ 'opengl/TextureHostOGL.h', 'PersistentBufferProvider.h', 'RenderTrace.h', + 'SourceSurfaceSharedData.h', 'SourceSurfaceVolatileData.h', 'TextureWrapperImage.h', 'TransactionIdAllocator.h', @@ -374,6 +375,7 @@ UNIFIED_SOURCES += [ 'RenderTrace.cpp', 'RotatedBuffer.cpp', 'ShareableCanvasLayer.cpp', + 'SourceSurfaceSharedData.cpp', 'SourceSurfaceVolatileData.cpp', 'TextureWrapperImage.cpp', ] diff --git a/gfx/thebes/gfxUtils.cpp b/gfx/thebes/gfxUtils.cpp index f958a56932da..d36bbf770a40 100644 --- a/gfx/thebes/gfxUtils.cpp +++ b/gfx/thebes/gfxUtils.cpp @@ -885,7 +885,7 @@ gfxUtils::CopySurfaceToDataSourceSurfaceWithFormat(SourceSurface* aSurface, Rect bounds(0, 0, aSurface->GetSize().width, aSurface->GetSize().height); - if (aSurface->GetType() != SurfaceType::DATA) { + if (!aSurface->IsDataSourceSurface()) { // If the surface is NOT of type DATA then its data is not mapped into main // memory. Format conversion is probably faster on the GPU, and by doing it // there we can avoid any expensive uploads/readbacks except for (possibly) diff --git a/image/test/gtest/TestDecodeToSurface.cpp b/image/test/gtest/TestDecodeToSurface.cpp index 278cdb1fdc89..dd22d4308d9b 100644 --- a/image/test/gtest/TestDecodeToSurface.cpp +++ b/image/test/gtest/TestDecodeToSurface.cpp @@ -47,7 +47,7 @@ public: imgIContainer::DECODE_FLAGS_DEFAULT); ASSERT_TRUE(mSurface != nullptr); - EXPECT_EQ(SurfaceType::DATA, mSurface->GetType()); + EXPECT_TRUE(mSurface->IsDataSourceSurface()); EXPECT_TRUE(mSurface->GetFormat() == SurfaceFormat::B8G8R8X8 || mSurface->GetFormat() == SurfaceFormat::B8G8R8A8); EXPECT_EQ(mTestCase.mSize, mSurface->GetSize()); diff --git a/image/test/gtest/TestDecoders.cpp b/image/test/gtest/TestDecoders.cpp index 0eba5b34a53c..d39b8e404a60 100644 --- a/image/test/gtest/TestDecoders.cpp +++ b/image/test/gtest/TestDecoders.cpp @@ -64,7 +64,7 @@ CheckDecoderState(const ImageTestCase& aTestCase, Decoder* aDecoder) RefPtr surface = currentFrame->GetSourceSurface(); // Verify that the resulting surfaces matches our expectations. - EXPECT_EQ(SurfaceType::DATA, surface->GetType()); + EXPECT_TRUE(surface->IsDataSourceSurface()); EXPECT_TRUE(surface->GetFormat() == SurfaceFormat::B8G8R8X8 || surface->GetFormat() == SurfaceFormat::B8G8R8A8); EXPECT_EQ(aTestCase.mOutputSize, surface->GetSize());