mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 12:15:51 +00:00
452 lines
13 KiB
C++
452 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
* 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 <stddef.h> // for size_t
|
|
#include "Units.h" // for ScreenIntRect
|
|
#include "gfxRect.h" // for gfxRect
|
|
#include "gfxPrefs.h" // for gfxPrefs
|
|
#include "mozilla/gfx/Point.h" // for IntSize, Point
|
|
#include "mozilla/gfx/Rect.h" // for Rect
|
|
#include "mozilla/gfx/Types.h" // for Color, SurfaceFormat
|
|
#include "mozilla/layers/Compositor.h" // for Compositor
|
|
#include "mozilla/layers/CompositorTypes.h"
|
|
#include "mozilla/layers/Effects.h" // for Effect, EffectChain, etc
|
|
#include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration
|
|
#include "nsPoint.h" // for nsIntPoint
|
|
#include "nsRect.h" // for nsIntRect
|
|
#include "nsIFile.h" // for nsIFile
|
|
#include "nsDirectoryServiceDefs.h" // for NS_OS_TMP_DIR
|
|
#include "prprf.h" // for PR_snprintf
|
|
#include "FPSCounter.h"
|
|
|
|
namespace mozilla {
|
|
namespace layers {
|
|
|
|
using namespace mozilla::gfx;
|
|
using namespace mozilla::gl;
|
|
|
|
FPSCounter::FPSCounter(const char* aName)
|
|
: mWriteIndex(0)
|
|
, mFPSName(aName)
|
|
{
|
|
Init();
|
|
}
|
|
|
|
FPSCounter::~FPSCounter() { }
|
|
|
|
void
|
|
FPSCounter::Init()
|
|
{
|
|
for (int i = 0; i < kMaxFrames; i++) {
|
|
mFrameTimestamps.AppendElement(TimeStamp());
|
|
}
|
|
mLastInterval = TimeStamp::Now();
|
|
}
|
|
|
|
// Returns true if we captured a full interval of data
|
|
bool
|
|
FPSCounter::CapturedFullInterval(TimeStamp aTimestamp) {
|
|
TimeDuration duration = aTimestamp - mLastInterval;
|
|
return duration.ToSecondsSigDigits() >= kFpsDumpInterval;
|
|
}
|
|
|
|
void
|
|
FPSCounter::AddFrame(TimeStamp aTimestamp) {
|
|
NS_ASSERTION(mWriteIndex < kMaxFrames, "We probably have a bug with the circular buffer");
|
|
NS_ASSERTION(mWriteIndex >= 0, "Circular Buffer index should never be negative");
|
|
|
|
int index = mWriteIndex++;
|
|
if (mWriteIndex == kMaxFrames) {
|
|
mWriteIndex = 0;
|
|
}
|
|
|
|
mFrameTimestamps[index] = aTimestamp;
|
|
|
|
if (CapturedFullInterval(aTimestamp)) {
|
|
PrintFPS();
|
|
WriteFrameTimeStamps();
|
|
mLastInterval = aTimestamp;
|
|
}
|
|
}
|
|
|
|
double
|
|
FPSCounter::AddFrameAndGetFps(TimeStamp aTimestamp) {
|
|
AddFrame(aTimestamp);
|
|
return GetFPS(aTimestamp);
|
|
}
|
|
|
|
int
|
|
FPSCounter::GetLatestReadIndex()
|
|
{
|
|
if (mWriteIndex == 0) {
|
|
return kMaxFrames - 1;
|
|
}
|
|
|
|
return mWriteIndex - 1;
|
|
}
|
|
|
|
TimeStamp
|
|
FPSCounter::GetLatestTimeStamp()
|
|
{
|
|
TimeStamp timestamp = mFrameTimestamps[GetLatestReadIndex()];
|
|
MOZ_ASSERT(!timestamp.IsNull(), "Cannot use null timestamps");
|
|
return timestamp;
|
|
}
|
|
|
|
// Returns true if we iterated over a full interval of data
|
|
bool
|
|
FPSCounter::IteratedFullInterval(TimeStamp aTimestamp, double aDuration) {
|
|
MOZ_ASSERT(mIteratorIndex >= 0, "Cannot be negative");
|
|
MOZ_ASSERT(mIteratorIndex < kMaxFrames, "Iterator index cannot be greater than kMaxFrames");
|
|
|
|
TimeStamp currentStamp = mFrameTimestamps[mIteratorIndex];
|
|
TimeDuration duration = aTimestamp - currentStamp;
|
|
return duration.ToSecondsSigDigits() >= aDuration;
|
|
}
|
|
|
|
void
|
|
FPSCounter::ResetReverseIterator()
|
|
{
|
|
mIteratorIndex = GetLatestReadIndex();
|
|
}
|
|
|
|
/***
|
|
* Returns true if we have another timestamp that is valid and
|
|
* is within the given duration that we're interested in.
|
|
* Duration is in seconds
|
|
*/
|
|
bool FPSCounter::HasNext(TimeStamp aTimestamp, double aDuration)
|
|
{
|
|
// Order of evaluation here has to stay the same
|
|
// otherwise IteratedFullInterval reads from mFrameTimestamps which cannot
|
|
// be null
|
|
return (mIteratorIndex != mWriteIndex) // Didn't loop around the buffer
|
|
&& !mFrameTimestamps[mIteratorIndex].IsNull() // valid data
|
|
&& !IteratedFullInterval(aTimestamp, aDuration);
|
|
}
|
|
|
|
TimeStamp
|
|
FPSCounter::GetNextTimeStamp()
|
|
{
|
|
TimeStamp timestamp = mFrameTimestamps[mIteratorIndex--];
|
|
MOZ_ASSERT(!timestamp.IsNull(), "Reading Invalid Timestamp Data");
|
|
|
|
if (mIteratorIndex == -1) {
|
|
mIteratorIndex = kMaxFrames - 1;
|
|
}
|
|
return timestamp;
|
|
}
|
|
|
|
/**
|
|
* GetFPS calculates how many frames we've already composited from the current
|
|
* frame timestamp and we iterate from the latest timestamp we recorded,
|
|
* going back in time. When we hit a frame that is longer than the 1 second
|
|
* from the current composited frame, we return how many frames we've counted.
|
|
* Just a visualization:
|
|
*
|
|
* aTimestamp
|
|
* Frames: 1 2 3 4 5 6 7 8 9 10 11 12
|
|
* Time -------------------------->
|
|
*
|
|
* GetFPS iterates from aTimestamp, which is the current frame.
|
|
* Then starting at frame 12, going back to frame 11, 10, etc, we calculate
|
|
* the duration of the recorded frame timestamp from aTimestamp.
|
|
* Once duration is greater than 1 second, we return how many frames
|
|
* we composited.
|
|
*/
|
|
double
|
|
FPSCounter::GetFPS(TimeStamp aTimestamp)
|
|
{
|
|
int frameCount = 0;
|
|
int duration = 1.0; // Only care about the last 1s of data
|
|
|
|
ResetReverseIterator();
|
|
while (HasNext(aTimestamp, duration)) {
|
|
GetNextTimeStamp();
|
|
frameCount++;
|
|
}
|
|
|
|
return frameCount;
|
|
}
|
|
|
|
// Iterate the same way we do in GetFPS()
|
|
int
|
|
FPSCounter::BuildHistogram(std::map<int, int>& aFpsData)
|
|
{
|
|
TimeStamp currentIntervalStart = GetLatestTimeStamp();
|
|
TimeStamp currentTimeStamp = GetLatestTimeStamp();
|
|
TimeStamp startTimeStamp = GetLatestTimeStamp();
|
|
|
|
int frameCount = 0;
|
|
int totalFrameCount = 0;
|
|
|
|
ResetReverseIterator();
|
|
while (HasNext(startTimeStamp)) {
|
|
currentTimeStamp = GetNextTimeStamp();
|
|
TimeDuration interval = currentIntervalStart - currentTimeStamp;
|
|
|
|
if (interval.ToSecondsSigDigits() >= 1.0 ) {
|
|
currentIntervalStart = currentTimeStamp;
|
|
aFpsData[frameCount]++;
|
|
frameCount = 0;
|
|
}
|
|
|
|
frameCount++;
|
|
totalFrameCount++;
|
|
}
|
|
|
|
TimeDuration totalTime = currentIntervalStart - currentTimeStamp;
|
|
printf_stderr("Discarded %d frames over %f ms in histogram for %s\n",
|
|
frameCount, totalTime.ToMilliseconds(), mFPSName);
|
|
return totalFrameCount;
|
|
}
|
|
|
|
// Iterate the same way we do in GetFPS()
|
|
void
|
|
FPSCounter::WriteFrameTimeStamps(PRFileDesc* fd)
|
|
{
|
|
const int bufferSize = 256;
|
|
char buffer[bufferSize];
|
|
int writtenCount = PR_snprintf(buffer, bufferSize, "FPS Data for: %s\n", mFPSName);
|
|
MOZ_ASSERT(writtenCount >= 0);
|
|
PR_Write(fd, buffer, writtenCount);
|
|
|
|
ResetReverseIterator();
|
|
TimeStamp startTimeStamp = GetLatestTimeStamp();
|
|
|
|
MOZ_ASSERT(HasNext(startTimeStamp));
|
|
TimeStamp previousSample = GetNextTimeStamp();
|
|
|
|
MOZ_ASSERT(HasNext(startTimeStamp));
|
|
TimeStamp nextTimeStamp = GetNextTimeStamp();
|
|
|
|
while (HasNext(startTimeStamp)) {
|
|
TimeDuration duration = previousSample - nextTimeStamp;
|
|
writtenCount = PR_snprintf(buffer, bufferSize, "%f,\n", duration.ToMilliseconds());
|
|
|
|
MOZ_ASSERT(writtenCount >= 0);
|
|
PR_Write(fd, buffer, writtenCount);
|
|
|
|
previousSample = nextTimeStamp;
|
|
nextTimeStamp = GetNextTimeStamp();
|
|
}
|
|
}
|
|
|
|
double
|
|
FPSCounter::GetMean(std::map<int, int> aHistogram)
|
|
{
|
|
double average = 0.0;
|
|
double samples = 0.0;
|
|
|
|
for (std::map<int, int>::iterator iter = aHistogram.begin();
|
|
iter != aHistogram.end(); ++iter)
|
|
{
|
|
int fps = iter->first;
|
|
int count = iter->second;
|
|
|
|
average += fps * count;
|
|
samples += count;
|
|
}
|
|
|
|
return average / samples;
|
|
}
|
|
|
|
double
|
|
FPSCounter::GetStdDev(std::map<int, int> aHistogram)
|
|
{
|
|
double sumOfDifferences = 0;
|
|
double average = GetMean(aHistogram);
|
|
double samples = 0.0;
|
|
|
|
for (std::map<int, int>::iterator iter = aHistogram.begin();
|
|
iter != aHistogram.end(); ++iter)
|
|
{
|
|
int fps = iter->first;
|
|
int count = iter->second;
|
|
|
|
double diff = ((double) fps) - average;
|
|
diff *= diff;
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
sumOfDifferences += diff;
|
|
}
|
|
samples += count;
|
|
}
|
|
|
|
double stdDev = sumOfDifferences / samples;
|
|
return sqrt(stdDev);
|
|
}
|
|
|
|
void
|
|
FPSCounter::PrintFPS()
|
|
{
|
|
if (!gfxPrefs::FPSPrintHistogram()) {
|
|
return;
|
|
}
|
|
|
|
std::map<int, int> histogram;
|
|
int totalFrames = BuildHistogram(histogram);
|
|
|
|
TimeDuration measurementInterval = mFrameTimestamps[GetLatestReadIndex()] - mLastInterval;
|
|
printf_stderr("FPS for %s. Total Frames: %d Time Interval: %f seconds\n",
|
|
mFPSName, totalFrames, measurementInterval.ToSecondsSigDigits());
|
|
|
|
PrintHistogram(histogram);
|
|
}
|
|
|
|
void
|
|
FPSCounter::PrintHistogram(std::map<int, int>& aHistogram)
|
|
{
|
|
int length = 0;
|
|
const int kBufferLength = 512;
|
|
char buffer[kBufferLength];
|
|
|
|
for (std::map<int, int>::iterator iter = aHistogram.begin();
|
|
iter != aHistogram.end(); iter++)
|
|
{
|
|
int fps = iter->first;
|
|
int count = iter->second;
|
|
|
|
length += PR_snprintf(buffer + length, kBufferLength - length,
|
|
"FPS: %d = %d. ", fps, count);
|
|
NS_ASSERTION(length >= kBufferLength, "Buffer overrun while printing FPS histogram.");
|
|
}
|
|
|
|
printf_stderr("%s\n", buffer);
|
|
printf_stderr("Mean: %f , std dev %f\n", GetMean(aHistogram), GetStdDev(aHistogram));
|
|
}
|
|
|
|
// Write FPS timestamp data to a file only if
|
|
// draw-fps.write-to-file is true
|
|
nsresult
|
|
FPSCounter::WriteFrameTimeStamps()
|
|
{
|
|
if (!gfxPrefs::WriteFPSToFile()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(mWriteIndex == 0);
|
|
|
|
nsCOMPtr<nsIFile> resultFile;
|
|
nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(resultFile));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!strncmp(mFPSName, "Compositor", strlen(mFPSName))) {
|
|
resultFile->Append(NS_LITERAL_STRING("fps.txt"));
|
|
} else {
|
|
resultFile->Append(NS_LITERAL_STRING("txn.txt"));
|
|
}
|
|
|
|
PRFileDesc* fd = nullptr;
|
|
int mode = 644;
|
|
int openFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
|
|
rv = resultFile->OpenNSPRFileDesc(openFlags, mode, &fd);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
WriteFrameTimeStamps(fd);
|
|
PR_Close(fd);
|
|
|
|
nsAutoCString path;
|
|
rv = resultFile->GetNativePath(path);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
printf_stderr("Wrote FPS data to file: %s\n", path.get());
|
|
return NS_OK;
|
|
}
|
|
|
|
FPSState::FPSState()
|
|
: mCompositionFps("Compositor")
|
|
, mTransactionFps("LayerTransactions")
|
|
{
|
|
}
|
|
|
|
// Size of the builtin font.
|
|
static const float FontHeight = 7.f;
|
|
static const float FontWidth = 4.f;
|
|
|
|
// Scale the font when drawing it to the viewport for better readability.
|
|
static const float FontScaleX = 2.f;
|
|
static const float FontScaleY = 3.f;
|
|
|
|
static void DrawDigits(unsigned int aValue,
|
|
int aOffsetX, int aOffsetY,
|
|
Compositor* aCompositor,
|
|
EffectChain& aEffectChain)
|
|
{
|
|
if (aValue > 999) {
|
|
aValue = 999;
|
|
}
|
|
|
|
unsigned int divisor = 100;
|
|
float textureWidth = FontWidth * 10;
|
|
gfx::Float opacity = 1;
|
|
gfx::Matrix4x4 transform;
|
|
transform.Scale(FontScaleX, FontScaleY, 1);
|
|
|
|
for (size_t n = 0; n < 3; ++n) {
|
|
unsigned int digit = aValue % (divisor * 10) / divisor;
|
|
divisor /= 10;
|
|
|
|
RefPtr<TexturedEffect> texturedEffect = static_cast<TexturedEffect*>(aEffectChain.mPrimaryEffect.get());
|
|
texturedEffect->mTextureCoords = Rect(float(digit * FontWidth) / textureWidth, 0, FontWidth / textureWidth, 1.0f);
|
|
|
|
Rect drawRect = Rect(aOffsetX + n * FontWidth, aOffsetY, FontWidth, FontHeight);
|
|
Rect clipRect = Rect(0, 0, 300, 100);
|
|
aCompositor->DrawQuad(drawRect, clipRect,
|
|
aEffectChain, opacity, transform);
|
|
}
|
|
}
|
|
|
|
void FPSState::DrawFPS(TimeStamp aNow,
|
|
int aOffsetX, int aOffsetY,
|
|
unsigned int aFillRatio,
|
|
Compositor* aCompositor)
|
|
{
|
|
if (!mFPSTextureSource) {
|
|
const char *text =
|
|
" "
|
|
" XXX XX XXX XXX X X XXX XXX XXX XXX XXX"
|
|
" X X X X X X X X X X X X X X"
|
|
" X X X XXX XXX XXX XXX XXX X XXX XXX"
|
|
" X X X X X X X X X X X X X"
|
|
" XXX XXX XXX XXX X XXX XXX X XXX X"
|
|
" ";
|
|
|
|
// Convert the text encoding above to RGBA.
|
|
int w = FontWidth * 10;
|
|
int h = FontHeight;
|
|
uint32_t* buf = (uint32_t *) malloc(w * h * sizeof(uint32_t));
|
|
for (int i = 0; i < h; i++) {
|
|
for (int j = 0; j < w; j++) {
|
|
uint32_t purple = 0xfff000ff;
|
|
uint32_t white = 0xffffffff;
|
|
buf[i * w + j] = (text[i * w + j] == ' ') ? purple : white;
|
|
}
|
|
}
|
|
|
|
int bytesPerPixel = 4;
|
|
RefPtr<DataSourceSurface> fpsSurface = Factory::CreateWrappingDataSourceSurface(
|
|
reinterpret_cast<uint8_t*>(buf), w * bytesPerPixel, IntSize(w, h), SurfaceFormat::B8G8R8A8);
|
|
mFPSTextureSource = aCompositor->CreateDataTextureSource();
|
|
mFPSTextureSource->Update(fpsSurface);
|
|
}
|
|
|
|
EffectChain effectChain;
|
|
effectChain.mPrimaryEffect = CreateTexturedEffect(SurfaceFormat::B8G8R8A8,
|
|
mFPSTextureSource,
|
|
Filter::POINT,
|
|
true);
|
|
|
|
unsigned int fps = unsigned(mCompositionFps.AddFrameAndGetFps(aNow));
|
|
unsigned int txnFps = unsigned(mTransactionFps.GetFPS(aNow));
|
|
|
|
DrawDigits(fps, aOffsetX + 0, aOffsetY, aCompositor, effectChain);
|
|
DrawDigits(txnFps, aOffsetX + FontWidth * 4, aOffsetY, aCompositor, effectChain);
|
|
DrawDigits(aFillRatio, aOffsetX + FontWidth * 8, aOffsetY, aCompositor, effectChain);
|
|
}
|
|
|
|
} // end namespace layers
|
|
} // end namespace mozilla
|