Bug 1444430 - Add a ProfilerScreenshots class which can be used to submit DataSourceSurfaces to the profiler. r=jrmuizel

ProfilerScreenshots encodes these surfaces to JPG data URLs, and submits them
as profiler markers. The encoding is done on a separate thread.

MozReview-Commit-ID: 7CKDBqUsLny

--HG--
extra : rebase_source : 45b608544b4ddf8502302cf974ca4e8b306de64e
This commit is contained in:
Markus Stange 2018-03-26 23:08:33 -04:00
Родитель de5088e49a
Коммит 2d93796161
5 изменённых файлов: 289 добавлений и 0 удалений

Просмотреть файл

@ -0,0 +1,142 @@
/* -*- 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 "mozilla/layers/ProfilerScreenshots.h"
#include "mozilla/TimeStamp.h"
#include "GeckoProfiler.h"
#include "gfxUtils.h"
#include "nsThreadUtils.h"
#ifdef MOZ_GECKO_PROFILER
#include "ProfilerMarkerPayload.h"
#endif
using namespace mozilla;
using namespace mozilla::layers;
ProfilerScreenshots::ProfilerScreenshots()
: mMutex("ProfilerScreenshots::mMutex")
, mLiveSurfaceCount(0)
{
}
ProfilerScreenshots::~ProfilerScreenshots()
{
if (mThread) {
mThread->Shutdown();
mThread = nullptr;
}
}
/* static */ bool
ProfilerScreenshots::IsEnabled()
{
#ifdef MOZ_GECKO_PROFILER
return profiler_feature_active(ProfilerFeature::Screenshots);
#else
return false;
#endif
}
void
ProfilerScreenshots::SubmitScreenshot(uintptr_t aWindowIdentifier,
const gfx::IntSize& aOriginalSize,
const IntSize& aScaledSize,
const TimeStamp& aTimeStamp,
const std::function<bool(DataSourceSurface*)>& aPopulateSurface)
{
#ifdef MOZ_GECKO_PROFILER
RefPtr<DataSourceSurface> backingSurface = TakeNextSurface();
if (!backingSurface) {
return;
}
MOZ_RELEASE_ASSERT(aScaledSize <= backingSurface->GetSize());
bool succeeded = aPopulateSurface(backingSurface);
if (!succeeded) {
PROFILER_ADD_MARKER("NoCompositorScreenshot because aPopulateSurface callback failed");
ReturnSurface(backingSurface);
return;
}
if (!mThread) {
nsresult rv =
NS_NewNamedThread("ProfScreenshot", getter_AddRefs(mThread));
if (NS_WARN_IF(NS_FAILED(rv))) {
PROFILER_ADD_MARKER("NoCompositorScreenshot because ProfilerScreenshots thread creation failed");
ReturnSurface(backingSurface);
return;
}
}
int sourceThread = profiler_current_thread_id();
uintptr_t windowIdentifier = aWindowIdentifier;
IntSize originalSize = aOriginalSize;
IntSize scaledSize = aScaledSize;
TimeStamp timeStamp = aTimeStamp;
mThread->Dispatch(
NS_NewRunnableFunction("ProfilerScreenshots::SubmitScreenshot",
[this, backingSurface, sourceThread, windowIdentifier,
originalSize, scaledSize, timeStamp]() {
// Create a new surface that wraps backingSurface's data but has the correct
// size.
{
DataSourceSurface::ScopedMap scopedMap(backingSurface, DataSourceSurface::READ);
RefPtr<DataSourceSurface> surf =
Factory::CreateWrappingDataSourceSurface(
scopedMap.GetData(), scopedMap.GetStride(), scaledSize, SurfaceFormat::B8G8R8A8);
// Encode surf to a JPEG data URL.
nsCString dataURL;
nsresult rv =
gfxUtils::EncodeSourceSurface(surf, NS_LITERAL_CSTRING("image/jpeg"),
NS_LITERAL_STRING("quality=85"),
gfxUtils::eDataURIEncode,
nullptr, &dataURL);
if (NS_SUCCEEDED(rv)) {
// Add a marker with the data URL.
profiler_add_marker_for_thread(
sourceThread,
"CompositorScreenshot",
MakeUnique<ScreenshotPayload>(timeStamp, Move(dataURL),
originalSize, windowIdentifier));
}
}
// Return backingSurface back to the surface pool.
ReturnSurface(backingSurface);
}));
#endif
}
already_AddRefed<DataSourceSurface>
ProfilerScreenshots::TakeNextSurface()
{
MutexAutoLock mon(mMutex);
if (!mAvailableSurfaces.IsEmpty()) {
RefPtr<DataSourceSurface> surf = mAvailableSurfaces[0];
mAvailableSurfaces.RemoveElementAt(0);
return surf.forget();
}
if (mLiveSurfaceCount >= 8) {
NS_WARNING("already 8 surfaces in flight, skipping capture for this composite");
return nullptr;
}
mLiveSurfaceCount++;
return Factory::CreateDataSourceSurface(ScreenshotSize(),
SurfaceFormat::B8G8R8A8);
}
void
ProfilerScreenshots::ReturnSurface(DataSourceSurface* aSurface)
{
MutexAutoLock mon(this->mMutex);
mAvailableSurfaces.AppendElement(aSurface);
}

