diff --git a/content/canvas/src/WebGLContext.cpp b/content/canvas/src/WebGLContext.cpp index b8a0bfabd49..a23b55456a8 100644 --- a/content/canvas/src/WebGLContext.cpp +++ b/content/canvas/src/WebGLContext.cpp @@ -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 @@ -1110,6 +1110,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 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 @@ -1125,137 +1215,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(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(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 diff --git a/content/canvas/src/WebGLContext.h b/content/canvas/src/WebGLContext.h index 37127ea7519..4a8337dbd92 100644 --- a/content/canvas/src/WebGLContext.h +++ b/content/canvas/src/WebGLContext.h @@ -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 mContextRestorer; - bool mAllowRestore; + bool mAllowContextRestore; + bool mLastLossWasSimulated; bool mContextLossTimerRunning; - bool mDrawSinceContextLossTimerSet; + bool mRunContextLossTimerAgain; ContextStatus mContextStatus; bool mContextLostErrorSet; diff --git a/content/canvas/src/WebGLContextDraw.cpp b/content/canvas/src/WebGLContextDraw.cpp index 7e6ac4f9941..80133b751cb 100644 --- a/content/canvas/src/WebGLContextDraw.cpp +++ b/content/canvas/src/WebGLContextDraw.cpp @@ -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(byteOffset), primcount); Draw_cleanup(); diff --git a/content/canvas/src/WebGLContextGL.cpp b/content/canvas/src/WebGLContextGL.cpp index 3ef14e968eb..b77c2cde47e 100644 --- a/content/canvas/src/WebGLContextGL.cpp +++ b/content/canvas/src/WebGLContextGL.cpp @@ -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 diff --git a/content/canvas/src/WebGLContextLossTimer.cpp b/content/canvas/src/WebGLContextLossTimer.cpp index 4c98b5bf6c8..6e6ff6c50a0 100644 --- a/content/canvas/src/WebGLContextLossTimer.cpp +++ b/content/canvas/src/WebGLContextLossTimer.cpp @@ -8,33 +8,45 @@ using namespace mozilla; /* static */ void -WebGLContext::RobustnessTimerCallbackStatic(nsITimer* timer, void *thisPointer) { - static_cast(thisPointer)->RobustnessTimerCallback(timer); +WebGLContext::ContextLossCallbackStatic(nsITimer* timer, void* thisPointer) +{ + (void)timer; + WebGLContext* context = static_cast(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(this), - 1000, - nsITimer::TYPE_ONE_SHOT); + mContextRestorer->InitWithFuncCallback(ContextLossCallbackStatic, + static_cast(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(); } } diff --git a/content/canvas/src/WebGLExtensionLoseContext.cpp b/content/canvas/src/WebGLExtensionLoseContext.cpp index e36eb76af7d..8871d6b408b 100644 --- a/content/canvas/src/WebGLExtensionLoseContext.cpp +++ b/content/canvas/src/WebGLExtensionLoseContext.cpp @@ -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) diff --git a/content/canvas/test/webgl-conformance/conformance/context/context-lost-restored.html b/content/canvas/test/webgl-conformance/conformance/context/context-lost-restored.html index a70d11c52d9..c50edd07a48 100644 --- a/content/canvas/test/webgl-conformance/conformance/context/context-lost-restored.html +++ b/content/canvas/test/webgl-conformance/conformance/context/context-lost-restored.html @@ -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"); diff --git a/gfx/gl/GLContext.h b/gfx/gl/GLContext.h index cc06f03f6bf..8e1da7f78cd 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -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 mFBOMapping;