diff --git a/content/canvas/src/WebGLContext.cpp b/content/canvas/src/WebGLContext.cpp index 387a161a9d92..6ffbdec3884f 100644 --- a/content/canvas/src/WebGLContext.cpp +++ b/content/canvas/src/WebGLContext.cpp @@ -83,21 +83,18 @@ 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), @@ -171,12 +168,12 @@ WebGLContext::WebGLContext() WebGLMemoryTracker::AddWebGLContext(this); - mAllowContextRestore = true; - mLastLossWasSimulated = false; + mAllowRestore = true; mContextLossTimerRunning = false; - mRunContextLossTimerAgain = false; + mDrawSinceContextLossTimerSet = false; mContextRestorer = do_CreateInstance("@mozilla.org/timer;1"); mContextStatus = ContextNotLost; + mContextLostErrorSet = false; mLoseContextOnHeapMinimize = false; mCanLoseContextInForeground = true; @@ -574,8 +571,11 @@ 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 @@ -1111,96 +1111,6 @@ 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 @@ -1216,123 +1126,137 @@ WebGLContext::EnqueueUpdateContextLossStatus() // At a bare minimum, from context lost to context restores, it would take 3 // full timer iterations: detection, webglcontextlost, webglcontextrestored. void -WebGLContext::UpdateContextLossStatus() +WebGLContext::RobustnessTimerCallback(nsITimer* timer) { + 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) { - // The context has been lost and we haven't yet triggered the - // callback, so do that now. - - bool useDefaultHandler; + bool defaultAction; nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(), static_cast(mCanvasElement), NS_LITERAL_STRING("webglcontextlost"), true, true, - &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; + &defaultAction); - // Fall through. - } + // If the script didn't handle the event, we don't allow restores. + if (defaultAction) + mAllowRestore = false; - 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. + // 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 { 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() { - printf_stderr("WebGL(%p)::ForceLoseContext\n", this); - MOZ_ASSERT(!IsContextLost()); + if (mContextStatus == ContextLostAwaitingEvent) + return; + mContextStatus = ContextLostAwaitingEvent; - mContextLostErrorSet = false; - mLastLossWasSimulated = false; - - // Burn it all! + // Queue up a task to restore the event. + SetupContextLossTimer(); 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 7343b2b5e274..7a0b38888327 100644 --- a/content/canvas/src/WebGLContext.h +++ b/content/canvas/src/WebGLContext.h @@ -200,6 +200,9 @@ 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, ...); @@ -256,14 +259,11 @@ public: bool MinCapabilityMode() const { return mMinCapability; } - void UpdateContextLossStatus(); - void EnqueueUpdateContextLossStatus(); - static void ContextLossCallbackStatic(nsITimer* timer, void* thisPointer); - void RunContextLossTimer(); + void RobustnessTimerCallback(nsITimer* timer); + static void RobustnessTimerCallbackStatic(nsITimer* timer, void *thisPointer); + void SetupContextLossTimer(); void TerminateContextLossTimer(); - bool TryToRestoreContext(); - void AssertCachedBindings(); void AssertCachedState(); @@ -653,12 +653,8 @@ 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) @@ -856,6 +852,7 @@ protected: bool mOptionsFrozen; bool mMinCapability; bool mDisableExtensions; + bool mHasRobustness; bool mIsMesa; bool mLoseContextOnHeapMinimize; bool mCanLoseContextInForeground; @@ -1102,6 +1099,7 @@ protected: GLenum type, const GLvoid *data); + void MaybeRestoreContext(); void ForceLoseContext(); void ForceRestoreContext(); @@ -1163,7 +1161,7 @@ protected: GLint mStencilRefFront, mStencilRefBack; GLuint mStencilValueMaskFront, mStencilValueMaskBack, - mStencilWriteMaskFront, mStencilWriteMaskBack; + mStencilWriteMaskFront, mStencilWriteMaskBack; realGLboolean mColorWriteMask[4]; realGLboolean mDepthWriteMask; GLfloat mColorClearValue[4]; @@ -1177,10 +1175,9 @@ protected: bool mAlreadyWarnedAboutViewportLargerThanDest; nsCOMPtr mContextRestorer; - bool mAllowContextRestore; - bool mLastLossWasSimulated; + bool mAllowRestore; bool mContextLossTimerRunning; - bool mRunContextLossTimerAgain; + bool mDrawSinceContextLossTimerSet; ContextStatus mContextStatus; bool mContextLostErrorSet; diff --git a/content/canvas/src/WebGLContextDraw.cpp b/content/canvas/src/WebGLContextDraw.cpp index 80133b751cb9..7e6ac4f99414 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; - RunContextLossTimer(); + SetupContextLossTimer(); 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; - RunContextLossTimer(); + SetupContextLossTimer(); gl->fDrawArraysInstanced(mode, first, count, primcount); Draw_cleanup(); @@ -296,7 +296,7 @@ WebGLContext::DrawElements(GLenum mode, GLsizei count, GLenum type, return; } - RunContextLossTimer(); + SetupContextLossTimer(); 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; - RunContextLossTimer(); + SetupContextLossTimer(); 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 1c64d4bdc90e..4d9dc17c52d6 100644 --- a/content/canvas/src/WebGLContextGL.cpp +++ b/content/canvas/src/WebGLContextGL.cpp @@ -3827,34 +3827,27 @@ WebGLContext::TexSubImage2D(GLenum target, GLint level, WebGLTexelFormat::RGBA8, false); } -void +bool WebGLContext::LoseContext() { if (IsContextLost()) - return ErrorInvalidOperation("loseContext: Context is already lost."); + return false; ForceLoseContext(); - mLastLossWasSimulated = true; + + return true; } -void +bool WebGLContext::RestoreContext() { - if (!IsContextLost()) - return ErrorInvalidOperation("restoreContext: Context is not lost."); - - if (!mLastLossWasSimulated) { - return ErrorInvalidOperation("restoreContext: Context loss was not simulated." - " Cannot simulate restore."); + if (!IsContextLost() || !mAllowRestore) { + return false; } - // 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 6e6ff6c50a0e..4c98b5bf6c89 100644 --- a/content/canvas/src/WebGLContextLossTimer.cpp +++ b/content/canvas/src/WebGLContextLossTimer.cpp @@ -8,45 +8,33 @@ using namespace mozilla; /* static */ void -WebGLContext::ContextLossCallbackStatic(nsITimer* timer, void* thisPointer) -{ - (void)timer; - WebGLContext* context = static_cast(thisPointer); - - context->TerminateContextLossTimer(); - - context->UpdateContextLossStatus(); +WebGLContext::RobustnessTimerCallbackStatic(nsITimer* timer, void *thisPointer) { + static_cast(thisPointer)->RobustnessTimerCallback(timer); } void -WebGLContext::RunContextLossTimer() -{ +WebGLContext::SetupContextLossTimer() { // 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) { - mRunContextLossTimerAgain = true; + mDrawSinceContextLossTimerSet = true; return; } - mContextRestorer->InitWithFuncCallback(ContextLossCallbackStatic, - static_cast(this), - 1000, - nsITimer::TYPE_ONE_SHOT); + + mContextRestorer->InitWithFuncCallback(RobustnessTimerCallbackStatic, + static_cast(this), + 1000, + nsITimer::TYPE_ONE_SHOT); mContextLossTimerRunning = true; - mRunContextLossTimerAgain = false; + mDrawSinceContextLossTimerSet = false; } void -WebGLContext::TerminateContextLossTimer() -{ - if (!mContextLossTimerRunning) - return; - - mContextRestorer->Cancel(); - mContextLossTimerRunning = false; - - if (mRunContextLossTimerAgain) { - RunContextLossTimer(); +WebGLContext::TerminateContextLossTimer() { + if (mContextLossTimerRunning) { + mContextRestorer->Cancel(); + mContextLossTimerRunning = false; } } diff --git a/content/canvas/src/WebGLContextValidate.cpp b/content/canvas/src/WebGLContextValidate.cpp index 86f1a2db2d42..5917181d7608 100644 --- a/content/canvas/src/WebGLContextValidate.cpp +++ b/content/canvas/src/WebGLContextValidate.cpp @@ -1647,10 +1647,6 @@ WebGLContext::InitAndValidateGL() mStencilWriteMaskFront = 0xffffffff; mStencilWriteMaskBack = 0xffffffff; - mDitherEnabled = false; - mRasterizerDiscardEnabled = false; - mScissorTestEnabled = false; - // Bindings, etc. mActiveTexture = 0; mEmitContextLostErrorOnce = true; diff --git a/content/canvas/src/WebGLExtensionLoseContext.cpp b/content/canvas/src/WebGLExtensionLoseContext.cpp index 8871d6b408bf..e36eb76af7d4 100644 --- a/content/canvas/src/WebGLExtensionLoseContext.cpp +++ b/content/canvas/src/WebGLExtensionLoseContext.cpp @@ -21,13 +21,15 @@ WebGLExtensionLoseContext::~WebGLExtensionLoseContext() void WebGLExtensionLoseContext::LoseContext() { - mContext->LoseContext(); + if (!mContext->LoseContext()) + mContext->mWebGLError = LOCAL_GL_INVALID_OPERATION; } -void +void WebGLExtensionLoseContext::RestoreContext() { - mContext->RestoreContext(); + if (!mContext->RestoreContext()) + mContext->mWebGLError = LOCAL_GL_INVALID_OPERATION; } 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 c50edd07a482..a70d11c52d99 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 c0c1161fdbcb..066e1c19e1d5 100644 --- a/gfx/gl/GLContext.h +++ b/gfx/gl/GLContext.h @@ -2698,6 +2698,19 @@ 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;