Bug 710163: fix EXT_lose_context semantics r=bjacob
authorDoug Sherk <dsherk@mozilla.com>
Wed, 04 Jan 2012 16:12:03 -0500
changeset 83807 8e3d4ad9e413e9d84ecf5ca20a76823e3096a54a
parent 83764 0cdaf07730732e1a2523d8fbed9810de3da4d7a6
child 83808 23f3b97f655a4e7db3df368d9c9a205bfde84e79
push id21798
push usermak77@bonardo.net
push dateThu, 05 Jan 2012 14:53:22 +0000
treeherdermozilla-central@595fba5b92d4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbjacob
bugs710163
milestone12.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 710163: fix EXT_lose_context semantics r=bjacob The EXT_lose_context extension spec has had updates from Khronos which break our current implementation. Primarily, it is mostly asynchronous now with more heavily defined behavior. NOTE: This patch will not pass on our current copy of context-lost.html and context-lost-restored.html see bug for more info.
content/canvas/src/WebGLContext.cpp
content/canvas/src/WebGLContext.h
content/canvas/src/WebGLContextGL.cpp
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -287,21 +287,23 @@ WebGLContext::WebGLContext()
     mGLMaxVertexUniformVectors = 0;
     
     // See OpenGL ES 2.0.25 spec, 6.2 State Tables, table 6.13
     mPixelStorePackAlignment = 4;
     mPixelStoreUnpackAlignment = 4;
 
     WebGLMemoryReporter::AddWebGLContext(this);
 
-    mContextLost = false;
-    mAllowRestore = false;
+    mAllowRestore = true;
     mRobustnessTimerRunning = false;
     mDrawSinceRobustnessTimerSet = false;
     mContextRestorer = do_CreateInstance("@mozilla.org/timer;1");
