forked from mirrors/gecko-dev
		
	 6c738a6ce0
			
		
	
	
		6c738a6ce0
		
	
	
	
	
		
			
			UnbindTextureSource() was removed by mistake. UnbindTextureSource() is necessary to call ReadUnlock(). Differential Revision: https://phabricator.services.mozilla.com/D132029
		
			
				
	
	
		
			378 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			378 lines
		
	
	
	
		
			14 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 "ImageComposite.h"
 | |
| 
 | |
| #include <inttypes.h>
 | |
| 
 | |
| #include "mozilla/ProfilerMarkers.h"
 | |
| #include "gfxPlatform.h"
 | |
| #include "nsPrintfCString.h"
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| using namespace gfx;
 | |
| 
 | |
| namespace layers {
 | |
| 
 | |
| /* static */ const float ImageComposite::BIAS_TIME_MS = 1.0f;
 | |
| 
 | |
| ImageComposite::ImageComposite() = default;
 | |
| 
 | |
| ImageComposite::~ImageComposite() = default;
 | |
| 
 | |
| TimeStamp ImageComposite::GetBiasedTime(const TimeStamp& aInput) const {
 | |
|   switch (mBias) {
 | |
|     case ImageComposite::BIAS_NEGATIVE:
 | |
|       return aInput - TimeDuration::FromMilliseconds(BIAS_TIME_MS);
 | |
|     case ImageComposite::BIAS_POSITIVE:
 | |
|       return aInput + TimeDuration::FromMilliseconds(BIAS_TIME_MS);
 | |
|     default:
 | |
|       return aInput;
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageComposite::UpdateBias(size_t aImageIndex, bool aFrameChanged) {
 | |
|   MOZ_ASSERT(aImageIndex < ImagesCount());
 | |
| 
 | |
|   TimeStamp compositionTime = GetCompositionTime();
 | |
|   TimeStamp compositedImageTime = mImages[aImageIndex].mTimeStamp;
 | |
|   TimeStamp nextImageTime = aImageIndex + 1 < ImagesCount()
 | |
|                                 ? mImages[aImageIndex + 1].mTimeStamp
 | |
|                                 : TimeStamp();
 | |
| 
 | |
|   if (profiler_thread_is_being_profiled_for_markers() && compositedImageTime &&
 | |
|       nextImageTime) {
 | |
|     TimeDuration offsetCurrent = compositedImageTime - compositionTime;
 | |
|     TimeDuration offsetNext = nextImageTime - compositionTime;
 | |
|     nsPrintfCString str("current %.2lfms, next %.2lfms",
 | |
|                         offsetCurrent.ToMilliseconds(),
 | |
|                         offsetNext.ToMilliseconds());
 | |
|     PROFILER_MARKER_TEXT("Video frame offsets", GRAPHICS, {}, str);
 | |
|   }
 | |
| 
 | |
|   if (compositedImageTime.IsNull()) {
 | |
|     mBias = ImageComposite::BIAS_NONE;
 | |
|     return;
 | |
|   }
 | |
|   TimeDuration threshold = TimeDuration::FromMilliseconds(1.5);
 | |
|   if (compositionTime - compositedImageTime < threshold &&
 | |
|       compositionTime - compositedImageTime > -threshold) {
 | |
|     // The chosen frame's time is very close to the composition time (probably
 | |
|     // just before the current composition time, but due to previously set
 | |
|     // negative bias, it could be just after the current composition time too).
 | |
|     // If the inter-frame time is almost exactly equal to (a multiple of)
 | |
|     // the inter-composition time, then we're in a dangerous situation because
 | |
|     // jitter might cause frames to fall one side or the other of the
 | |
|     // composition times, causing many frames to be skipped or duplicated.
 | |
|     // Try to prevent that by adding a negative bias to the frame times during
 | |
|     // the next composite; that should ensure the next frame's time is treated
 | |
|     // as falling just before a composite time.
 | |
|     mBias = ImageComposite::BIAS_NEGATIVE;
 | |
|     return;
 | |
|   }
 | |
|   if (!nextImageTime.IsNull() && nextImageTime - compositionTime < threshold &&
 | |
|       nextImageTime - compositionTime > -threshold) {
 | |
|     // The next frame's time is very close to our composition time (probably
 | |
|     // just after the current composition time, but due to previously set
 | |
|     // positive bias, it could be just before the current composition time too).
 | |
|     // We're in a dangerous situation because jitter might cause frames to
 | |
|     // fall one side or the other of the composition times, causing many frames
 | |
|     // to be skipped or duplicated.
 | |
|     // Specifically, the next composite is at risk of picking the "next + 1"
 | |
|     // frame rather than the "next" frame, which would cause the "next" frame to
 | |
|     // be skipped. Try to prevent that by adding a positive bias to the frame
 | |
|     // times during the next composite; if the inter-frame time is almost
 | |
|     // exactly equal to the inter-composition time, that should ensure that the
 | |
|     // next + 1 frame falls just *after* the next composition time, and the next
 | |
|     // composite should then pick the next frame rather than the next + 1 frame.
 | |
|     mBias = ImageComposite::BIAS_POSITIVE;
 | |
|     return;
 | |
|   }
 | |
|   if (aFrameChanged) {
 | |
|     // The current and next video frames are a sufficient distance from the
 | |
|     // composition time and we can reliably pick the right frame without bias.
 | |
|     // Reset the bias.
 | |
|     // We only do this when the frame changed. Otherwise, when playing a 30fps
 | |
|     // video on a 60fps display, we'd keep resetting the bias during the "middle
 | |
|     // frames".
 | |
|     mBias = ImageComposite::BIAS_NONE;
 | |
|   }
 | |
| }
 | |
| 
 | |
| int ImageComposite::ChooseImageIndex() {
 | |
|   // ChooseImageIndex is called for all images in the layer when it is visible.
 | |
|   // Change to this behaviour would break dropped frames counting calculation:
 | |
|   // We rely on this assumption to determine if during successive runs an
 | |
|   // image is returned that isn't the one following immediately the previous one
 | |
|   if (mImages.IsEmpty()) {
 | |
|     return -1;
 | |
|   }
 | |
| 
 | |
|   TimeStamp compositionTime = GetCompositionTime();
 | |
|   auto compositionOpportunityId = GetCompositionOpportunityId();
 | |
|   if (compositionTime &&
 | |
|       compositionOpportunityId != mLastChooseImageIndexComposition) {
 | |
|     // We are inside a composition, in the first call to ChooseImageIndex during
 | |
|     // this composition.
 | |
|     // Find the newest frame whose biased timestamp is at or before
 | |
|     // `compositionTime`.
 | |
|     uint32_t imageIndex = 0;
 | |
|     while (imageIndex + 1 < mImages.Length() &&
 | |
|            mImages[imageIndex + 1].mTextureHost->IsValid() &&
 | |
|            GetBiasedTime(mImages[imageIndex + 1].mTimeStamp) <=
 | |
|                compositionTime) {
 | |
|       ++imageIndex;
 | |
|     }
 | |
| 
 | |
|     if (!mImages[imageIndex].mTextureHost->IsValid()) {
 | |
|       // Still not ready to be shown.
 | |
|       return -1;
 | |
|     }
 | |
| 
 | |
|     bool wasVisibleAtPreviousComposition =
 | |
|         compositionOpportunityId == mLastChooseImageIndexComposition.Next();
 | |
| 
 | |
|     bool frameChanged =
 | |
|         UpdateCompositedFrame(imageIndex, wasVisibleAtPreviousComposition);
 | |
|     UpdateBias(imageIndex, frameChanged);
 | |
| 
 | |
|     mLastChooseImageIndexComposition = compositionOpportunityId;
 | |
| 
 | |
|     return imageIndex;
 | |
|   }
 | |
| 
 | |
|   // We've been called before during this composition, or we're not in a
 | |
|   // composition. Just return the last image we picked (if it's one of the
 | |
|   // current images).
 | |
|   for (uint32_t i = 0; i < mImages.Length(); ++i) {
 | |
|     if (mImages[i].mFrameID == mLastFrameID &&
 | |
|         mImages[i].mProducerID == mLastProducerID) {
 | |
|       return i;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| const ImageComposite::TimedImage* ImageComposite::ChooseImage() {
 | |
|   int index = ChooseImageIndex();
 | |
|   return index >= 0 ? &mImages[index] : nullptr;
 | |
| }
 | |
| 
 | |
| void ImageComposite::RemoveImagesWithTextureHost(TextureHost* aTexture) {
 | |
|   for (int32_t i = mImages.Length() - 1; i >= 0; --i) {
 | |
|     if (mImages[i].mTextureHost == aTexture) {
 | |
|       aTexture->UnbindTextureSource();
 | |
|       mImages.RemoveElementAt(i);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void ImageComposite::ClearImages() { mImages.Clear(); }
 | |
| 
 | |
| void ImageComposite::SetImages(nsTArray<TimedImage>&& aNewImages) {
 | |
|   if (!aNewImages.IsEmpty()) {
 | |
|     DetectTimeStampJitter(&aNewImages[0]);
 | |
| 
 | |
|     // Frames older than the first frame in aNewImages that we haven't shown yet
 | |
|     // will never be shown.
 | |
|     CountSkippedFrames(&aNewImages[0]);
 | |
| 
 | |
|     if (profiler_thread_is_being_profiled_for_markers()) {
 | |
|       int len = aNewImages.Length();
 | |
|       const auto& first = aNewImages[0];
 | |
|       const auto& last = aNewImages.LastElement();
 | |
|       nsPrintfCString str("%d %s, frameID %" PRId32 " (prod %" PRId32
 | |
|                           ") to frameID %" PRId32 " (prod %" PRId32 ")",
 | |
|                           len, len == 1 ? "image" : "images", first.mFrameID,
 | |
|                           first.mProducerID, last.mFrameID, last.mProducerID);
 | |
|       PROFILER_MARKER_TEXT("ImageComposite::SetImages", GRAPHICS, {}, str);
 | |
|     }
 | |
|   }
 | |
|   mImages = std::move(aNewImages);
 | |
| }
 | |
| 
 | |
| // Returns whether the frame changed.
 | |
| bool ImageComposite::UpdateCompositedFrame(
 | |
|     int aImageIndex, bool aWasVisibleAtPreviousComposition) {
 | |
|   MOZ_RELEASE_ASSERT(aImageIndex >= 0);
 | |
|   MOZ_RELEASE_ASSERT(aImageIndex < static_cast<int>(mImages.Length()));
 | |
|   const TimedImage& image = mImages[aImageIndex];
 | |
| 
 | |
|   auto compositionOpportunityId = GetCompositionOpportunityId();
 | |
|   TimeStamp compositionTime = GetCompositionTime();
 | |
|   MOZ_RELEASE_ASSERT(compositionTime,
 | |
|                      "Should only be called during a composition");
 | |
| 
 | |
|   nsCString descr;
 | |
|   if (profiler_thread_is_being_profiled_for_markers()) {
 | |
|     nsCString relativeTimeString;
 | |
|     if (image.mTimeStamp) {
 | |
|       relativeTimeString.AppendPrintf(
 | |
|           " [relative timestamp %.1lfms]",
 | |
|           (image.mTimeStamp - compositionTime).ToMilliseconds());
 | |
|     }
 | |
|     int remainingImages = mImages.Length() - 1 - aImageIndex;
 | |
|     static const char* kBiasStrings[] = {"NONE", "NEGATIVE", "POSITIVE"};
 | |
|     descr.AppendPrintf(
 | |
|         "frameID %" PRId32 " (producerID %" PRId32 ") [composite %" PRIu64
 | |
|         "] [bias %s] [%d remaining %s]%s",
 | |
|         image.mFrameID, image.mProducerID, compositionOpportunityId.mId,
 | |
|         kBiasStrings[mBias], remainingImages,
 | |
|         remainingImages == 1 ? "image" : "images", relativeTimeString.get());
 | |
|     if (mLastProducerID != image.mProducerID) {
 | |
|       descr.AppendPrintf(", previous producerID: %" PRId32, mLastProducerID);
 | |
|     } else if (mLastFrameID != image.mFrameID) {
 | |
|       descr.AppendPrintf(", previous frameID: %" PRId32, mLastFrameID);
 | |
|     } else {
 | |
|       descr.AppendLiteral(", no change");
 | |
|     }
 | |
|   }
 | |
|   PROFILER_MARKER_TEXT("UpdateCompositedFrame", GRAPHICS, {}, descr);
 | |
| 
 | |
|   if (mLastFrameID == image.mFrameID && mLastProducerID == image.mProducerID) {
 | |
|     // The frame didn't change.
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   CountSkippedFrames(&image);
 | |
| 
 | |
|   int32_t dropped = mSkippedFramesSinceLastComposite;
 | |
|   mSkippedFramesSinceLastComposite = 0;
 | |
| 
 | |
|   if (!aWasVisibleAtPreviousComposition) {
 | |
|     // This video was not part of the on-screen scene during the previous
 | |
|     // composition opportunity, for example it may have been scrolled off-screen
 | |
|     // or in a background tab, or compositing might have been paused.
 | |
|     // Ignore any skipped frames and don't count them as dropped.
 | |
|     dropped = 0;
 | |
|   }
 | |
| 
 | |
|   if (dropped > 0) {
 | |
|     mDroppedFrames += dropped;
 | |
|     if (profiler_thread_is_being_profiled_for_markers()) {
 | |
|       const char* frameOrFrames = dropped == 1 ? "frame" : "frames";
 | |
|       nsPrintfCString text("%" PRId32 " %s dropped: %" PRId32 " -> %" PRId32
 | |
|                            " (producer %" PRId32 ")",
 | |
|                            dropped, frameOrFrames, mLastFrameID, image.mFrameID,
 | |
|                            mLastProducerID);
 | |
|       PROFILER_MARKER_TEXT("Video frames dropped", GRAPHICS, {}, text);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mLastFrameID = image.mFrameID;
 | |
|   mLastProducerID = image.mProducerID;
 | |
|   mLastFrameUpdateComposition = compositionOpportunityId;
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void ImageComposite::OnFinishRendering(int aImageIndex,
 | |
|                                        const TimedImage* aImage,
 | |
|                                        base::ProcessId aProcessId,
 | |
|                                        const CompositableHandle& aHandle) {
 | |
|   if (mLastFrameUpdateComposition != GetCompositionOpportunityId()) {
 | |
|     // The frame did not change in this composition.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (aHandle) {
 | |
|     ImageCompositeNotificationInfo info;
 | |
|     info.mImageBridgeProcessId = aProcessId;
 | |
|     info.mNotification = ImageCompositeNotification(
 | |
|         aHandle, aImage->mTimeStamp, GetCompositionTime(), mLastFrameID,
 | |
|         mLastProducerID);
 | |
|     AppendImageCompositeNotification(info);
 | |
|   }
 | |
| }
 | |
| 
 | |
| const ImageComposite::TimedImage* ImageComposite::GetImage(
 | |
|     size_t aIndex) const {
 | |
|   if (aIndex >= mImages.Length()) {
 | |
|     return nullptr;
 | |
|   }
 | |
|   return &mImages[aIndex];
 | |
| }
 | |
| 
 | |
| void ImageComposite::CountSkippedFrames(const TimedImage* aImage) {
 | |
|   if (aImage->mProducerID != mLastProducerID) {
 | |
|     // Switched producers.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (mImages.IsEmpty() || aImage->mFrameID <= mLastFrameID + 1) {
 | |
|     // No frames were skipped.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   uint32_t targetFrameRate = gfxPlatform::TargetFrameRate();
 | |
|   if (targetFrameRate == 0) {
 | |
|     // Can't know whether we could have reasonably displayed all video frames.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   double targetFrameDurationMS = 1000.0 / targetFrameRate;
 | |
| 
 | |
|   // Count how many images in mImages were skipped between mLastFrameID and
 | |
|   // aImage.mFrameID. Only count frames for which we can estimate a duration by
 | |
|   // looking at the next frame's timestamp, and only if the video frame rate is
 | |
|   // no faster than the target frame rate.
 | |
|   int32_t skipped = 0;
 | |
|   for (size_t i = 0; i + 1 < mImages.Length(); i++) {
 | |
|     const auto& img = mImages[i];
 | |
|     if (img.mProducerID != aImage->mProducerID ||
 | |
|         img.mFrameID <= mLastFrameID || img.mFrameID >= aImage->mFrameID) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     // We skipped img! Estimate img's time duration.
 | |
|     const auto& next = mImages[i + 1];
 | |
|     if (next.mProducerID != aImage->mProducerID) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     MOZ_ASSERT(next.mFrameID > img.mFrameID);
 | |
|     TimeDuration duration = next.mTimeStamp - img.mTimeStamp;
 | |
|     if (floor(duration.ToMilliseconds()) >= floor(targetFrameDurationMS)) {
 | |
|       // Count the frame.
 | |
|       skipped++;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mSkippedFramesSinceLastComposite += skipped;
 | |
| }
 | |
| 
 | |
| void ImageComposite::DetectTimeStampJitter(const TimedImage* aNewImage) {
 | |
|   if (!profiler_thread_is_being_profiled_for_markers() ||
 | |
|       aNewImage->mTimeStamp.IsNull()) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Find aNewImage in mImages and compute its timestamp delta, if found.
 | |
|   // Ideally, a given video frame should never change its timestamp (jitter
 | |
|   // should be zero). However, we re-adjust video frame timestamps based on the
 | |
|   // audio clock. If the audio clock drifts compared to the system clock, or if
 | |
|   // there are bugs or inaccuracies in the computation of these timestamps,
 | |
|   // jitter will be non-zero.
 | |
|   Maybe<TimeDuration> jitter;
 | |
|   for (const auto& img : mImages) {
 | |
|     if (img.mProducerID == aNewImage->mProducerID &&
 | |
|         img.mFrameID == aNewImage->mFrameID) {
 | |
|       if (!img.mTimeStamp.IsNull()) {
 | |
|         jitter = Some(aNewImage->mTimeStamp - img.mTimeStamp);
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   if (jitter) {
 | |
|     nsPrintfCString text("%.2lfms", jitter->ToMilliseconds());
 | |
|     PROFILER_MARKER_TEXT("VideoFrameTimeStampJitter", GRAPHICS, {}, text);
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // namespace layers
 | |
| }  // namespace mozilla
 |