gecko-dev/gfx/layers/composite/ImageComposite.cpp
alwu d832456285 Bug 1579127 - only count the frame dropping due to system overload. r=mattwoodrow
When user adjusts the video playback rate, which might cause we sending images in a speed that is faster than the speend we composite images.

In this situation, the frame dropping actually won't cause any visual defect and we also don't want to report this frame dropping to user, because it's not caused by system overloading, it's just our compositor doesn't support compositing images in such a high rate.

Therefore, we should check if the dropped images are caused by system overload or high update rate, and only report the former to user.

Differential Revision: https://phabricator.services.mozilla.com/D46236

--HG--
extra : moz-landing-system : lando
2019-10-08 08:06:13 +00:00

235 lines
8.2 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 "gfxPlatform.h"
namespace mozilla {
using namespace gfx;
namespace layers {
/* static */ const float ImageComposite::BIAS_TIME_MS = 1.0f;
ImageComposite::ImageComposite()
: mLastFrameID(-1),
mLastProducerID(-1),
mBias(BIAS_NONE),
mDroppedFrames(0),
mLastChosenImageIndex(0) {}
ImageComposite::~ImageComposite() {}
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) {
MOZ_ASSERT(aImageIndex < ImagesCount());
TimeStamp compositionTime = GetCompositionTime();
TimeStamp compositedImageTime = mImages[aImageIndex].mTimeStamp;
TimeStamp nextImageTime = aImageIndex + 1 < ImagesCount()
? mImages[aImageIndex + 1].mTimeStamp
: TimeStamp();
if (compositedImageTime.IsNull()) {
mBias = ImageComposite::BIAS_NONE;
return;
}
TimeDuration threshold = TimeDuration::FromMilliseconds(1.0);
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.
// 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_POSITIVE;
return;
}
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 now = GetCompositionTime();
if (now.IsNull()) {
// Not in a composition, so just return the last image we composited
// (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 -1;
}
uint32_t result = mLastChosenImageIndex;
while (result + 1 < mImages.Length() &&
GetBiasedTime(mImages[result + 1].mTimeStamp) <= now) {
++result;
}
if (result - mLastChosenImageIndex > 1) {
// We're not returning the same image as the last call to ChooseImageIndex
// or the immediately next one. We can assume that the frames not returned
// have been dropped as they were too late to be displayed
for (size_t idx = mLastChosenImageIndex; idx <= result; idx++) {
if (IsImagesUpdateRateFasterThanCompositedRate(mImages[result],
mImages[idx])) {
continue;
}
mDroppedFrames++;
PROFILER_ADD_MARKER("Video frames dropped", GRAPHICS);
}
}
mLastChosenImageIndex = result;
return result;
}
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();
mLastChosenImageIndex = 0;
}
uint32_t ImageComposite::ScanForLastFrameIndex(
const nsTArray<TimedImage>& aNewImages) {
if (mImages.IsEmpty()) {
return 0;
}
uint32_t i = mLastChosenImageIndex;
uint32_t newIndex = 0;
uint32_t dropped = 0;
// See if the new array of images have any images in common with the
// previous list that we haven't played yet.
uint32_t j = 0;
while (i < mImages.Length() && j < aNewImages.Length()) {
if (mImages[i].mProducerID != aNewImages[j].mProducerID) {
// This is new content, can stop.
newIndex = j;
break;
}
int32_t oldFrameID = mImages[i].mFrameID;
int32_t newFrameID = aNewImages[j].mFrameID;
if (oldFrameID > newFrameID) {
// This is an image we have already returned, we don't need to present
// it again and can start from this index next time.
newIndex = ++j;
continue;
}
if (oldFrameID < mLastFrameID) {
// we have already returned that frame previously, ignore.
i++;
continue;
}
if (oldFrameID < newFrameID) {
// This is a new image, all images prior the new one and not yet
// rendered can be considered as dropped. Those images have a FrameID
// inferior to the new image.
for (++i; i < mImages.Length() && mImages[i].mFrameID < newFrameID &&
mImages[i].mProducerID == aNewImages[j].mProducerID;
i++) {
if (IsImagesUpdateRateFasterThanCompositedRate(aNewImages[j],
mImages[i])) {
continue;
}
dropped++;
}
break;
}
i++;
j++;
}
if (dropped > 0) {
mDroppedFrames += dropped;
PROFILER_ADD_MARKER("Video frames dropped", GRAPHICS);
}
if (newIndex >= aNewImages.Length()) {
// Somehow none of those images should be rendered (can this happen?)
// We will always return the last one for now.
newIndex = aNewImages.Length() - 1;
}
return newIndex;
}
void ImageComposite::SetImages(nsTArray<TimedImage>&& aNewImages) {
mLastChosenImageIndex = ScanForLastFrameIndex(aNewImages);
mImages = std::move(aNewImages);
}
const ImageComposite::TimedImage* ImageComposite::GetImage(
size_t aIndex) const {
if (aIndex >= mImages.Length()) {
return nullptr;
}
return &mImages[aIndex];
}
bool ImageComposite::IsImagesUpdateRateFasterThanCompositedRate(
const TimedImage& aNewImage, const TimedImage& aOldImage) const {
MOZ_ASSERT(aNewImage.mFrameID >= aOldImage.mFrameID);
const uint32_t compositedRate = gfxPlatform::TargetFrameRate();
if (compositedRate == 0) {
return true;
}
const double compositedInterval = 1.0 / compositedRate;
return aNewImage.mTimeStamp - aOldImage.mTimeStamp <
TimeDuration::FromSeconds(compositedInterval);
}
} // namespace layers
} // namespace mozilla