Bug 980178 - Clean up context loss handling code. - r=kamidphish

This commit is contained in:
Jeff Gilbert 2014-06-02 14:40:15 -07:00
parent 6f87c8e03e
commit d05a5159cc
8 changed files with 244 additions and 161 deletions

View File

@ -83,18 +83,21 @@ WebGLMemoryPressureObserver::Observe(nsISupports* aSubject,
if (!mContext->mCanLoseContextInForeground &&
ProcessPriorityManager::CurrentProcessIsForeground())
{
wantToLoseContext = false;
else if (!nsCRT::strcmp(aSomeData,
MOZ_UTF16("heap-minimize")))
} else if (!nsCRT::strcmp(aSomeData,
MOZ_UTF16("heap-minimize")))
{
wantToLoseContext = mContext->mLoseContextOnHeapMinimize;
}
if (wantToLoseContext)
if (wantToLoseContext) {
mContext->ForceLoseContext();
}
return NS_OK;
}
WebGLContextOptions::WebGLContextOptions()
: alpha(true), depth(true), stencil(false),
premultipliedAlpha(true), antialias(true),
@ -168,12 +171,12 @@ WebGLContext::WebGLContext()
WebGLMemoryTracker::AddWebGLContext(this);
mAllowRestore = true;
mAllowContextRestore = true;
mLastLossWasSimulated = false;
mContextLossTimerRunning = false;
mDrawSinceContextLossTimerSet = false;
mRunContextLossTimerAgain = false;
mContextRestorer = do_CreateInstance("@mozilla.org/timer;1");
mContextStatus = ContextNotLost;
mContextLostErrorSet = false;
mLoseContextOnHeapMinimize = false;
mCanLoseContextInForeground = true;
@ -571,11 +574,8 @@ WebGLContext::SetDimensions(int32_t width, int32_t height)
mResetLayer = true;
mOptionsFrozen = true;
mHasRobustness = gl->HasRobustness();
// increment the generation number
++mGeneration;
#if 0
if (mGeneration > 0) {
// XXX dispatch context lost event
@ -1115,6 +1115,96 @@ WebGLContext::DummyFramebufferOperation(const char *info)
ErrorInvalidFramebufferOperation("%s: incomplete framebuffer", info);
}
static bool
CheckContextLost(GLContext* gl, bool* out_isGuilty)
{
MOZ_ASSERT(gl);
MOZ_ASSERT(out_isGuilty);
bool isEGL = gl->GetContextType() == gl::GLContextType::EGL;
GLenum resetStatus = LOCAL_GL_NO_ERROR;
if (gl->HasRobustness()) {
gl->MakeCurrent();
resetStatus = gl->fGetGraphicsResetStatus();
} else if (isEGL) {
// Simulate a ARB_robustness guilty context loss for when we
// get an EGL_CONTEXT_LOST error. It may not actually be guilty,
// but we can't make any distinction.
if (!gl->MakeCurrent(true) && gl->IsContextLost()) {
resetStatus = LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB;
}
}
if (resetStatus == LOCAL_GL_NO_ERROR) {
*out_isGuilty = false;
return false;
}
// Assume guilty unless we find otherwise!
bool isGuilty = true;
switch (resetStatus) {
case LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB:
// Either nothing wrong, or not our fault.
isGuilty = false;
break;
case LOCAL_GL_GUILTY_CONTEXT_RESET_ARB:
NS_WARNING("WebGL content on the page definitely caused the graphics"
" card to reset.");
break;
case LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB:
NS_WARNING("WebGL content on the page might have caused the graphics"
" card to reset");
// If we can't tell, assume guilty.
break;
default:
MOZ_ASSERT(false, "Unreachable.");
// If we do get here, let's pretend to be guilty as an escape plan.
break;
}
if (isGuilty) {
NS_WARNING("WebGL context on this page is considered guilty, and will"
" not be restored.");
}
*out_isGuilty = isGuilty;
return true;
}
bool
WebGLContext::TryToRestoreContext()
{
if (NS_FAILED(SetDimensions(mWidth, mHeight)))
return false;
return true;
}
class UpdateContextLossStatusTask : public nsRunnable
{
WebGLContext* const mContext;
public:
UpdateContextLossStatusTask(WebGLContext* context)
: mContext(context)
{
}
NS_IMETHOD Run() {
mContext->UpdateContextLossStatus();
return NS_OK;
}
};
void
WebGLContext::EnqueueUpdateContextLossStatus()
{
nsCOMPtr<nsIRunnable> task = new UpdateContextLossStatusTask(this);
NS_DispatchToCurrentThread(task);
}
// We use this timer for many things. Here are the things that it is activated for:
// 1) If a script is using the MOZ_WEBGL_lose_context extension.
// 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the
@ -1130,137 +1220,123 @@ WebGLContext::DummyFramebufferOperation(const char *info)
// At a bare minimum, from context lost to context restores, it would take 3
// full timer iterations: detection, webglcontextlost, webglcontextrestored.
void
WebGLContext::RobustnessTimerCallback(nsITimer* timer)
WebGLContext::UpdateContextLossStatus()
{
TerminateContextLossTimer();
if (!mCanvasElement) {
// the canvas is gone. That happens when the page was closed before we got
// this timer event. In this case, there's nothing to do here, just don't crash.
return;
}
if (mContextStatus == ContextNotLost) {
// We don't know that we're lost, but we might be, so we need to
// check. If we're guilty, don't allow restores, though.
bool isGuilty = true;
bool isContextLost = CheckContextLost(gl, &isGuilty);
if (isContextLost) {
if (isGuilty)
mAllowContextRestore = false;
ForceLoseContext();
}
// Fall through.
}
// If the context has been lost and we're waiting for it to be restored, do
// that now.
if (mContextStatus == ContextLostAwaitingEvent) {
bool defaultAction;
// The context has been lost and we haven't yet triggered the
// callback, so do that now.
bool useDefaultHandler;
nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
NS_LITERAL_STRING("webglcontextlost"),
true,
true,
&defaultAction);
&useDefaultHandler);
// We sent the callback, so we're just 'regular lost' now.
mContextStatus = ContextLost;
// If we're told to use the default handler, it means the script
// didn't bother to handle the event. In this case, we shouldn't
// auto-restore the context.
if (useDefaultHandler)
mAllowContextRestore = false;
// If the script didn't handle the event, we don't allow restores.
if (defaultAction)
mAllowRestore = false;
// Fall through.
}
// If the script handled the event and we are allowing restores, then
// mark it to be restored. Otherwise, leave it as context lost
// (unusable).
if (!defaultAction && mAllowRestore) {
ForceRestoreContext();
// Restart the timer so that it will be restored on the next
// callback.
SetupContextLossTimer();
} else {
if (mContextStatus == ContextLost) {
// Context is lost, and we've already sent the callback. We
// should try to restore the context if we're both allowed to,
// and supposed to.
// Are we allowed to restore the context?
if (!mAllowContextRestore)
return;
// If we're only simulated-lost, we shouldn't auto-restore, and
// instead we should wait for restoreContext() to be called.
if (mLastLossWasSimulated)
return;
ForceRestoreContext();
return;
}
if (mContextStatus == ContextLostAwaitingRestore) {
// Context is lost, but we should try to restore it.
if (!mAllowContextRestore) {
// We might decide this after thinking we'd be OK restoring
// the context, so downgrade.
mContextStatus = ContextLost;
}
} else if (mContextStatus == ContextLostAwaitingRestore) {
// Try to restore the context. If it fails, try again later.
if (NS_FAILED(SetDimensions(mWidth, mHeight))) {
SetupContextLossTimer();
return;
}
if (!TryToRestoreContext()) {
// Failed to restore. Try again later.
RunContextLossTimer();
return;
}
// Revival!
mContextStatus = ContextNotLost;
nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
static_cast<nsIDOMHTMLCanvasElement*>(mCanvasElement),
NS_LITERAL_STRING("webglcontextrestored"),
true,
true);
// Set all flags back to the state they were in before the context was
// lost.
mEmitContextLostErrorOnce = true;
mAllowRestore = true;
}
MaybeRestoreContext();
return;
}
void
WebGLContext::MaybeRestoreContext()
{
// Don't try to handle it if we already know it's busted.
if (mContextStatus != ContextNotLost || gl == nullptr)
return;
bool isEGL = gl->GetContextType() == gl::GLContextType::EGL,
isANGLE = gl->IsANGLE();
GLContext::ContextResetARB resetStatus = GLContext::CONTEXT_NO_ERROR;
if (mHasRobustness) {
gl->MakeCurrent();
resetStatus = (GLContext::ContextResetARB) gl->fGetGraphicsResetStatus();
} else if (isEGL) {
// Simulate a ARB_robustness guilty context loss for when we
// get an EGL_CONTEXT_LOST error. It may not actually be guilty,
// but we can't make any distinction, so we must assume the worst
// case.
if (!gl->MakeCurrent(true) && gl->IsContextLost()) {
resetStatus = GLContext::CONTEXT_GUILTY_CONTEXT_RESET_ARB;
}
}
if (resetStatus != GLContext::CONTEXT_NO_ERROR) {
// It's already lost, but clean up after it and signal to JS that it is
// lost.
ForceLoseContext();
}
switch (resetStatus) {
case GLContext::CONTEXT_NO_ERROR:
// If there has been activity since the timer was set, it's possible
// that we did or are going to miss something, so clear this flag and
// run it again some time later.
if (mDrawSinceContextLossTimerSet)
SetupContextLossTimer();
break;
case GLContext::CONTEXT_GUILTY_CONTEXT_RESET_ARB:
NS_WARNING("WebGL content on the page caused the graphics card to reset; not restoring the context");
mAllowRestore = false;
break;
case GLContext::CONTEXT_INNOCENT_CONTEXT_RESET_ARB:
break;
case GLContext::CONTEXT_UNKNOWN_CONTEXT_RESET_ARB:
NS_WARNING("WebGL content on the page might have caused the graphics card to reset");
if (isEGL && isANGLE) {
// If we're using ANGLE, we ONLY get back UNKNOWN context resets, including for guilty contexts.
// This means that we can't restore it or risk restoring a guilty context. Should this ever change,
// we can get rid of the whole IsANGLE() junk from GLContext.h since, as of writing, this is the
// only use for it. See ANGLE issue 261.
mAllowRestore = false;
}
break;
}
}
void
WebGLContext::ForceLoseContext()
{
if (mContextStatus == ContextLostAwaitingEvent)
return;
printf_stderr("WebGL(%p)::ForceLoseContext\n", this);
MOZ_ASSERT(!IsContextLost());
mContextStatus = ContextLostAwaitingEvent;
// Queue up a task to restore the event.
SetupContextLossTimer();
mContextLostErrorSet = false;
mLastLossWasSimulated = false;
// Burn it all!
DestroyResourcesAndContext();
// Queue up a task, since we know the status changed.
EnqueueUpdateContextLossStatus();
}
void
WebGLContext::ForceRestoreContext()
{
printf_stderr("WebGL(%p)::ForceRestoreContext\n", this);
mContextStatus = ContextLostAwaitingRestore;
mAllowContextRestore = true; // Hey, you did say 'force'.
// Queue up a task, since we know the status changed.
EnqueueUpdateContextLossStatus();
}
void