Просмотреть файл

@ -0,0 +1,108 @@
/* -*- 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/. */
#ifndef mozilla_layers_ProfilerScreenshots_h
#define mozilla_layers_ProfilerScreenshots_h
#include <functional>
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "mozilla/gfx/Point.h"
class nsIThread;
namespace mozilla {
namespace gfx {
class DataSourceSurface;
}
namespace layers {
/**
* Can be used to submit screenshots from the compositor to the profiler.
* Screenshots have a fixed bounding size. The user of this class will usually
* scale down the window contents first, ideally on the GPU, then read back the
* small scaled down image into main memory, and then call SubmitScreenshot to
* pass the data to the profiler.
* This class encodes each screenshot to a JPEG data URL, on a separate thread.
* This class manages that thread and recycles memory buffers.
*/
class ProfilerScreenshots final
{
public:
ProfilerScreenshots();
~ProfilerScreenshots();
/**
* Returns whether the profiler is currently active and is running with the
* "screenshots" feature enabled.
*/
static bool IsEnabled();
/**
* Returns a fixed size that all screenshots should be resized to fit into.
*/
static gfx::IntSize ScreenshotSize() { return gfx::IntSize(350, 350); }
/**
* The main functionality provided by this class.
* This method will synchronously invoke the supplied aPopulateSurface
* callback function with a DataSourceSurface in which the callback should
* store the pixel data. This surface may be larger than aScaledSize, but
* only the data in the rectangle (0, 0, aScaledSize.width, aScaledSize.height)
* will be read.
* @param aWindowIdentifier A pointer-sized integer that can be used to match
* up multiple screenshots from the same window.
* @param aOriginalSize The unscaled size of the snapshotted window.
* @param aScaledSize The scaled size, aScaled <= ScreenshotSize(), which the
* snapshot has been resized to.
* @param aTimeStamp The time at which the snapshot was taken. In
* asynchronous readback implementations, this is the time at which the
* readback / copy command was put into the command stream to the GPU, not
* the time at which the readback data was mapped into main memory.
* @param aPopulateSurface A callback that the caller needs to implement,
* which needs to copy the screenshot pixel data into the surface that's
* supplied to the callback. Called zero or one times, synchronously.
*/
void SubmitScreenshot(uintptr_t aWindowIdentifier, const gfx::IntSize& aOriginalSize,
const gfx::IntSize& aScaledSize, const TimeStamp& aTimeStamp,
const std::function<bool(gfx::DataSourceSurface*)>& aPopulateSurface);
private:
/**
* Recycle a surface from mAvailableSurfaces or create a new one if all
* surfaces are currently in use, up to some maximum limit.
* Returns null if the limit is reached.
* Can be called on any thread.
*/
already_AddRefed<DataSourceSurface> TakeNextSurface();
/**
* Return aSurface back into the mAvailableSurfaces pool. Can be called on
* any thread.
*/
void ReturnSurface(DataSourceSurface* aSurface);
// The thread on which encoding happens.
nsCOMPtr<nsIThread> mThread;
// An array of surfaces ready to be recycled. Can be accessed from multiple
// threads, protected by mMutex.
nsTArray<RefPtr<DataSourceSurface>> mAvailableSurfaces;
// Protects mAvailableSurfaces.
Mutex mMutex;
// The total number of surfaces created. If encoding is fast enough to happen
// entirely in the time between two calls to SubmitScreenshot, this should
// never exceed 1.
uint32_t mLiveSurfaceCount;
};
} // namespace layers
} // namespace mozilla
#endif // mozilla_layers_ProfilerScreenshots_h

