/* -*- 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 "mozilla/layers/OMTASampler.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/layers/CompositorAnimationStorage.h" #include "mozilla/layers/CompositorThread.h" #include "mozilla/layers/OMTAController.h" #include "mozilla/layers/SynchronousTask.h" #include "mozilla/layers/WebRenderBridgeParent.h" #include "mozilla/webrender/WebRenderAPI.h" namespace mozilla { namespace layers { StaticMutex OMTASampler::sWindowIdLock; StaticAutoPtr>> OMTASampler::sWindowIdMap; OMTASampler::OMTASampler(const RefPtr& aAnimStorage, LayersId aRootLayersId) : mAnimStorage(aAnimStorage), mStorageLock("OMTASampler::mStorageLock"), mThreadIdLock("OMTASampler::mThreadIdLock"), mSampleTimeLock("OMTASampler::mSampleTimeLock") { mController = new OMTAController(aRootLayersId); } void OMTASampler::Destroy() { StaticMutexAutoLock lock(sWindowIdLock); if (mWindowId) { MOZ_ASSERT(sWindowIdMap); sWindowIdMap->erase(wr::AsUint64(*mWindowId)); } } void OMTASampler::SetWebRenderWindowId(const wr::WrWindowId& aWindowId) { StaticMutexAutoLock lock(sWindowIdLock); MOZ_ASSERT(!mWindowId); mWindowId = Some(aWindowId); if (!sWindowIdMap) { sWindowIdMap = new std::unordered_map>(); NS_DispatchToMainThread( NS_NewRunnableFunction("OMTASampler::ClearOnShutdown", [] { ClearOnShutdown(&sWindowIdMap); })); } (*sWindowIdMap)[wr::AsUint64(aWindowId)] = this; } /*static*/ void OMTASampler::SetSamplerThread(const wr::WrWindowId& aWindowId) { if (RefPtr sampler = GetSampler(aWindowId)) { MutexAutoLock lock(sampler->mThreadIdLock); sampler->mSamplerThreadId = Some(PlatformThread::CurrentId()); } } /*static*/ void OMTASampler::Sample(const wr::WrWindowId& aWindowId, wr::Transaction* aTransaction) { if (RefPtr sampler = GetSampler(aWindowId)) { wr::TransactionWrapper txn(aTransaction); sampler->Sample(txn); } } void OMTASampler::SetSampleTime(const TimeStamp& aSampleTime) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); const bool hasAnimations = HasAnimations(); MutexAutoLock lock(mSampleTimeLock); // Reset the previous time stamp if we don't already have any running // animations to avoid using the time which is far behind for newly // started animations. mPreviousSampleTime = hasAnimations ? std::move(mSampleTime) : TimeStamp(); mSampleTime = aSampleTime; } void OMTASampler::ResetPreviousSampleTime() { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); MutexAutoLock lock(mSampleTimeLock); mPreviousSampleTime = TimeStamp(); } void OMTASampler::Sample(wr::TransactionWrapper& aTxn) { MOZ_ASSERT(IsSamplerThread()); TimeStamp sampleTime; TimeStamp previousSampleTime; { // scope lock MutexAutoLock lock(mSampleTimeLock); // If mSampleTime is null we're in a startup phase where the // WebRenderBridgeParent hasn't yet provided us with a sample time. // If we're that early there probably aren't any OMTA animations happening // anyway, so using Timestamp::Now() should be fine. sampleTime = mSampleTime.IsNull() ? TimeStamp::Now() : mSampleTime; previousSampleTime = mPreviousSampleTime; } WrAnimations animations = SampleAnimations(previousSampleTime, sampleTime); // We do this even if the arrays are empty, because it will clear out any // previous properties store on the WR side, which is desirable. aTxn.UpdateDynamicProperties(animations.mOpacityArrays, animations.mTransformArrays, animations.mColorArrays); } WrAnimations OMTASampler::SampleAnimations(const TimeStamp& aPreviousSampleTime, const TimeStamp& aSampleTime) { MOZ_ASSERT(IsSamplerThread()); MutexAutoLock lock(mStorageLock); mAnimStorage->SampleAnimations(mController, aPreviousSampleTime, aSampleTime); return mAnimStorage->CollectWebRenderAnimations(); } OMTAValue OMTASampler::GetOMTAValue(const uint64_t& aId) const { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); MutexAutoLock lock(mStorageLock); return mAnimStorage->GetOMTAValue(aId); } void OMTASampler::SampleForTesting(const Maybe& aTestingSampleTime) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); TimeStamp sampleTime; TimeStamp previousSampleTime; { // scope lock MutexAutoLock timeLock(mSampleTimeLock); if (aTestingSampleTime) { // If we are on testing refresh mode, use the testing time stamp for both // of the previous sample time and the current sample time since unlike // normal refresh mode, the testing mode animations on the compositor are // synchronously composed, so we don't need to worry about the time gap // between the main thread and compositor thread. sampleTime = *aTestingSampleTime; previousSampleTime = *aTestingSampleTime; } else { sampleTime = mSampleTime; previousSampleTime = mPreviousSampleTime; } } MutexAutoLock storageLock(mStorageLock); mAnimStorage->SampleAnimations(mController, previousSampleTime, sampleTime); } void OMTASampler::SetAnimations( uint64_t aId, const LayersId& aLayersId, const nsTArray& aAnimations) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); MutexAutoLock lock(mStorageLock); mAnimStorage->SetAnimations(aId, aLayersId, aAnimations); } bool OMTASampler::HasAnimations() const { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); MutexAutoLock lock(mStorageLock); return mAnimStorage->HasAnimations(); } void OMTASampler::ClearActiveAnimations( std::unordered_map& aActiveAnimations) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); MutexAutoLock lock(mStorageLock); for (const auto& id : aActiveAnimations) { mAnimStorage->ClearById(id.first); } } void OMTASampler::RemoveEpochDataPriorTo( std::queue& aCompositorAnimationsToDelete, std::unordered_map& aActiveAnimations, const wr::WrEpoch& aRenderedEpoch) { MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread()); MutexAutoLock lock(mStorageLock); while (!aCompositorAnimationsToDelete.empty()) { if (aRenderedEpoch < aCompositorAnimationsToDelete.front().mEpoch) { break; } for (uint64_t id : aCompositorAnimationsToDelete.front().mIds) { const auto activeAnim = aActiveAnimations.find(id); if (activeAnim == aActiveAnimations.end()) { NS_ERROR("Tried to delete invalid animation"); continue; } // Check if animation delete request is still valid. if (activeAnim->second <= aCompositorAnimationsToDelete.front().mEpoch) { mAnimStorage->ClearById(id); aActiveAnimations.erase(activeAnim); } } aCompositorAnimationsToDelete.pop(); } } bool OMTASampler::IsSamplerThread() const { MutexAutoLock lock(mThreadIdLock); return mSamplerThreadId && PlatformThread::CurrentId() == *mSamplerThreadId; } /*static*/ already_AddRefed OMTASampler::GetSampler( const wr::WrWindowId& aWindowId) { RefPtr sampler; StaticMutexAutoLock lock(sWindowIdLock); if (sWindowIdMap) { auto it = sWindowIdMap->find(wr::AsUint64(aWindowId)); if (it != sWindowIdMap->end()) { sampler = it->second; } } return sampler.forget(); } } // namespace layers } // namespace mozilla void omta_register_sampler(mozilla::wr::WrWindowId aWindowId) { mozilla::layers::OMTASampler::SetSamplerThread(aWindowId); } void omta_sample(mozilla::wr::WrWindowId aWindowId, mozilla::wr::Transaction* aTransaction) { mozilla::layers::OMTASampler::Sample(aWindowId, aTransaction); } void omta_deregister_sampler(mozilla::wr::WrWindowId aWindowId) {}