View File

@ -202,9 +202,6 @@ public:
int32_t x, int32_t y, int32_t w, int32_t h)
{ return NS_ERROR_NOT_IMPLEMENTED; }
bool LoseContext();
bool RestoreContext();
void SynthesizeGLError(GLenum err);
void SynthesizeGLError(GLenum err, const char *fmt, ...);
@ -261,11 +258,14 @@ public:
bool MinCapabilityMode() const { return mMinCapability; }
void RobustnessTimerCallback(nsITimer* timer);
static void RobustnessTimerCallbackStatic(nsITimer* timer, void *thisPointer);
void SetupContextLossTimer();
void UpdateContextLossStatus();
void EnqueueUpdateContextLossStatus();
static void ContextLossCallbackStatic(nsITimer* timer, void* thisPointer);
void RunContextLossTimer();
void TerminateContextLossTimer();
bool TryToRestoreContext();
void AssertCachedBindings();
void AssertCachedState();
@ -666,8 +666,12 @@ public:
bool ValidateSamplerUniformSetter(const char* info,
WebGLUniformLocation *location,
GLint value);
void Viewport(GLint x, GLint y, GLsizei width, GLsizei height);
// -----------------------------------------------------------------------------
// WEBGL_lose_context
public:
void LoseContext();
void RestoreContext();
// -----------------------------------------------------------------------------
// Asynchronous Queries (WebGLContextAsyncQueries.cpp)
@ -869,7 +873,6 @@ protected:
bool mOptionsFrozen;
bool mMinCapability;
bool mDisableExtensions;
bool mHasRobustness;
bool mIsMesa;
bool mLoseContextOnHeapMinimize;
bool mCanLoseContextInForeground;
@ -1116,7 +1119,6 @@ protected:
GLenum type,
const GLvoid *data);
void MaybeRestoreContext();
void ForceLoseContext();
void ForceRestoreContext();
@ -1178,7 +1180,7 @@ protected:
GLint mStencilRefFront, mStencilRefBack;
GLuint mStencilValueMaskFront, mStencilValueMaskBack,
mStencilWriteMaskFront, mStencilWriteMaskBack;
mStencilWriteMaskFront, mStencilWriteMaskBack;
realGLboolean mColorWriteMask[4];
realGLboolean mDepthWriteMask;
GLfloat mColorClearValue[4];
@ -1192,9 +1194,10 @@ protected:
bool mAlreadyWarnedAboutViewportLargerThanDest;
nsCOMPtr<nsITimer> mContextRestorer;
bool mAllowRestore;
bool mAllowContextRestore;
bool mLastLossWasSimulated;
bool mContextLossTimerRunning;
bool mDrawSinceContextLossTimerSet;
bool mRunContextLossTimerAgain;
ContextStatus mContextStatus;
bool mContextLostErrorSet;