Просмотреть файл

@ -221,6 +221,7 @@ EXPORTS.mozilla.layers += [
'opengl/TextureHostOGL.h',
'PaintThread.h',
'PersistentBufferProvider.h',
'ProfilerScreenshots.h',
'RenderTrace.h',
'RotatedBuffer.h',
'ShareableCanvasRenderer.h',
@ -457,6 +458,7 @@ UNIFIED_SOURCES += [
'opengl/TextureClientOGL.cpp',
'opengl/TextureHostOGL.cpp',
'PaintThread.cpp',
'ProfilerScreenshots.cpp',
'ReadbackProcessor.cpp',
'RenderTrace.cpp',
'RotatedBuffer.cpp',

Просмотреть файл

@ -3,6 +3,8 @@
* 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 <inttypes.h>
#include "GeckoProfiler.h"
#include "ProfilerBacktrace.h"
#include "ProfilerMarkerPayload.h"
@ -128,6 +130,20 @@ VsyncMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
aWriter.StringProperty("category", "VsyncTimestamp");
}
void
ScreenshotPayload::StreamPayload(SpliceableJSONWriter& aWriter,
const TimeStamp& aProcessStartTime,
UniqueStacks& aUniqueStacks)
{
aUniqueStacks.mUniqueStrings->WriteProperty(aWriter, "url", mScreenshotDataURL.get());
char hexWindowID[32];
SprintfLiteral(hexWindowID, "0x%" PRIXPTR, mWindowIdentifier);
aWriter.StringProperty("windowID", hexWindowID);
aWriter.DoubleProperty("windowWidth", mWindowSize.width);
aWriter.DoubleProperty("windowHeight", mWindowSize.height);
}
void
GCSliceMarkerPayload::StreamPayload(SpliceableJSONWriter& aWriter,
const TimeStamp& aProcessStartTime,

Просмотреть файл

@ -210,6 +210,27 @@ private:
mozilla::TimeStamp mVsyncTimestamp;
};
class ScreenshotPayload : public ProfilerMarkerPayload
{
public:
explicit ScreenshotPayload(mozilla::TimeStamp aTimeStamp,
nsCString&& aScreenshotDataURL,
const mozilla::gfx::IntSize& aWindowSize,
uintptr_t aWindowIdentifier)
: ProfilerMarkerPayload(aTimeStamp, mozilla::TimeStamp())
, mScreenshotDataURL(mozilla::Move(aScreenshotDataURL))
, mWindowSize(aWindowSize)
, mWindowIdentifier(aWindowIdentifier)
{}
DECL_STREAM_PAYLOAD
private:
nsCString mScreenshotDataURL;
mozilla::gfx::IntSize mWindowSize;
uintptr_t mWindowIdentifier;
};
class GCSliceMarkerPayload : public ProfilerMarkerPayload
{
public: