/* -*- 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 "WindowRenderer.h" #include "gfxPlatform.h" #include "mozilla/dom/Animation.h" // for Animation #include "mozilla/dom/AnimationEffect.h" #include "mozilla/EffectSet.h" #include "mozilla/layers/PersistentBufferProvider.h" // for PersistentBufferProviderBasic, PersistentBufferProvider (ptr only) #include "nsDisplayList.h" using namespace mozilla::gfx; using namespace mozilla::layers; namespace mozilla { /** * StartFrameTimeRecording, together with StopFrameTimeRecording * enable recording of frame intervals. * * To allow concurrent consumers, a cyclic array is used which serves all * consumers, practically stateless with regard to consumers. * * To save resources, the buffer is allocated on first call to * StartFrameTimeRecording and recording is paused if no consumer which called * StartFrameTimeRecording is able to get valid results (because the cyclic * buffer was overwritten since that call). * * To determine availability of the data upon StopFrameTimeRecording: * - mRecording.mNextIndex increases on each RecordFrame, and never resets. * - Cyclic buffer position is realized as mNextIndex % bufferSize. * - StartFrameTimeRecording returns mNextIndex. When StopFrameTimeRecording is * called, the required start index is passed as an arg, and we're able to * calculate the required length. If this length is bigger than bufferSize, it * means data was overwritten. otherwise, we can return the entire sequence. * - To determine if we need to pause, mLatestStartIndex is updated to * mNextIndex on each call to StartFrameTimeRecording. If this index gets * overwritten, it means that all earlier start indices obtained via * StartFrameTimeRecording were also overwritten, hence, no point in * recording, so pause. * - mCurrentRunStartIndex indicates the oldest index of the recording after * which the recording was not paused. If StopFrameTimeRecording is invoked * with a start index older than this, it means that some frames were not * recorded, so data is invalid. */ uint32_t FrameRecorder::StartFrameTimeRecording(int32_t aBufferSize) { if (mRecording.mIsPaused) { mRecording.mIsPaused = false; if (!mRecording.mIntervals.Length()) { // Initialize recording buffers mRecording.mIntervals.SetLength(aBufferSize); } // After being paused, recent values got invalid. Update them to now. mRecording.mLastFrameTime = TimeStamp::Now(); // Any recording which started before this is invalid, since we were paused. mRecording.mCurrentRunStartIndex = mRecording.mNextIndex; } // If we'll overwrite this index, there are no more consumers with aStartIndex // for which we're able to provide the full recording, so no point in keep // recording. mRecording.mLatestStartIndex = mRecording.mNextIndex; return mRecording.mNextIndex; } void FrameRecorder::RecordFrame() { if (!mRecording.mIsPaused) { TimeStamp now = TimeStamp::Now(); uint32_t i = mRecording.mNextIndex % mRecording.mIntervals.Length(); mRecording.mIntervals[i] = static_cast((now - mRecording.mLastFrameTime).ToMilliseconds()); mRecording.mNextIndex++; mRecording.mLastFrameTime = now; if (mRecording.mNextIndex > (mRecording.mLatestStartIndex + mRecording.mIntervals.Length())) { // We've just overwritten the most recent recording start -> pause. mRecording.mIsPaused = true; } } } void FrameRecorder::StopFrameTimeRecording(uint32_t aStartIndex, nsTArray& aFrameIntervals) { uint32_t bufferSize = mRecording.mIntervals.Length(); uint32_t length = mRecording.mNextIndex - aStartIndex; if (mRecording.mIsPaused || length > bufferSize || aStartIndex < mRecording.mCurrentRunStartIndex) { // aStartIndex is too old. Also if aStartIndex was issued before // mRecordingNextIndex overflowed (uint32_t) // and stopped after the overflow (would happen once every 828 days of // constant 60fps). length = 0; } if (!length) { aFrameIntervals.Clear(); return; // empty recording, return empty arrays. } // Set length in advance to avoid possibly repeated reallocations aFrameIntervals.SetLength(length); uint32_t cyclicPos = aStartIndex % bufferSize; for (uint32_t i = 0; i < length; i++, cyclicPos++) { if (cyclicPos == bufferSize) { cyclicPos = 0; } aFrameIntervals[i] = mRecording.mIntervals[cyclicPos]; } } already_AddRefed WindowRenderer::CreatePersistentBufferProvider( const mozilla::gfx::IntSize& aSize, mozilla::gfx::SurfaceFormat aFormat, bool aWillReadFrequently) { RefPtr bufferProvider; // If we are using remote canvas we don't want to use acceleration in // non-remote layer managers, so we always use the fallback software one. // If will-read-frequently is set, avoid using the preferred backend in // favor of the fallback backend in case the preferred backend provides // acceleration. if (!aWillReadFrequently && (!gfxPlatform::UseRemoteCanvas() || !gfxPlatform::IsBackendAccelerated( gfxPlatform::GetPlatform()->GetPreferredCanvasBackend()))) { bufferProvider = PersistentBufferProviderBasic::Create( aSize, aFormat, gfxPlatform::GetPlatform()->GetPreferredCanvasBackend()); } if (!bufferProvider) { bufferProvider = PersistentBufferProviderBasic::Create( aSize, aFormat, gfxPlatform::GetPlatform()->GetFallbackCanvasBackend()); } return bufferProvider.forget(); } void WindowRenderer::AddPartialPrerenderedAnimation( uint64_t aCompositorAnimationId, dom::Animation* aAnimation) { mPartialPrerenderedAnimations.InsertOrUpdate(aCompositorAnimationId, RefPtr{aAnimation}); aAnimation->SetPartialPrerendered(aCompositorAnimationId); } void WindowRenderer::RemovePartialPrerenderedAnimation( uint64_t aCompositorAnimationId, dom::Animation* aAnimation) { MOZ_ASSERT(aAnimation); #ifdef DEBUG RefPtr animation; if (mPartialPrerenderedAnimations.Remove(aCompositorAnimationId, getter_AddRefs(animation)) && // It may be possible that either animation's effect has already been // nulled out via Animation::SetEffect() so ignore such cases. aAnimation->GetEffect() && aAnimation->GetEffect()->AsKeyframeEffect() && animation->GetEffect() && animation->GetEffect()->AsKeyframeEffect()) { MOZ_ASSERT( EffectSet::GetForEffect(aAnimation->GetEffect()->AsKeyframeEffect()) == EffectSet::GetForEffect(animation->GetEffect()->AsKeyframeEffect())); } #else mPartialPrerenderedAnimations.Remove(aCompositorAnimationId); #endif aAnimation->ResetPartialPrerendered(); } void WindowRenderer::UpdatePartialPrerenderedAnimations( const nsTArray& aJankedAnimations) { for (uint64_t id : aJankedAnimations) { RefPtr animation; if (mPartialPrerenderedAnimations.Remove(id, getter_AddRefs(animation))) { animation->UpdatePartialPrerendered(); } } } void FallbackRenderer::SetTarget(gfxContext* aTarget, layers::BufferMode aDoubleBuffering) { mTarget = aTarget; mBufferMode = aDoubleBuffering; } bool FallbackRenderer::BeginTransaction(const nsCString& aURL) { if (!mTarget) { return false; } return true; } void FallbackRenderer::EndTransactionWithColor(const nsIntRect& aRect, const gfx::DeviceColor& aColor) { mTarget->GetDrawTarget()->FillRect(Rect(aRect), ColorPattern(aColor)); } void FallbackRenderer::EndTransactionWithList(nsDisplayListBuilder* aBuilder, nsDisplayList* aList, int32_t aAppUnitsPerDevPixel, EndTransactionFlags aFlags) { if (aFlags & EndTransactionFlags::END_NO_COMPOSITE) { return; } DrawTarget* dt = mTarget->GetDrawTarget(); BackendType backend = gfxPlatform::GetPlatform()->GetContentBackendFor( LayersBackend::LAYERS_NONE); RefPtr dest = gfxPlatform::GetPlatform()->CreateDrawTargetForBackend( backend, dt->GetSize(), dt->GetFormat()); if (dest) { gfxContext ctx(dest, /* aPreserveTransform */ true); nsRegion opaque = aList->GetOpaqueRegion(aBuilder); if (opaque.Contains(aList->GetComponentAlphaBounds(aBuilder))) { dest->SetPermitSubpixelAA(true); } aList->Paint(aBuilder, &ctx, aAppUnitsPerDevPixel); RefPtr snapshot = dest->Snapshot(); dt->DrawSurface(snapshot, Rect(dest->GetRect()), Rect(dest->GetRect()), DrawSurfaceOptions(), DrawOptions(1.0f, CompositionOp::OP_SOURCE)); } } } // namespace mozilla