Bug 1042291 - Add a CanvasDrawObserver which observes the first few seconds/frames of a Canvas2D's lifetime to determine using a heuristic if it should be software or GPU backed. r=gw280, r=snorp

This commit is contained in:
George Wright 2014-09-10 16:15:43 -04:00
parent e2bc55a28c
commit c1cc8c3af6
7 changed files with 172 additions and 2 deletions

View File

@ -89,6 +89,7 @@
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Preferences.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/unused.h"
#include "nsCCUncollectableMarker.h"
@ -691,6 +692,106 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPattern, Release)
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext)
class CanvasDrawObserver
{
public:
CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext);
// Only enumerate draw calls that could affect the heuristic
enum DrawCallType {
PutImageData,
GetImageData,
DrawImage
};
// This is the one that we call on relevant draw calls and count
// GPU vs. CPU preferrable calls...
void DidDrawCall(DrawCallType aType);
// When this returns true, the observer is done making the decisions.
// Right now, we expect to get rid of the observer after the FrameEnd
// returns true, though the decision could eventually change if the
// function calls shift. If we change to monitor the functions called
// and make decisions to change more than once, we would probably want
// FrameEnd to reset the timer and counters as it returns true.
bool FrameEnd();
private:
// These values will be picked up from preferences:
int32_t mMinFramesBeforeDecision;
float mMinSecondsBeforeDecision;
int32_t mMinCallsBeforeDecision;
CanvasRenderingContext2D* mCanvasContext;
int32_t mSoftwarePreferredCalls;
int32_t mGPUPreferredCalls;
int32_t mFramesRendered;
TimeStamp mCreationTime;
};
// We are not checking for the validity of the preference values. For example,
// negative values will have an effect of a quick exit, so no harm done.
CanvasDrawObserver::CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext)
: mMinFramesBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinFrames())
, mMinSecondsBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinSeconds())
, mMinCallsBeforeDecision(gfxPrefs::CanvasAutoAccelerateMinCalls())
, mCanvasContext(aCanvasContext)
, mSoftwarePreferredCalls(0)
, mGPUPreferredCalls(0)
, mFramesRendered(0)
, mCreationTime(TimeStamp::NowLoRes())
{}
void
CanvasDrawObserver::DidDrawCall(DrawCallType aType)
{
switch (aType) {
case PutImageData:
case GetImageData:
if (mGPUPreferredCalls == 0 && mSoftwarePreferredCalls == 0) {
mCreationTime = TimeStamp::NowLoRes();
}
mSoftwarePreferredCalls++;
break;
case DrawImage:
if (mGPUPreferredCalls == 0 && mSoftwarePreferredCalls == 0) {
mCreationTime = TimeStamp::NowLoRes();
}
mGPUPreferredCalls++;
break;
}
}
// If we return true, the observer is done making the decisions...
bool
CanvasDrawObserver::FrameEnd()
{
mFramesRendered++;
// We log the first mMinFramesBeforeDecision frames of any
// canvas object then make a call to determine whether it should
// be GPU or CPU backed
if ((mFramesRendered >= mMinFramesBeforeDecision) ||
((TimeStamp::NowLoRes() - mCreationTime).ToSeconds()) > mMinSecondsBeforeDecision) {
// If we don't have enough data, don't bother changing...
if (mGPUPreferredCalls > mMinCallsBeforeDecision ||
mSoftwarePreferredCalls > mMinCallsBeforeDecision) {
if (mGPUPreferredCalls >= mSoftwarePreferredCalls) {
mCanvasContext->SwitchRenderingMode(CanvasRenderingContext2D::RenderingMode::OpenGLBackendMode);
} else {
mCanvasContext->SwitchRenderingMode(CanvasRenderingContext2D::RenderingMode::SoftwareBackendMode);
}
}
// If we ever redesign this class to constantly monitor the functions
// and keep making decisions, we would probably want to reset the counters
// and the timers here...
return true;
}
return false;
}
class CanvasRenderingContext2DUserData : public LayerUserData {
public:
explicit CanvasRenderingContext2DUserData(CanvasRenderingContext2D *aContext)
@ -724,6 +825,12 @@ public:
static_cast<CanvasRenderingContext2DUserData*>(aData);
if (self->mContext) {
self->mContext->MarkContextClean();
if (self->mContext->mDrawObserver) {
if (self->mContext->mDrawObserver->FrameEnd()) {
// Note that this call deletes and nulls out mDrawObserver:
self->mContext->RemoveDrawObserver();
}
}
}
}
bool IsForContext(CanvasRenderingContext2D *aContext)
@ -828,6 +935,7 @@ CanvasRenderingContext2D::CanvasRenderingContext2D()
, mZero(false), mOpaque(false)
, mResetLayer(true)
, mIPC(false)
, mDrawObserver(nullptr)
, mIsEntireFrameInvalid(false)
, mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false)
, mInvalidateCount(0)
@ -839,10 +947,14 @@ CanvasRenderingContext2D::CanvasRenderingContext2D()
mRenderingMode = RenderingMode::SoftwareBackendMode;
}
if (gfxPlatform::GetPlatform()->HaveChoiceOfHWAndSWCanvas()) {
mDrawObserver = new CanvasDrawObserver(this);
}
}
CanvasRenderingContext2D::~CanvasRenderingContext2D()
{
RemoveDrawObserver();
RemovePostRefreshObserver();
Reset();
// Drop references from all CanvasRenderingContext2DUserData to this context
@ -1443,6 +1555,10 @@ CanvasRenderingContext2D::SetContextOptions(JSContext* aCx, JS::Handle<JS::Value
if (Preferences::GetBool("gfx.canvas.willReadFrequently.enable", false)) {
// Use software when there is going to be a lot of readback
if (attributes.mWillReadFrequently) {
// We want to lock into software, so remove the observer that
// may potentially change that...
RemoveDrawObserver();
mRenderingMode = RenderingMode::SoftwareBackendMode;
}
}
@ -4009,6 +4125,10 @@ CanvasRenderingContext2D::DrawImage(const HTMLImageOrCanvasOrVideoElement& image
uint8_t optional_argc,
ErrorResult& error)
{
if (mDrawObserver) {
mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::DrawImage);
}
MOZ_ASSERT(optional_argc == 0 || optional_argc == 2 || optional_argc == 6);
RefPtr<SourceSurface> srcSurf;
@ -4628,6 +4748,10 @@ CanvasRenderingContext2D::GetImageData(JSContext* aCx, double aSx,
double aSy, double aSw,
double aSh, ErrorResult& error)
{
if (mDrawObserver) {
mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData);
}
EnsureTarget();
if (!IsTargetValid()) {
error.Throw(NS_ERROR_FAILURE);
@ -4708,6 +4832,10 @@ CanvasRenderingContext2D::GetImageDataArray(JSContext* aCx,
uint32_t aHeight,
JSObject** aRetval)
{
if (mDrawObserver) {
mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::GetImageData);
}
MOZ_ASSERT(aWidth && aHeight);
CheckedInt<uint32_t> len = CheckedInt<uint32_t>(aWidth) * aHeight * 4;
@ -4887,6 +5015,10 @@ CanvasRenderingContext2D::PutImageData_explicit(int32_t x, int32_t y, uint32_t w
bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY,
int32_t dirtyWidth, int32_t dirtyHeight)
{
if (mDrawObserver) {
mDrawObserver->DidDrawCall(CanvasDrawObserver::DrawCallType::PutImageData);
}
if (w == 0 || h == 0) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
@ -5066,6 +5198,15 @@ CanvasRenderingContext2D::SkiaGLTex() const
return (uint32_t)(uintptr_t)mTarget->GetNativeSurface(NativeSurfaceType::OPENGL_TEXTURE);
}
void CanvasRenderingContext2D::RemoveDrawObserver()
{
if (mDrawObserver) {
delete mDrawObserver;
mDrawObserver = nullptr;
}
}
already_AddRefed<CanvasLayer>
CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder,
CanvasLayer *aOldLayer,

View File

@ -114,6 +114,7 @@ private:
struct CanvasBidiProcessor;
class CanvasRenderingContext2DUserData;
class CanvasDrawObserver;
/**
** CanvasRenderingContext2D
@ -775,6 +776,12 @@ protected:
uint32_t SkiaGLTex() const;
// This observes our draw calls at the beginning of the canvas
// lifetime and switches to software or GPU mode depending on
// what it thinks is best
CanvasDrawObserver* mDrawObserver;
void RemoveDrawObserver();
/**
* Flag to avoid duplicate calls to InvalidateFrame. Set to true whenever
* Redraw is called, reset to false when Render is called.
@ -1089,6 +1096,7 @@ protected:
}
friend struct CanvasBidiProcessor;
friend class CanvasDrawObserver;
};
MOZ_FINISH_NESTED_ENUM_CLASS(CanvasRenderingContext2D::CanvasMultiGetterType)

View File

@ -406,6 +406,11 @@ gfxAndroidPlatform::GetScreenDepth() const
bool
gfxAndroidPlatform::UseAcceleratedSkiaCanvas()
{
return HaveChoiceOfHWAndSWCanvas() && gfxPlatform::UseAcceleratedSkiaCanvas();
}
bool gfxAndroidPlatform::HaveChoiceOfHWAndSWCanvas()
{
#ifdef MOZ_WIDGET_ANDROID
if (AndroidBridge::Bridge()->GetAPIVersion() < 11) {
@ -413,6 +418,5 @@ gfxAndroidPlatform::UseAcceleratedSkiaCanvas()
return false;
}
#endif
return gfxPlatform::UseAcceleratedSkiaCanvas();
return gfxPlatform::HaveChoiceOfHWAndSWCanvas();
}

View File

@ -87,6 +87,7 @@ public:
return true;
}
virtual bool HaveChoiceOfHWAndSWCanvas() MOZ_OVERRIDE;
virtual bool UseAcceleratedSkiaCanvas() MOZ_OVERRIDE;
#ifdef MOZ_WIDGET_GONK

View File

@ -939,6 +939,11 @@ gfxPlatform::UseAcceleratedSkiaCanvas()
mPreferredCanvasBackend == BackendType::SKIA;
}
bool gfxPlatform::HaveChoiceOfHWAndSWCanvas()
{
return mPreferredCanvasBackend == BackendType::SKIA;
}
void
gfxPlatform::InitializeSkiaCacheLimits()
{

View File

@ -262,6 +262,14 @@ public:
return BackendTypeBit(aType) & mContentBackendBitmask;
}
/// This function lets us know if the current preferences/platform
/// combination allows for both accelerated and not accelerated canvas
/// implementations. If it does, and other relevant preferences are
/// asking for it, we will examine the commands in the first few seconds
/// of the canvas usage, and potentially change to accelerated or
/// non-accelerated canvas.
virtual bool HaveChoiceOfHWAndSWCanvas();
virtual bool UseAcceleratedSkiaCanvas();
virtual void InitializeSkiaCacheLimits();

View File

@ -185,6 +185,9 @@ private:
#if defined(ANDROID)
DECL_GFX_PREF(Once, "gfx.apitrace.enabled", UseApitrace, bool, false);
#endif
DECL_GFX_PREF(Live, "gfx.canvas.auto_accelerate.min_frames", CanvasAutoAccelerateMinFrames, int32_t, 30);
DECL_GFX_PREF(Live, "gfx.canvas.auto_accelerate.min_seconds", CanvasAutoAccelerateMinSeconds, float, 5.0f);
DECL_GFX_PREF(Live, "gfx.canvas.auto_accelerate.min_calls", CanvasAutoAccelerateMinCalls, int32_t, 4);
DECL_GFX_PREF(Live, "gfx.canvas.azure.accelerated", CanvasAzureAccelerated, bool, false);
DECL_GFX_PREF(Once, "gfx.canvas.skiagl.dynamic-cache", CanvasSkiaGLDynamicCache, bool, false);
DECL_GFX_PREF(Once, "gfx.canvas.skiagl.cache-size", CanvasSkiaGLCacheSize, int32_t, 96);