+    mContextStatus = ContextStable;
+    mContextLostErrorSet = false;
+    mContextLostDueToTest = false;
 }
 
 WebGLContext::~WebGLContext()
 {
     DestroyResourcesAndContext();
     WebGLMemoryReporter::RemoveWebGLContext(this);
     TerminateRobustnessTimer();
     mContextRestorer = nsnull;
@@ -943,17 +945,17 @@ bool WebGLContext::IsExtensionSupported(
             MakeContextCurrent();
             isSupported = gl->IsExtensionSupported(gl->IsGLES2() ? GLContext::OES_texture_float 
                                                                  : GLContext::ARB_texture_float);
 	    break;
         case WebGL_OES_standard_derivatives:
             // We always support this extension.
             isSupported = true;
             break;
-        case WebGL_WEBGL_EXT_lose_context:
+        case WebGL_MOZ_WEBGL_lose_context:
             // We always support this extension.
             isSupported = true;
             break;
         default:
             isSupported = false;
     }
 
     return isSupported;
@@ -975,28 +977,28 @@ WebGLContext::GetExtension(const nsAStri
     if (aName.EqualsLiteral("OES_texture_float")) {
         if (IsExtensionSupported(WebGL_OES_texture_float))
             ei = WebGL_OES_texture_float;
     }
     else if (aName.EqualsLiteral("OES_standard_derivatives")) {
         if (IsExtensionSupported(WebGL_OES_standard_derivatives))
             ei = WebGL_OES_standard_derivatives;
     }
-    else if (aName.EqualsLiteral("WEBGL_EXT_lose_context")) {
-        if (IsExtensionSupported(WebGL_WEBGL_EXT_lose_context))
-            ei = WebGL_WEBGL_EXT_lose_context;
+    else if (aName.EqualsLiteral("MOZ_WEBGL_lose_context")) {
+        if (IsExtensionSupported(WebGL_MOZ_WEBGL_lose_context))
+            ei = WebGL_MOZ_WEBGL_lose_context;
     }
 
     if (ei != WebGLExtensionID_Max) {
         if (!IsExtensionEnabled(ei)) {
             switch (ei) {
                 case WebGL_OES_standard_derivatives:
                     mEnabledExtensions[ei] = new WebGLExtensionStandardDerivatives(this);
                     break;
-                case WebGL_WEBGL_EXT_lose_context:
+                case WebGL_MOZ_WEBGL_lose_context:
                     mEnabledExtensions[ei] = new WebGLExtensionLoseContext(this);
                     break;
                 // create an extension for any types that don't
                 // have any additional tokens or methods
                 default:
                     mEnabledExtensions[ei] = new WebGLExtension(this);
                     break;
             }
@@ -1092,39 +1094,105 @@ WebGLContext::EnsureBackbufferClearedAsN
     ForceClearFramebufferWithDefaultValues(LOCAL_GL_COLOR_BUFFER_BIT |
                                            LOCAL_GL_DEPTH_BUFFER_BIT |
                                            LOCAL_GL_STENCIL_BUFFER_BIT,
                                            nsIntRect(0, 0, mWidth, mHeight));
 
     Invalidate();
 }
 
+// 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
+//    CONTEXT_LOST_WEBGL error has been triggered.
+// 3) If we are using ANGLE, or anything that supports ARB_robustness, query the
+//    GPU periodically to see if the reset status bit has been set.
+// In all of these situations, we use this timer to send the script context lost
+// and restored events asynchronously. For example, if it triggers a context loss,
+// the webglcontextlost event will be sent to it the next time the robustness timer
+// fires.
+// Note that this timer mechanism is not used unless one of these 3 criteria
+// are met.
+// At a bare minimum, from context lost to context restores, it would take 3
+// full timer iterations: detection, webglcontextlost, webglcontextrestored.
 NS_IMETHODIMP
 WebGLContext::Notify(nsITimer* timer)
 {
     TerminateRobustnessTimer();
+    // If the context has been lost and we're waiting for it to be restored, do
+    // that now.
+    if (mContextStatus == ContextLostAwaitingEvent) {
+        bool defaultAction;
+        nsContentUtils::DispatchTrustedEvent(HTMLCanvasElement()->OwnerDoc(),
+                                             (nsIDOMHTMLCanvasElement*) HTMLCanvasElement(),
+                                             NS_LITERAL_STRING("webglcontextlost"),
+                                             PR_TRUE,
+                                             PR_TRUE,
+                                             &defaultAction);
+
+        // If the script didn't handle the event, we don't allow restores.
+        if (defaultAction)
+            mAllowRestore = false;
+
+        // 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.
+            SetupRobustnessTimer();
+        } else {
+            mContextStatus = ContextLost;
+        }
+    } else if (mContextStatus == ContextLostAwaitingRestore) {
+        // Try to restore the context. If it fails, try again later.
+        if (NS_FAILED(SetDimensions(mWidth, mHeight))) {
+            SetupRobustnessTimer();
+            return NS_OK;
+        }
+        mContextStatus = ContextStable;
+        nsContentUtils::DispatchTrustedEvent(HTMLCanvasElement()->OwnerDoc(),
+                                             (nsIDOMHTMLCanvasElement*) HTMLCanvasElement(),
+                                             NS_LITERAL_STRING("webglcontextrestored"),
+                                             PR_TRUE,
+                                             PR_TRUE);
+        // Set all flags back to the state they were in before the context was
+        // lost.
+        mContextLostErrorSet = false;
+        mContextLostDueToTest = false;
+        mAllowRestore = true;
+    }
+
     MaybeRestoreContext();
     return NS_OK;
 }
 
 void
 WebGLContext::MaybeRestoreContext()
 {
-    if (mContextLost || mAllowRestore)
+    // Don't try to handle it if we already know it's busted.
+    if (mContextStatus != ContextStable || gl == nsnull)
         return;
 
     bool isEGL = gl->GetContextType() == GLContext::ContextTypeEGL,
          isANGLE = gl->IsANGLE();
 
+    // If was lost due to a forced context loss, don't try to handle it.
+    // Also, we also don't try to handle if if we don't have robustness.
+    // Note that the code in this function is used only for situations where
+    // we have an actual context loss, and not a simulated one.
+    if (mContextLostDueToTest ||
+        (!mHasRobustness && !isEGL))
+        return;
+
     GLContext::ContextResetARB resetStatus = GLContext::CONTEXT_NO_ERROR;
     if (mHasRobustness) {
         gl->MakeCurrent();
         resetStatus = (GLContext::ContextResetARB) gl->fGetGraphicsResetStatus();
-    // This call is safe as it does not actually interact with GL, so the
-    // context does not have to be current.
     } 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;
         }
@@ -1138,68 +1206,49 @@ WebGLContext::MaybeRestoreContext()
 
     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 (mDrawSinceRobustnessTimerSet)
                 SetupRobustnessTimer();
-            return;
+            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");
-            return;
+            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.
-                return;
+                mAllowRestore = false;
             }
             break;
     }
-
-    ForceRestoreContext();
 }
 
 void
 WebGLContext::ForceLoseContext()
 {
-    TerminateRobustnessTimer();
-
-    mWebGLError = LOCAL_GL_CONTEXT_LOST;
-
-    bool defaultAction;
-    mContextLost = true;
+    mContextStatus = ContextLostAwaitingEvent;
+    // Queue up a task to restore the event.
+    SetupRobustnessTimer();
     DestroyResourcesAndContext();
-    nsContentUtils::DispatchTrustedEvent(HTMLCanvasElement()->OwnerDoc(), 
-                                         (nsIDOMHTMLCanvasElement*) HTMLCanvasElement(), 
-                                         NS_LITERAL_STRING("webglcontextlost"), 
-                                         PR_TRUE, 
-                                         PR_TRUE,
-                                         &defaultAction);
-    if (defaultAction)
-        mAllowRestore = false;
 }
 
 void
 WebGLContext::ForceRestoreContext()
 {
-    mContextLost = false;
-    mAllowRestore = false;
-    SetDimensions(mHeight, mWidth);
-    nsContentUtils::DispatchTrustedEvent(HTMLCanvasElement()->OwnerDoc(), 
-                                         (nsIDOMHTMLCanvasElement*) HTMLCanvasElement(), 
-                                         NS_LITERAL_STRING("webglcontextrestored"), 
-                                         PR_TRUE, 
-                                         PR_TRUE);
+    mContextStatus = ContextLostAwaitingRestore;
 }
 
 //
 // XPCOM goop
 //
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebGLContext)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext)
@@ -1466,18 +1515,18 @@ WebGLContext::GetSupportedExtensions(nsI
     NS_ENSURE_TRUE(wrval, NS_ERROR_FAILURE);
 
     nsTArray<const char *> extList;
 
     if (IsExtensionSupported(WebGL_OES_texture_float))
         extList.InsertElementAt(extList.Length(), "OES_texture_float");
     if (IsExtensionSupported(WebGL_OES_standard_derivatives))
         extList.InsertElementAt(extList.Length(), "OES_standard_derivatives");
-    if (IsExtensionSupported(WebGL_WEBGL_EXT_lose_context))
-        extList.InsertElementAt(extList.Length(), "WEBGL_EXT_lose_context");
+    if (IsExtensionSupported(WebGL_MOZ_WEBGL_lose_context))
+        extList.InsertElementAt(extList.Length(), "MOZ_WEBGL_lose_context");
 
     nsresult rv;
     if (extList.Length() > 0) {
         rv = wrval->SetAsArray(nsIDataType::VTYPE_CHAR_STR, nsnull,
                                extList.Length(), &extList[0]);
     } else {
         rv = wrval->SetAsEmptyArray();
     }
@@ -1486,12 +1535,18 @@ WebGLContext::GetSupportedExtensions(nsI
 
     *retval = wrval.forget().get();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::IsContextLost(WebGLboolean *retval)
 {
-    *retval = mContextLost;
+    *retval = mContextStatus != ContextStable;
     return NS_OK;
 }
 
+// Internalized version of IsContextLost.
+bool
+WebGLContext::IsContextStable()
+{
+    return mContextStatus == ContextStable;
+}
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -570,20 +570,27 @@ public:
         GLenum currentGLError;
         UpdateWebGLErrorAndClearGLError(&currentGLError);
     }
     
     bool MinCapabilityMode() const {
         return mMinCapability;
     }
 
+    // See the comment over WebGLContext::Notify() for more information on this.
+    bool ShouldEnableRobustnessTimer() {
+        return mHasRobustness ||
+               IsExtensionEnabled(WebGL_MOZ_WEBGL_lose_context) ||
+               (gl != nsnull && gl->GetContextType() == gl::GLContext::ContextTypeEGL);
+    }
+
     // Sets up the GL_ARB_robustness timer if it isn't already, so that if the
     // driver gets restarted, the context may get reset with it.
     void SetupRobustnessTimer() {
-        if (mContextLost || (!mHasRobustness && gl->GetContextType() != gl::GLContext::ContextTypeEGL))
+        if (!ShouldEnableRobustnessTimer())
             return;
 
         // 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.
         if (mRobustnessTimerRunning) {
             mDrawSinceRobustnessTimerSet = true;
@@ -663,21 +670,40 @@ protected:
     PRInt32 mGLMaxTextureSize;
     PRInt32 mGLMaxCubeMapTextureSize;
     PRInt32 mGLMaxTextureImageUnits;
     PRInt32 mGLMaxVertexTextureImageUnits;
     PRInt32 mGLMaxVaryingVectors;
     PRInt32 mGLMaxFragmentUniformVectors;
     PRInt32 mGLMaxVertexUniformVectors;
 
+    // Represents current status, or state, of the context. That is, is it lost
+    // or stable and what part of the context lost process are we currently at.
+    // This is used to support the WebGL spec's asyncronous nature in handling
+    // context loss.
+    enum ContextStatus {
+        // The context is stable; there either are none or we don't know of any.
+        ContextStable,
+        // The context has been lost, but we have not yet sent an event to the
+        // script informing it of this.
+        ContextLostAwaitingEvent,
+        // The context has been lost, and we have sent the script an event
+        // informing it of this.
+        ContextLost,
+        // The context is lost, an event has been sent to the script, and the
+        // script correctly handled the event. We are waiting for the context to
+        // be restored.
+        ContextLostAwaitingRestore
+    };
+
     // extensions
     enum WebGLExtensionID {
         WebGL_OES_texture_float,
         WebGL_OES_standard_derivatives,
-        WebGL_WEBGL_EXT_lose_context,
+        WebGL_MOZ_WEBGL_lose_context,
         WebGLExtensionID_Max
     };
     nsCOMPtr<WebGLExtension> mEnabledExtensions[WebGLExtensionID_Max];
     bool IsExtensionEnabled(WebGLExtensionID ext) const {
         NS_ABORT_IF_FALSE(ext >= 0 && ext < WebGLExtensionID_Max, "bogus index!");
         return mEnabledExtensions[ext] != nsnull;
     }
     bool IsExtensionSupported(WebGLExtensionID ei);
@@ -800,16 +826,17 @@ protected:
                              GLsizei width,
                              GLsizei height,
                              GLint border,
                              GLenum format,
                              GLenum type,
                              const GLvoid *data);
 
     void MaybeRestoreContext();
+    bool IsContextStable();
     void ForceLoseContext();
     void ForceRestoreContext();
 
     // the buffers bound to the current program's attribs
     nsTArray<WebGLVertexAttribData> mAttribBuffers;
 
     nsTArray<WebGLRefPtr<WebGLTexture> > mBound2DTextures;
     nsTArray<WebGLRefPtr<WebGLTexture> > mBoundCubeMapTextures;
@@ -856,20 +883,22 @@ protected:
     realGLboolean mDitherEnabled;
     WebGLfloat mColorClearValue[4];
     WebGLint mStencilClearValue;
     WebGLfloat mDepthClearValue;
 
     int mBackbufferClearingStatus;
 
     nsCOMPtr<nsITimer> mContextRestorer;
-    bool mContextLost;
     bool mAllowRestore;
     bool mRobustnessTimerRunning;
     bool mDrawSinceRobustnessTimerSet;
+    ContextStatus mContextStatus;
+    bool mContextLostErrorSet;
+    bool mContextLostDueToTest;
 
 public:
     // console logging helpers
     static void LogMessage(const char *fmt, ...);
     static void LogMessage(const char *fmt, va_list ap);
     void LogMessageIfVerbose(const char *fmt, ...);
     void LogMessageIfVerbose(const char *fmt, va_list ap);
 
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -2528,19 +2528,22 @@ WebGLContext::CreateTexture(nsIWebGLText
     NS_ADDREF(*retval = globj);
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 WebGLContext::GetError(WebGLenum *_retval)
 {
-    if (!mContextLost) {
+    if (mContextStatus == ContextStable) {
         MakeContextCurrent();
         UpdateWebGLErrorAndClearGLError();
+    } else if (!mContextLostErrorSet) {
+        mWebGLError = LOCAL_GL_CONTEXT_LOST;
+        mContextLostErrorSet = true;
     }
 
     *_retval = mWebGLError;
     mWebGLError = LOCAL_GL_NO_ERROR;
 
     return NS_OK;
 }
 
@@ -5140,30 +5143,29 @@ WebGLContext::TexSubImage2D_dom(WebGLenu
                               isurf->Data(), byteLength,
                               -1,
                               srcFormat, mPixelStorePremultiplyAlpha);
 }
 
 bool
 WebGLContext::LoseContext()
 {
-    if (mContextLost) {
+    if (mContextLost)
         return false;
-    }
-
-    mAllowRestore = true;
+
+    mContextLostDueToTest = true;
     ForceLoseContext();
 
     return true;
 }
 
 bool
 WebGLContext::RestoreContext()
 {
-    if (!mContextLost || !mAllowRestore) {
+    if (IsContextStable() || !mAllowRestore) {
         return false;
     }
 
     ForceRestoreContext();
 
     return true;
 }