/* -*- 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 "IpcResourceUpdateQueue.h" #include #include #include "mozilla/Maybe.h" #include "mozilla/ipc/SharedMemory.h" #include "mozilla/layers/PTextureChild.h" #include "mozilla/layers/WebRenderBridgeChild.h" namespace mozilla { namespace wr { using namespace mozilla::layers; ShmSegmentsWriter::ShmSegmentsWriter(layers::WebRenderBridgeChild* aAllocator, size_t aChunkSize) : mShmAllocator(aAllocator), mCursor(0), mChunkSize(aChunkSize) { MOZ_ASSERT(mShmAllocator); } ShmSegmentsWriter::~ShmSegmentsWriter() { Clear(); } ShmSegmentsWriter::ShmSegmentsWriter(ShmSegmentsWriter&& aOther) noexcept : mSmallAllocs(std::move(aOther.mSmallAllocs)), mLargeAllocs(std::move(aOther.mLargeAllocs)), mShmAllocator(aOther.mShmAllocator), mCursor(aOther.mCursor), mChunkSize(aOther.mChunkSize) { aOther.mCursor = 0; } ShmSegmentsWriter& ShmSegmentsWriter::operator=( ShmSegmentsWriter&& aOther) noexcept { MOZ_ASSERT(IsEmpty(), "Will forget existing updates!"); Clear(); mSmallAllocs = std::move(aOther.mSmallAllocs); mLargeAllocs = std::move(aOther.mLargeAllocs); mShmAllocator = aOther.mShmAllocator; mCursor = aOther.mCursor; mChunkSize = aOther.mChunkSize; aOther.mCursor = 0; return *this; } layers::OffsetRange ShmSegmentsWriter::Write(Range aBytes) { const size_t start = mCursor; const size_t length = aBytes.length(); if (length >= mChunkSize * 4) { auto range = AllocLargeChunk(length); if (range.length()) { // Allocation was successful uint8_t* dstPtr = mLargeAllocs.LastElement().get(); memcpy(dstPtr, aBytes.begin().get(), length); } return range; } int remainingBytesToCopy = length; size_t srcCursor = 0; size_t dstCursor = mCursor; size_t currAllocLen = mSmallAllocs.Length(); while (remainingBytesToCopy > 0) { if (dstCursor >= mSmallAllocs.Length() * mChunkSize) { if (!AllocChunk()) { // Allocation failed, so roll back to the state at the start of this // Write() call and abort. while (mSmallAllocs.Length() > currAllocLen) { RefCountedShmem shm = mSmallAllocs.PopLastElement(); RefCountedShm::Dealloc(mShmAllocator, shm); } MOZ_ASSERT(mSmallAllocs.Length() == currAllocLen); return layers::OffsetRange(0, start, 0); } // Allocation succeeded, so dstCursor should now be pointing to // something inside the allocation buffer MOZ_ASSERT(dstCursor < (mSmallAllocs.Length() * mChunkSize)); } const size_t dstMaxOffset = mChunkSize * mSmallAllocs.Length(); const size_t dstBaseOffset = mChunkSize * (mSmallAllocs.Length() - 1); MOZ_ASSERT(dstCursor >= dstBaseOffset); MOZ_ASSERT(dstCursor <= dstMaxOffset); size_t availableRange = dstMaxOffset - dstCursor; size_t copyRange = std::min(availableRange, remainingBytesToCopy); uint8_t* srcPtr = &aBytes[srcCursor]; uint8_t* dstPtr = RefCountedShm::GetBytes(mSmallAllocs.LastElement()) + (dstCursor - dstBaseOffset); memcpy(dstPtr, srcPtr, copyRange); srcCursor += copyRange; dstCursor += copyRange; remainingBytesToCopy -= copyRange; // sanity check MOZ_ASSERT(remainingBytesToCopy >= 0); } mCursor += length; return layers::OffsetRange(0, start, length); } bool ShmSegmentsWriter::AllocChunk() { RefCountedShmem shm; if (!mShmAllocator->AllocResourceShmem(mChunkSize, shm)) { gfxCriticalNote << "ShmSegmentsWriter failed to allocate chunk #" << mSmallAllocs.Length(); MOZ_ASSERT(false, "ShmSegmentsWriter fails to allocate chunk"); return false; } RefCountedShm::AddRef(shm); mSmallAllocs.AppendElement(shm); return true; } layers::OffsetRange ShmSegmentsWriter::AllocLargeChunk(size_t aSize) { ipc::Shmem shm; if (!mShmAllocator->AllocShmem(aSize, &shm)) { gfxCriticalNote << "ShmSegmentsWriter failed to allocate large chunk of size " << aSize; MOZ_ASSERT(false, "ShmSegmentsWriter fails to allocate large chunk"); return layers::OffsetRange(0, 0, 0); } mLargeAllocs.AppendElement(shm); return layers::OffsetRange(mLargeAllocs.Length(), 0, aSize); } void ShmSegmentsWriter::Flush(nsTArray& aSmallAllocs, nsTArray& aLargeAllocs) { MOZ_ASSERT(aSmallAllocs.IsEmpty()); MOZ_ASSERT(aLargeAllocs.IsEmpty()); aSmallAllocs = std::move(mSmallAllocs); aLargeAllocs = std::move(mLargeAllocs); mCursor = 0; } bool ShmSegmentsWriter::IsEmpty() const { return mCursor == 0; } void ShmSegmentsWriter::Clear() { if (mShmAllocator) { IpcResourceUpdateQueue::ReleaseShmems(mShmAllocator, mSmallAllocs); IpcResourceUpdateQueue::ReleaseShmems(mShmAllocator, mLargeAllocs); } mCursor = 0; } ShmSegmentsReader::ShmSegmentsReader( const nsTArray& aSmallShmems, const nsTArray& aLargeShmems) : mSmallAllocs(aSmallShmems), mLargeAllocs(aLargeShmems), mChunkSize(0) { if (mSmallAllocs.IsEmpty()) { return; } mChunkSize = RefCountedShm::GetSize(mSmallAllocs[0]); // Check that all shmems are readable and have the same size. If anything // isn't right, set mChunkSize to zero which signifies that the reader is // in an invalid state and Read calls will return false; for (const auto& shm : mSmallAllocs) { if (!RefCountedShm::IsValid(shm) || RefCountedShm::GetSize(shm) != mChunkSize || RefCountedShm::GetBytes(shm) == nullptr) { mChunkSize = 0; return; } } for (const auto& shm : mLargeAllocs) { if (!shm.IsReadable() || shm.get() == nullptr) { mChunkSize = 0; return; } } } bool ShmSegmentsReader::ReadLarge(const layers::OffsetRange& aRange, wr::Vec& aInto) { // source = zero is for small allocs. MOZ_RELEASE_ASSERT(aRange.source() != 0); if (aRange.source() > mLargeAllocs.Length()) { return false; } size_t id = aRange.source() - 1; const ipc::Shmem& shm = mLargeAllocs[id]; if (shm.Size() < aRange.length()) { return false; } uint8_t* srcPtr = shm.get(); aInto.PushBytes(Range(srcPtr, aRange.length())); return true; } bool ShmSegmentsReader::Read(const layers::OffsetRange& aRange, wr::Vec& aInto) { if (aRange.length() == 0) { return true; } if (aRange.source() != 0) { return ReadLarge(aRange, aInto); } if (mChunkSize == 0) { return false; } if (aRange.start() + aRange.length() > mChunkSize * mSmallAllocs.Length()) { return false; } size_t initialLength = aInto.Length(); size_t srcCursor = aRange.start(); size_t remainingBytesToCopy = aRange.length(); while (remainingBytesToCopy > 0) { const size_t shm_idx = srcCursor / mChunkSize; const size_t ptrOffset = srcCursor % mChunkSize; const size_t copyRange = std::min(remainingBytesToCopy, mChunkSize - ptrOffset); uint8_t* srcPtr = RefCountedShm::GetBytes(mSmallAllocs[shm_idx]) + ptrOffset; aInto.PushBytes(Range(srcPtr, copyRange)); srcCursor += copyRange; remainingBytesToCopy -= copyRange; } return aInto.Length() - initialLength == aRange.length(); } Maybe> ShmSegmentsReader::GetReadPointerLarge( const layers::OffsetRange& aRange) { // source = zero is for small allocs. MOZ_RELEASE_ASSERT(aRange.source() != 0); if (aRange.source() > mLargeAllocs.Length()) { return Nothing(); } size_t id = aRange.source() - 1; const ipc::Shmem& shm = mLargeAllocs[id]; if (shm.Size() < aRange.length()) { return Nothing(); } uint8_t* srcPtr = shm.get(); return Some(Range(srcPtr, aRange.length())); } Maybe> ShmSegmentsReader::GetReadPointer( const layers::OffsetRange& aRange) { if (aRange.length() == 0) { return Some(Range()); } if (aRange.source() != 0) { return GetReadPointerLarge(aRange); } if (mChunkSize == 0 || aRange.start() + aRange.length() > mChunkSize * mSmallAllocs.Length()) { return Nothing(); } size_t srcCursor = aRange.start(); size_t remainingBytesToCopy = aRange.length(); const size_t shm_idx = srcCursor / mChunkSize; const size_t ptrOffset = srcCursor % mChunkSize; // Return nothing if we can't return a pointer to the full range if (mChunkSize - ptrOffset < remainingBytesToCopy) { return Nothing(); } uint8_t* srcPtr = RefCountedShm::GetBytes(mSmallAllocs[shm_idx]) + ptrOffset; return Some(Range(srcPtr, remainingBytesToCopy)); } IpcResourceUpdateQueue::IpcResourceUpdateQueue( layers::WebRenderBridgeChild* aAllocator, size_t aChunkSize) : mWriter(aAllocator, aChunkSize) {} IpcResourceUpdateQueue::IpcResourceUpdateQueue( IpcResourceUpdateQueue&& aOther) noexcept : mWriter(std::move(aOther.mWriter)), mUpdates(std::move(aOther.mUpdates)) {} IpcResourceUpdateQueue& IpcResourceUpdateQueue::operator=( IpcResourceUpdateQueue&& aOther) noexcept { MOZ_ASSERT(IsEmpty(), "Will forget existing updates!"); mWriter = std::move(aOther.mWriter); mUpdates = std::move(aOther.mUpdates); return *this; } void IpcResourceUpdateQueue::ReplaceResources(IpcResourceUpdateQueue&& aOther) { MOZ_ASSERT(IsEmpty(), "Will forget existing updates!"); mWriter = std::move(aOther.mWriter); mUpdates = std::move(aOther.mUpdates); } bool IpcResourceUpdateQueue::AddImage(ImageKey key, const ImageDescriptor& aDescriptor, Range aBytes) { auto bytes = mWriter.Write(aBytes); if (!bytes.length()) { return false; } mUpdates.AppendElement(layers::OpAddImage(aDescriptor, bytes, 0, key)); return true; } bool IpcResourceUpdateQueue::AddBlobImage(BlobImageKey key, const ImageDescriptor& aDescriptor, Range aBytes, ImageIntRect aVisibleRect) { MOZ_RELEASE_ASSERT(aDescriptor.width > 0 && aDescriptor.height > 0); auto bytes = mWriter.Write(aBytes); if (!bytes.length()) { return false; } mUpdates.AppendElement( layers::OpAddBlobImage(aDescriptor, bytes, aVisibleRect, 0, key)); return true; } void IpcResourceUpdateQueue::AddSharedExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey) { mUpdates.AppendElement(layers::OpAddSharedExternalImage(aExtId, aKey)); } void IpcResourceUpdateQueue::PushExternalImageForTexture( wr::ExternalImageId aExtId, wr::ImageKey aKey, layers::TextureClient* aTexture, bool aIsUpdate) { MOZ_ASSERT(aTexture); MOZ_ASSERT(aTexture->GetIPDLActor()); MOZ_RELEASE_ASSERT(aTexture->GetIPDLActor()->GetIPCChannel() == mWriter.WrBridge()->GetIPCChannel()); mUpdates.AppendElement(layers::OpPushExternalImageForTexture( aExtId, aKey, WrapNotNull(aTexture->GetIPDLActor()), aIsUpdate)); } bool IpcResourceUpdateQueue::UpdateImageBuffer( ImageKey aKey, const ImageDescriptor& aDescriptor, Range aBytes) { auto bytes = mWriter.Write(aBytes); if (!bytes.length()) { return false; } mUpdates.AppendElement(layers::OpUpdateImage(aDescriptor, bytes, aKey)); return true; } bool IpcResourceUpdateQueue::UpdateBlobImage(BlobImageKey aKey, const ImageDescriptor& aDescriptor, Range aBytes, ImageIntRect aVisibleRect, ImageIntRect aDirtyRect) { MOZ_ASSERT(aVisibleRect.width > 0 && aVisibleRect.height > 0); auto bytes = mWriter.Write(aBytes); if (!bytes.length()) { return false; } mUpdates.AppendElement(layers::OpUpdateBlobImage(aDescriptor, bytes, aKey, aVisibleRect, aDirtyRect)); return true; } void IpcResourceUpdateQueue::UpdateSharedExternalImage( wr::ExternalImageId aExtId, wr::ImageKey aKey, ImageIntRect aDirtyRect) { mUpdates.AppendElement( layers::OpUpdateSharedExternalImage(aExtId, aKey, aDirtyRect)); } void IpcResourceUpdateQueue::SetBlobImageVisibleArea( wr::BlobImageKey aKey, const ImageIntRect& aArea) { mUpdates.AppendElement(layers::OpSetBlobImageVisibleArea(aArea, aKey)); } void IpcResourceUpdateQueue::DeleteImage(ImageKey aKey) { mUpdates.AppendElement(layers::OpDeleteImage(aKey)); } void IpcResourceUpdateQueue::DeleteBlobImage(BlobImageKey aKey) { mUpdates.AppendElement(layers::OpDeleteBlobImage(aKey)); } bool IpcResourceUpdateQueue::AddRawFont(wr::FontKey aKey, Range aBytes, uint32_t aIndex) { auto bytes = mWriter.Write(aBytes); if (!bytes.length()) { return false; } mUpdates.AppendElement(layers::OpAddRawFont(bytes, aIndex, aKey)); return true; } bool IpcResourceUpdateQueue::AddFontDescriptor(wr::FontKey aKey, Range aBytes, uint32_t aIndex) { auto bytes = mWriter.Write(aBytes); if (!bytes.length()) { return false; } mUpdates.AppendElement(layers::OpAddFontDescriptor(bytes, aIndex, aKey)); return true; } void IpcResourceUpdateQueue::DeleteFont(wr::FontKey aKey) { mUpdates.AppendElement(layers::OpDeleteFont(aKey)); } void IpcResourceUpdateQueue::AddFontInstance( wr::FontInstanceKey aKey, wr::FontKey aFontKey, float aGlyphSize, const wr::FontInstanceOptions* aOptions, const wr::FontInstancePlatformOptions* aPlatformOptions, Range aVariations) { auto bytes = mWriter.WriteAsBytes(aVariations); mUpdates.AppendElement(layers::OpAddFontInstance( aOptions ? Some(*aOptions) : Nothing(), aPlatformOptions ? Some(*aPlatformOptions) : Nothing(), bytes, aKey, aFontKey, aGlyphSize)); } void IpcResourceUpdateQueue::DeleteFontInstance(wr::FontInstanceKey aKey) { mUpdates.AppendElement(layers::OpDeleteFontInstance(aKey)); } void IpcResourceUpdateQueue::Flush( nsTArray& aUpdates, nsTArray& aSmallAllocs, nsTArray& aLargeAllocs) { aUpdates = std::move(mUpdates); mWriter.Flush(aSmallAllocs, aLargeAllocs); } bool IpcResourceUpdateQueue::IsEmpty() const { if (mUpdates.Length() == 0) { MOZ_ASSERT(mWriter.IsEmpty()); return true; } return false; } void IpcResourceUpdateQueue::Clear() { mWriter.Clear(); mUpdates.Clear(); } // static void IpcResourceUpdateQueue::ReleaseShmems( ipc::IProtocol* aShmAllocator, nsTArray& aShms) { for (auto& shm : aShms) { if (RefCountedShm::IsValid(shm) && RefCountedShm::Release(shm) == 0) { RefCountedShm::Dealloc(aShmAllocator, shm); } } aShms.Clear(); } // static void IpcResourceUpdateQueue::ReleaseShmems(ipc::IProtocol* aShmAllocator, nsTArray& aShms) { for (auto& shm : aShms) { aShmAllocator->DeallocShmem(shm); } aShms.Clear(); } } // namespace wr } // namespace mozilla