View File

@ -128,7 +128,7 @@ WebGLContext::DrawArrays(GLenum mode, GLint first, GLsizei count)
if (!DrawArrays_check(first, count, 1, "drawArrays"))
return;
SetupContextLossTimer();
RunContextLossTimer();
gl->fDrawArrays(mode, first, count);
Draw_cleanup();
@ -149,7 +149,7 @@ WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsiz
if (!DrawInstanced_check("drawArraysInstanced"))
return;
SetupContextLossTimer();
RunContextLossTimer();
gl->fDrawArraysInstanced(mode, first, count, primcount);
Draw_cleanup();
@ -296,7 +296,7 @@ WebGLContext::DrawElements(GLenum mode, GLsizei count, GLenum type,
return;
}
SetupContextLossTimer();
RunContextLossTimer();
if (gl->IsSupported(gl::GLFeature::draw_range_elements)) {
gl->fDrawRangeElements(mode, 0, upperBound,
@ -324,7 +324,7 @@ WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei count, GLenum type,
if (!DrawInstanced_check("drawElementsInstanced"))
return;
SetupContextLossTimer();
RunContextLossTimer();
gl->fDrawElementsInstanced(mode, count, type, reinterpret_cast<GLvoid*>(byteOffset), primcount);
Draw_cleanup();

View File

@ -3857,27 +3857,34 @@ WebGLContext::TexSubImage2D(GLenum target, GLint level,
WebGLTexelFormat::RGBA8, false);
}
bool
void
WebGLContext::LoseContext()
{
if (IsContextLost())
return false;
return ErrorInvalidOperation("loseContext: Context is already lost.");
ForceLoseContext();
return true;
mLastLossWasSimulated = true;
}
bool
void
WebGLContext::RestoreContext()
{
if (!IsContextLost() || !mAllowRestore) {
return false;
if (!IsContextLost())
return ErrorInvalidOperation("restoreContext: Context is not lost.");
if (!mLastLossWasSimulated) {
return ErrorInvalidOperation("restoreContext: Context loss was not simulated."
" Cannot simulate restore.");
}
// If we're currently lost, and the last loss was simulated, then
// we're currently only simulated-lost, allowing us to call
// restoreContext().
if (!mAllowContextRestore)
return ErrorInvalidOperation("restoreContext: Context cannot be restored.");
ForceRestoreContext();
return true;
}
bool

View File

@ -8,33 +8,45 @@
using namespace mozilla;
/* static */ void
WebGLContext::RobustnessTimerCallbackStatic(nsITimer* timer, void *thisPointer) {
static_cast<WebGLContext*>(thisPointer)->RobustnessTimerCallback(timer);
WebGLContext::ContextLossCallbackStatic(nsITimer* timer, void* thisPointer)
{
(void)timer;
WebGLContext* context = static_cast<WebGLContext*>(thisPointer);
context->TerminateContextLossTimer();
context->UpdateContextLossStatus();
}
void
WebGLContext::SetupContextLossTimer() {
WebGLContext::RunContextLossTimer()
{
// If the timer was already running, don't restart it here. Instead,
// wait until the previous call is done, then fire it one more time.
// This is an optimization to prevent unnecessary cross-communication
// between threads.
// This is an optimization to prevent unnecessary
// cross-communication between threads.
if (mContextLossTimerRunning) {
mDrawSinceContextLossTimerSet = true;
mRunContextLossTimerAgain = true;
return;
}
mContextRestorer->InitWithFuncCallback(RobustnessTimerCallbackStatic,
static_cast<void*>(this),
1000,
nsITimer::TYPE_ONE_SHOT);
mContextRestorer->InitWithFuncCallback(ContextLossCallbackStatic,
static_cast<void*>(this),
1000,
nsITimer::TYPE_ONE_SHOT);
mContextLossTimerRunning = true;
mDrawSinceContextLossTimerSet = false;
mRunContextLossTimerAgain = false;
}
void
WebGLContext::TerminateContextLossTimer() {
if (mContextLossTimerRunning) {
mContextRestorer->Cancel();
mContextLossTimerRunning = false;
WebGLContext::TerminateContextLossTimer()
{
if (!mContextLossTimerRunning)
return;
mContextRestorer->Cancel();
mContextLossTimerRunning = false;
if (mRunContextLossTimerAgain) {
RunContextLossTimer();
}
}

View File

@ -21,15 +21,13 @@ WebGLExtensionLoseContext::~WebGLExtensionLoseContext()
void
WebGLExtensionLoseContext::LoseContext()
{
if (!mContext->LoseContext())
mContext->mWebGLError = LOCAL_GL_INVALID_OPERATION;
mContext->LoseContext();
}
void
void
WebGLExtensionLoseContext::RestoreContext()
{
if (!mContext->RestoreContext())
mContext->mWebGLError = LOCAL_GL_INVALID_OPERATION;
mContext->RestoreContext();
}
IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionLoseContext)

View File

@ -99,7 +99,7 @@ function testLosingAndRestoringContext()
// restore the context after this event has exited.
setTimeout(function() {
shouldGenerateGLError(gl, gl.NO_ERROR, "extension.restoreContext()");
// The context should still be lost. It will not get restored until the
// The context should still be lost. It will not get restored until the
// webglrestorecontext event is fired.
shouldBeTrue("gl.isContextLost()");
shouldBe("gl.getError()", "gl.NO_ERROR");

View File

@ -2699,19 +2699,6 @@ public:
GLint GetMaxTextureImageSize() { return mMaxTextureImageSize; }
public:
/**
* Context reset constants.
* These are used to determine who is guilty when a context reset
* happens.
*/
enum ContextResetARB {
CONTEXT_NO_ERROR = 0,
CONTEXT_GUILTY_CONTEXT_RESET_ARB = 0x8253,
CONTEXT_INNOCENT_CONTEXT_RESET_ARB = 0x8254,
CONTEXT_UNKNOWN_CONTEXT_RESET_ARB = 0x8255
};
public:
std::map<GLuint, SharedSurface_GL*> mFBOMapping;