forked from mirrors/gecko-dev
We can't rely on the FrameID continuity to determine if a frame has been dropped due to timing or not. The reason being that the VideoSink will not send to the compositor frames it knows as being late already (causing a discontinuity in the frames IDs), and count them as being dropped. If we were to look at discontinuity on the compositor we would account for those frames twice. FramesID will also increase non-linearly if a frame isn't painted because it's not visible (either out of the visible tree or in a hidden tab). What we can measure however, is when a frame should have been painted but didn't because it was too late by looking at the value returned by ImageComposite::ChooseImageIndex() or when a new set of images is being received by the ImageComposite. Any images found in the earlier array but never returned must have been dropped due to timing. Looking at the index continuity greatly simplify the logic as we no longer need to worry if a video is hidden or not, or be part of a layer that is itself hidden as neither SetImages will be called then, nor ChooseImage For now, we only account for those frames dropped, and do not report them yet. Differential Revision: https://phabricator.services.mozilla.com/D2176
228 lines
7.1 KiB
C++
228 lines
7.1 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"
|
|
|
|
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
|
|
mDroppedFrames += result - mLastChosenImageIndex - 1;
|
|
}
|
|
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++) {
|
|
dropped++;
|
|
}
|
|
break;
|
|
}
|
|
i++;
|
|
j++;
|
|
}
|
|
if (dropped > 0) {
|
|
mDroppedFrames += dropped;
|
|
}
|
|
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];
|
|
}
|
|
|
|
} // namespace layers
|
|
} // namespace mozilla
|