tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

ImageComposite.cpp (14330B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "ImageComposite.h"
      8 
      9 #include <inttypes.h>
     10 
     11 #include "mozilla/ProfilerMarkers.h"
     12 #include "gfxPlatform.h"
     13 #include "nsPrintfCString.h"
     14 
     15 namespace mozilla {
     16 
     17 using namespace gfx;
     18 
     19 namespace layers {
     20 
     21 /* static */ const float ImageComposite::BIAS_TIME_MS = 1.0f;
     22 
     23 ImageComposite::ImageComposite() = default;
     24 
     25 ImageComposite::~ImageComposite() = default;
     26 
     27 TimeStamp ImageComposite::GetBiasedTime(const TimeStamp& aInput) const {
     28  switch (mBias) {
     29    case ImageComposite::BIAS_NEGATIVE:
     30      return aInput - TimeDuration::FromMilliseconds(BIAS_TIME_MS);
     31    case ImageComposite::BIAS_POSITIVE:
     32      return aInput + TimeDuration::FromMilliseconds(BIAS_TIME_MS);
     33    default:
     34      return aInput;
     35  }
     36 }
     37 
     38 void ImageComposite::UpdateBias(size_t aImageIndex, bool aFrameChanged) {
     39  MOZ_ASSERT(aImageIndex < ImagesCount());
     40 
     41  TimeStamp compositionTime = GetCompositionTime();
     42  TimeStamp compositedImageTime = mImages[aImageIndex].mTimeStamp;
     43  TimeStamp nextImageTime = aImageIndex + 1 < ImagesCount()
     44                                ? mImages[aImageIndex + 1].mTimeStamp
     45                                : TimeStamp();
     46 
     47  if (profiler_thread_is_being_profiled_for_markers() && compositedImageTime &&
     48      nextImageTime) {
     49    TimeDuration offsetCurrent = compositedImageTime - compositionTime;
     50    TimeDuration offsetNext = nextImageTime - compositionTime;
     51    nsPrintfCString str("current %.2lfms, next %.2lfms",
     52                        offsetCurrent.ToMilliseconds(),
     53                        offsetNext.ToMilliseconds());
     54    PROFILER_MARKER_TEXT("Video frame offsets", GRAPHICS, {}, str);
     55  }
     56 
     57  if (compositedImageTime.IsNull()) {
     58    mBias = ImageComposite::BIAS_NONE;
     59    return;
     60  }
     61  TimeDuration threshold = TimeDuration::FromMilliseconds(1.5);
     62  if (compositionTime - compositedImageTime < threshold &&
     63      compositionTime - compositedImageTime > -threshold) {
     64    // The chosen frame's time is very close to the composition time (probably
     65    // just before the current composition time, but due to previously set
     66    // negative bias, it could be just after the current composition time too).
     67    // If the inter-frame time is almost exactly equal to (a multiple of)
     68    // the inter-composition time, then we're in a dangerous situation because
     69    // jitter might cause frames to fall one side or the other of the
     70    // composition times, causing many frames to be skipped or duplicated.
     71    // Try to prevent that by adding a negative bias to the frame times during
     72    // the next composite; that should ensure the next frame's time is treated
     73    // as falling just before a composite time.
     74    mBias = ImageComposite::BIAS_NEGATIVE;
     75    return;
     76  }
     77  if (!nextImageTime.IsNull() && nextImageTime - compositionTime < threshold &&
     78      nextImageTime - compositionTime > -threshold) {
     79    // The next frame's time is very close to our composition time (probably
     80    // just after the current composition time, but due to previously set
     81    // positive bias, it could be just before the current composition time too).
     82    // We're in a dangerous situation because jitter might cause frames to
     83    // fall one side or the other of the composition times, causing many frames
     84    // to be skipped or duplicated.
     85    // Specifically, the next composite is at risk of picking the "next + 1"
     86    // frame rather than the "next" frame, which would cause the "next" frame to
     87    // be skipped. Try to prevent that by adding a positive bias to the frame
     88    // times during the next composite; if the inter-frame time is almost
     89    // exactly equal to the inter-composition time, that should ensure that the
     90    // next + 1 frame falls just *after* the next composition time, and the next
     91    // composite should then pick the next frame rather than the next + 1 frame.
     92    mBias = ImageComposite::BIAS_POSITIVE;
     93    return;
     94  }
     95  if (aFrameChanged) {
     96    // The current and next video frames are a sufficient distance from the
     97    // composition time and we can reliably pick the right frame without bias.
     98    // Reset the bias.
     99    // We only do this when the frame changed. Otherwise, when playing a 30fps
    100    // video on a 60fps display, we'd keep resetting the bias during the "middle
    101    // frames".
    102    mBias = ImageComposite::BIAS_NONE;
    103  }
    104 }
    105 
    106 int ImageComposite::ChooseImageIndex() {
    107  // ChooseImageIndex is called for all images in the layer when it is visible.
    108  // Change to this behaviour would break dropped frames counting calculation:
    109  // We rely on this assumption to determine if during successive runs an
    110  // image is returned that isn't the one following immediately the previous one
    111  if (mImages.IsEmpty()) {
    112    return -1;
    113  }
    114 
    115  TimeStamp compositionTime = GetCompositionTime();
    116  auto compositionOpportunityId = GetCompositionOpportunityId();
    117  if (compositionTime &&
    118      compositionOpportunityId != mLastChooseImageIndexComposition) {
    119    // We are inside a composition, in the first call to ChooseImageIndex during
    120    // this composition.
    121    // Find the newest frame whose biased timestamp is at or before
    122    // `compositionTime`.
    123    uint32_t imageIndex = 0;
    124    while (imageIndex + 1 < mImages.Length() &&
    125           mImages[imageIndex + 1].mTextureHost->IsValid() &&
    126           GetBiasedTime(mImages[imageIndex + 1].mTimeStamp) <=
    127               compositionTime) {
    128      ++imageIndex;
    129    }
    130 
    131    if (!mImages[imageIndex].mTextureHost->IsValid()) {
    132      // Still not ready to be shown.
    133      return -1;
    134    }
    135 
    136    bool wasVisibleAtPreviousComposition =
    137        compositionOpportunityId == mLastChooseImageIndexComposition.Next();
    138 
    139    bool frameChanged =
    140        UpdateCompositedFrame(imageIndex, wasVisibleAtPreviousComposition);
    141    UpdateBias(imageIndex, frameChanged);
    142 
    143    mLastChooseImageIndexComposition = compositionOpportunityId;
    144 
    145    return imageIndex;
    146  }
    147 
    148  // We've been called before during this composition, or we're not in a
    149  // composition. Just return the last image we picked (if it's one of the
    150  // current images).
    151  for (uint32_t i = 0; i < mImages.Length(); ++i) {
    152    if (mImages[i].mFrameID == mLastFrameID &&
    153        mImages[i].mProducerID == mLastProducerID) {
    154      return i;
    155    }
    156  }
    157 
    158  return 0;
    159 }
    160 
    161 const ImageComposite::TimedImage* ImageComposite::ChooseImage() {
    162  int index = ChooseImageIndex();
    163  return index >= 0 ? &mImages[index] : nullptr;
    164 }
    165 
    166 void ImageComposite::RemoveImagesWithTextureHost(TextureHost* aTexture) {
    167  for (int32_t i = mImages.Length() - 1; i >= 0; --i) {
    168    if (mImages[i].mTextureHost == aTexture) {
    169      mImages.RemoveElementAt(i);
    170    }
    171  }
    172 }
    173 
    174 void ImageComposite::ClearImages() { mImages.Clear(); }
    175 
    176 void ImageComposite::SetImages(nsTArray<TimedImage>&& aNewImages) {
    177  if (!aNewImages.IsEmpty()) {
    178    DetectTimeStampJitter(&aNewImages[0]);
    179 
    180    // Frames older than the first frame in aNewImages that we haven't shown yet
    181    // will never be shown.
    182    CountSkippedFrames(&aNewImages[0]);
    183 
    184    if (profiler_thread_is_being_profiled_for_markers()) {
    185      int len = aNewImages.Length();
    186      const auto& first = aNewImages[0];
    187      const auto& last = aNewImages.LastElement();
    188      nsPrintfCString str("%d %s, frameID %" PRId32 " (prod %" PRId32
    189                          ") to frameID %" PRId32 " (prod %" PRId32 ")",
    190                          len, len == 1 ? "image" : "images", first.mFrameID,
    191                          first.mProducerID, last.mFrameID, last.mProducerID);
    192      PROFILER_MARKER_TEXT("ImageComposite::SetImages", GRAPHICS, {}, str);
    193    }
    194  }
    195  mImages = std::move(aNewImages);
    196 }
    197 
    198 // Returns whether the frame changed.
    199 bool ImageComposite::UpdateCompositedFrame(
    200    int aImageIndex, bool aWasVisibleAtPreviousComposition) {
    201  MOZ_RELEASE_ASSERT(aImageIndex >= 0);
    202  MOZ_RELEASE_ASSERT(aImageIndex < static_cast<int>(mImages.Length()));
    203  const TimedImage& image = mImages[aImageIndex];
    204 
    205  auto compositionOpportunityId = GetCompositionOpportunityId();
    206  TimeStamp compositionTime = GetCompositionTime();
    207  MOZ_RELEASE_ASSERT(compositionTime,
    208                     "Should only be called during a composition");
    209 
    210  nsCString descr;
    211  if (profiler_thread_is_being_profiled_for_markers()) {
    212    nsCString relativeTimeString;
    213    if (image.mTimeStamp) {
    214      relativeTimeString.AppendPrintf(
    215          " [relative timestamp %.1lfms]",
    216          (image.mTimeStamp - compositionTime).ToMilliseconds());
    217    }
    218    int remainingImages = mImages.Length() - 1 - aImageIndex;
    219    static const char* kBiasStrings[] = {"NONE", "NEGATIVE", "POSITIVE"};
    220    descr.AppendPrintf(
    221        "frameID %" PRId32 " (producerID %" PRId32 ") [composite %" PRIu64
    222        "] [bias %s] [%d remaining %s]%s",
    223        image.mFrameID, image.mProducerID, compositionOpportunityId.mId,
    224        kBiasStrings[mBias], remainingImages,
    225        remainingImages == 1 ? "image" : "images", relativeTimeString.get());
    226    if (mLastProducerID != image.mProducerID) {
    227      descr.AppendPrintf(", previous producerID: %" PRId32, mLastProducerID);
    228    } else if (mLastFrameID != image.mFrameID) {
    229      descr.AppendPrintf(", previous frameID: %" PRId32, mLastFrameID);
    230    } else {
    231      descr.AppendLiteral(", no change");
    232    }
    233  }
    234  PROFILER_MARKER_TEXT("UpdateCompositedFrame", GRAPHICS, {}, descr);
    235 
    236  if (mLastFrameID == image.mFrameID && mLastProducerID == image.mProducerID) {
    237    // The frame didn't change.
    238    return false;
    239  }
    240 
    241  CountSkippedFrames(&image);
    242 
    243  int32_t dropped = mSkippedFramesSinceLastComposite;
    244  mSkippedFramesSinceLastComposite = 0;
    245 
    246  if (!aWasVisibleAtPreviousComposition) {
    247    // This video was not part of the on-screen scene during the previous
    248    // composition opportunity, for example it may have been scrolled off-screen
    249    // or in a background tab, or compositing might have been paused.
    250    // Ignore any skipped frames and don't count them as dropped.
    251    dropped = 0;
    252  }
    253 
    254  if (dropped > 0) {
    255    mDroppedFrames += dropped;
    256    if (profiler_thread_is_being_profiled_for_markers()) {
    257      const char* frameOrFrames = dropped == 1 ? "frame" : "frames";
    258      nsPrintfCString text("%" PRId32 " %s dropped: %" PRId32 " -> %" PRId32
    259                           " (producer %" PRId32 ")",
    260                           dropped, frameOrFrames, mLastFrameID, image.mFrameID,
    261                           mLastProducerID);
    262      PROFILER_MARKER_TEXT("Video frames dropped", GRAPHICS, {}, text);
    263    }
    264  }
    265 
    266  mLastFrameID = image.mFrameID;
    267  mLastProducerID = image.mProducerID;
    268  mLastFrameUpdateComposition = compositionOpportunityId;
    269 
    270  return true;
    271 }
    272 
    273 void ImageComposite::OnFinishRendering(int aImageIndex,
    274                                       const TimedImage* aImage,
    275                                       base::ProcessId aProcessId,
    276                                       const CompositableHandle& aHandle) {
    277  if (mLastFrameUpdateComposition != GetCompositionOpportunityId()) {
    278    // The frame did not change in this composition.
    279    return;
    280  }
    281 
    282  if (aHandle) {
    283    ImageCompositeNotificationInfo info;
    284    info.mImageBridgeProcessId = aProcessId;
    285    info.mNotification = ImageCompositeNotification(
    286        aHandle, aImage->mTimeStamp, GetCompositionTime(), mLastFrameID,
    287        mLastProducerID);
    288    AppendImageCompositeNotification(info);
    289  }
    290 }
    291 
    292 const ImageComposite::TimedImage* ImageComposite::GetImage(
    293    size_t aIndex) const {
    294  if (aIndex >= mImages.Length()) {
    295    return nullptr;
    296  }
    297  return &mImages[aIndex];
    298 }
    299 
    300 void ImageComposite::CountSkippedFrames(const TimedImage* aImage) {
    301  if (aImage->mProducerID != mLastProducerID) {
    302    // Switched producers.
    303    return;
    304  }
    305 
    306  if (mImages.IsEmpty() || aImage->mFrameID <= mLastFrameID + 1) {
    307    // No frames were skipped.
    308    return;
    309  }
    310 
    311  uint32_t targetFrameRate = gfxPlatform::TargetFrameRate();
    312  if (targetFrameRate == 0) {
    313    // Can't know whether we could have reasonably displayed all video frames.
    314    return;
    315  }
    316 
    317  double targetFrameDurationMS = 1000.0 / targetFrameRate;
    318 
    319  // Count how many images in mImages were skipped between mLastFrameID and
    320  // aImage.mFrameID. Only count frames for which we can estimate a duration by
    321  // looking at the next frame's timestamp, and only if the video frame rate is
    322  // no faster than the target frame rate.
    323  int32_t skipped = 0;
    324  for (size_t i = 0; i + 1 < mImages.Length(); i++) {
    325    const auto& img = mImages[i];
    326    if (img.mProducerID != aImage->mProducerID ||
    327        img.mFrameID <= mLastFrameID || img.mFrameID >= aImage->mFrameID) {
    328      continue;
    329    }
    330 
    331    // We skipped img! Estimate img's time duration.
    332    const auto& next = mImages[i + 1];
    333    if (next.mProducerID != aImage->mProducerID) {
    334      continue;
    335    }
    336 
    337    MOZ_ASSERT(next.mFrameID > img.mFrameID);
    338    TimeDuration duration = next.mTimeStamp - img.mTimeStamp;
    339    if (floor(duration.ToMilliseconds()) >= floor(targetFrameDurationMS)) {
    340      // Count the frame.
    341      skipped++;
    342    }
    343  }
    344 
    345  mSkippedFramesSinceLastComposite += skipped;
    346 }
    347 
    348 void ImageComposite::DetectTimeStampJitter(const TimedImage* aNewImage) {
    349  if (!profiler_thread_is_being_profiled_for_markers() ||
    350      aNewImage->mTimeStamp.IsNull()) {
    351    return;
    352  }
    353 
    354  // Find aNewImage in mImages and compute its timestamp delta, if found.
    355  // Ideally, a given video frame should never change its timestamp (jitter
    356  // should be zero). However, we re-adjust video frame timestamps based on the
    357  // audio clock. If the audio clock drifts compared to the system clock, or if
    358  // there are bugs or inaccuracies in the computation of these timestamps,
    359  // jitter will be non-zero.
    360  Maybe<TimeDuration> jitter;
    361  for (const auto& img : mImages) {
    362    if (img.mProducerID == aNewImage->mProducerID &&
    363        img.mFrameID == aNewImage->mFrameID) {
    364      if (!img.mTimeStamp.IsNull()) {
    365        jitter = Some(aNewImage->mTimeStamp - img.mTimeStamp);
    366      }
    367      break;
    368    }
    369  }
    370  if (jitter) {
    371    nsPrintfCString text("%.2lfms", jitter->ToMilliseconds());
    372    PROFILER_MARKER_TEXT("VideoFrameTimeStampJitter", GRAPHICS, {}, text);
    373  }
    374 }
    375 
    376 }  // namespace layers
    377 }  // namespace mozilla