Bug 1102667 - Fix our float texture/rb/fb support. - r=kamidphish
authorjdashg <jdashg+github@gmail.com>
Fri, 21 Nov 2014 15:04:08 -0800
changeset 241608 e8fb97477d9ef999141b764dd4dd8828f5d3c4d6
parent 241607 b93c921507218eebdc0d16fb5d2d88b4b229fb09
child 241609 d95090fbf3dc7d2830e6f022dee1dc982561e759
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskamidphish
bugs1102667
milestone36.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 1102667 - Fix our float texture/rb/fb support. - r=kamidphish
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextExtensions.cpp
dom/canvas/WebGLExtensionColorBufferFloat.cpp
dom/canvas/WebGLExtensionColorBufferHalfFloat.cpp
dom/canvas/WebGLFramebuffer.cpp
dom/canvas/test/webgl-mochitest.ini
dom/canvas/test/webgl-mochitest/test_implicit_color_buffer_float.html
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1079,16 +1079,19 @@ protected:
     int32_t mGLMaxDrawBuffers;
     uint32_t mGLMaxTransformFeedbackSeparateAttribs;
 
 public:
     GLuint MaxVertexAttribs() const {
         return mGLMaxVertexAttribs;
     }
 
+
+    bool IsFormatValidForFB(GLenum sizedFormat) const;
+
 protected:
     // Represents current status of the context with respect to context loss.
     // That is, whether the context is lost, and what part of the context loss
     // process we currently are 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.
@@ -1111,16 +1114,20 @@ protected:
                             WebGLExtensionID::Max,
                             nsRefPtr<WebGLExtensionBase>> ExtensionsArrayType;
 
     ExtensionsArrayType mExtensions;
 
     // enable an extension. the extension should not be enabled before.
     void EnableExtension(WebGLExtensionID ext);
 
+    // Enable an extension if it's supported. Return the extension on success.
+    WebGLExtensionBase* EnableSupportedExtension(JSContext* js,
+                                                 WebGLExtensionID ext);
+
     // returns true if the extension has been enabled by calling getExtension.
     bool IsExtensionEnabled(WebGLExtensionID ext) const;
 
     // returns true if the extension is supported for this JSContext (this decides what getSupportedExtensions exposes)
     bool IsExtensionSupported(JSContext *cx, WebGLExtensionID ext) const;
     bool IsExtensionSupported(WebGLExtensionID ext) const;
 
     static const char* GetExtensionString(WebGLExtensionID ext);
--- a/dom/canvas/WebGLContextExtensions.cpp
+++ b/dom/canvas/WebGLContextExtensions.cpp
@@ -181,43 +181,54 @@ bool WebGLContext::IsExtensionSupported(
 }
 
 static bool
 CompareWebGLExtensionName(const nsACString& name, const char *other)
 {
     return name.Equals(other, nsCaseInsensitiveCStringComparator());
 }
 
+WebGLExtensionBase*
+WebGLContext::EnableSupportedExtension(JSContext* js, WebGLExtensionID ext)
+{
+    if (!IsExtensionEnabled(ext)) {
+        if (!IsExtensionSupported(js, ext))
+            return nullptr;
+
+        EnableExtension(ext);
+    }
+
+    return mExtensions[ext];
+}
+
 void
 WebGLContext::GetExtension(JSContext *cx, const nsAString& aName,
                            JS::MutableHandle<JSObject*> aRetval,
                            ErrorResult& rv)
 {
     if (IsContextLost()) {
         aRetval.set(nullptr);
         return;
     }
 
     NS_LossyConvertUTF16toASCII name(aName);
 
     WebGLExtensionID ext = WebGLExtensionID::Unknown;
 
     // step 1: figure what extension is wanted
-    for (size_t i = 0; i < size_t(WebGLExtensionID::Max); i++)
-    {
+    for (size_t i = 0; i < size_t(WebGLExtensionID::Max); i++) {
         WebGLExtensionID extension = WebGLExtensionID(i);
 
         if (CompareWebGLExtensionName(name, GetExtensionString(extension))) {
             ext = extension;
             break;
         }
     }
 
-    if (ext == WebGLExtensionID::Unknown)
-    {
+    if (ext == WebGLExtensionID::Unknown) {
         /**
          * We keep backward compatibility for these deprecated vendor-prefixed
          * alias. Do not add new ones anymore. Hide it behind the
          * webgl.enable-draft-extensions flag instead.
          */
         if (CompareWebGLExtensionName(name, "MOZ_WEBGL_lose_context")) {
             ext = WebGLExtensionID::WEBGL_lose_context;
         }
@@ -249,21 +260,37 @@ WebGLContext::GetExtension(JSContext *cx
 
     // step 2: check if the extension is supported
     if (!IsExtensionSupported(cx, ext)) {
         aRetval.set(nullptr);
         return;
     }
 
     // step 3: if the extension hadn't been previously been created, create it now, thus enabling it
-    if (!IsExtensionEnabled(ext)) {
-        EnableExtension(ext);
+    WebGLExtensionBase* extObj = EnableSupportedExtension(cx, ext);
+    if (!extObj) {
+        aRetval.set(nullptr);
+        return;
     }
 
-    aRetval.set(WebGLObjectAsJSObject(cx, mExtensions[ext].get(), rv));
+    // Step 4: Enable any implied extensions.
+    switch (ext) {
+    case WebGLExtensionID::OES_texture_float:
+        EnableSupportedExtension(cx, WebGLExtensionID::WEBGL_color_buffer_float);
+        break;
+
+    case WebGLExtensionID::OES_texture_half_float:
+        EnableSupportedExtension(cx, WebGLExtensionID::EXT_color_buffer_half_float);
+        break;
+
+    default:
+        break;
+    }
+
+    aRetval.set(WebGLObjectAsJSObject(cx, extObj, rv));
 }
 
 void
 WebGLContext::EnableExtension(WebGLExtensionID ext)
 {
     MOZ_ASSERT(IsExtensionEnabled(ext) == false);
 
     WebGLExtensionBase* obj = nullptr;
--- a/dom/canvas/WebGLExtensionColorBufferFloat.cpp
+++ b/dom/canvas/WebGLExtensionColorBufferFloat.cpp
@@ -18,13 +18,18 @@ WebGLExtensionColorBufferFloat::WebGLExt
 
 WebGLExtensionColorBufferFloat::~WebGLExtensionColorBufferFloat()
 {
 }
 
 bool
 WebGLExtensionColorBufferFloat::IsSupported(const WebGLContext* context)
 {
-    return context->GL()->IsSupported(gl::GLFeature::renderbuffer_color_float) &&
-           context->GL()->IsSupported(gl::GLFeature::frag_color_float);
+    gl::GLContext* gl = context->GL();
+
+    // ANGLE supports this, but doesn't have a way to advertize its support,
+    // since it's compliant with WEBGL_color_buffer_float's clamping, but not
+    // EXT_color_buffer_float.
+    return gl->IsSupported(gl::GLFeature::renderbuffer_color_float) ||
+           gl->IsANGLE();
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionColorBufferFloat)
--- a/dom/canvas/WebGLExtensionColorBufferHalfFloat.cpp
+++ b/dom/canvas/WebGLExtensionColorBufferHalfFloat.cpp
@@ -18,13 +18,15 @@ WebGLExtensionColorBufferHalfFloat::WebG
 
 WebGLExtensionColorBufferHalfFloat::~WebGLExtensionColorBufferHalfFloat()
 {
 }
 
 bool
 WebGLExtensionColorBufferHalfFloat::IsSupported(const WebGLContext* context)
 {
-    return context->GL()->IsSupported(gl::GLFeature::renderbuffer_color_half_float) &&
-           context->GL()->IsSupported(gl::GLFeature::frag_color_float);
+    gl::GLContext* gl = context->GL();
+
+    // ANGLE doesn't support ReadPixels from a RGBA16F with RGBA/FLOAT.
+    return gl->IsSupported(gl::GLFeature::renderbuffer_color_half_float);
 }
 
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionColorBufferHalfFloat)
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -279,16 +279,46 @@ IsValidFBORenderbufferDepthStencilFormat
 
 static inline bool
 IsValidFBORenderbufferStencilFormat(GLenum internalFormat)
 {
     return internalFormat == LOCAL_GL_STENCIL_INDEX8;
 }
 
 bool
+WebGLContext::IsFormatValidForFB(GLenum sizedFormat) const
+{
+    switch (sizedFormat) {
+    case LOCAL_GL_ALPHA8:
+    case LOCAL_GL_LUMINANCE8:
+    case LOCAL_GL_LUMINANCE8_ALPHA8:
+    case LOCAL_GL_RGB8:
+    case LOCAL_GL_RGBA8:
+    case LOCAL_GL_RGB565:
+    case LOCAL_GL_RGB5_A1:
+    case LOCAL_GL_RGBA4:
+        return true;
+
+    case LOCAL_GL_SRGB8:
+    case LOCAL_GL_SRGB8_ALPHA8_EXT:
+        return IsExtensionEnabled(WebGLExtensionID::EXT_sRGB);
+
+    case LOCAL_GL_RGB32F:
+    case LOCAL_GL_RGBA32F:
+        return IsExtensionEnabled(WebGLExtensionID::WEBGL_color_buffer_float);
+
+    case LOCAL_GL_RGB16F:
+    case LOCAL_GL_RGBA16F:
+        return IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float);
+    }
+
+    return false;
+}
+
+bool
 WebGLFramebuffer::Attachment::IsComplete() const
 {
     if (!HasImage())
         return false;
 
     const WebGLRectangleObject& rect = RectangleObject();
 
     if (!rect.Width() ||
@@ -296,33 +326,34 @@ WebGLFramebuffer::Attachment::IsComplete
     {
         return false;
     }
 
     if (Texture()) {
         MOZ_ASSERT(Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel));
         const WebGLTexture::ImageInfo& imageInfo =
             Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel);
-        GLenum internalformat = imageInfo.EffectiveInternalFormat().get();
+        GLenum sizedFormat = imageInfo.EffectiveInternalFormat().get();
 
         if (mAttachmentPoint == LOCAL_GL_DEPTH_ATTACHMENT)
-            return IsValidFBOTextureDepthFormat(internalformat);
+            return IsValidFBOTextureDepthFormat(sizedFormat);
 
         if (mAttachmentPoint == LOCAL_GL_STENCIL_ATTACHMENT)
             return false; // Textures can't have the correct format for stencil buffers
 
         if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-            return IsValidFBOTextureDepthStencilFormat(internalformat);
+            return IsValidFBOTextureDepthStencilFormat(sizedFormat);
         }
 
         if (mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0 &&
             mAttachmentPoint <= FBAttachment(LOCAL_GL_COLOR_ATTACHMENT0 - 1 +
                                              WebGLContext::kMaxColorAttachments))
         {
-            return IsValidFBOTextureColorFormat(internalformat);
+            WebGLContext* webgl = Texture()->Context();
+            return webgl->IsFormatValidForFB(sizedFormat);
         }
         MOZ_ASSERT(false, "Invalid WebGL attachment point?");
         return false;
     }
 
     if (Renderbuffer()) {
         GLenum internalFormat = Renderbuffer()->InternalFormat();
 
@@ -334,17 +365,18 @@ WebGLFramebuffer::Attachment::IsComplete
 
         if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
             return IsValidFBORenderbufferDepthStencilFormat(internalFormat);
 
         if (mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0 &&
             mAttachmentPoint <= FBAttachment(LOCAL_GL_COLOR_ATTACHMENT0 - 1 +
                                              WebGLContext::kMaxColorAttachments))
         {
-            return IsValidFBORenderbufferColorFormat(internalFormat);
+            WebGLContext* webgl = Renderbuffer()->Context();
+            return webgl->IsFormatValidForFB(internalFormat);
         }
         MOZ_ASSERT(false, "Invalid WebGL attachment point?");
         return false;
     }
 
     MOZ_ASSERT(false, "Should not get here.");
     return false;
 }
--- a/dom/canvas/test/webgl-mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest.ini
@@ -8,16 +8,17 @@ support-files =
 [webgl-mochitest/test_backbuffer_channels.html]
 fail-if = (os == 'b2g')
 [webgl-mochitest/test_depth_readpixels.html]
 [webgl-mochitest/test_draw.html]
 [webgl-mochitest/test_fb_param.html]
 [webgl-mochitest/test_fb_param_crash.html]
 [webgl-mochitest/test_hidden_alpha.html]
 skip-if = (os == 'b2g') || buildapp == 'mulet' # Mulet - bug 1093639 (crashes in libLLVM-3.0.so)
+[webgl-mochitest/test_implicit_color_buffer_float.html]
 [webgl-mochitest/test_highp_fs.html]
 [webgl-mochitest/test_no_arr_points.html]
 skip-if = android_version == '10' #Android 2.3 aws only; bug 1030942
 [webgl-mochitest/test_noprog_draw.html]
 [webgl-mochitest/test_privileged_exts.html]
 [webgl-mochitest/test_texsubimage_float.html]
 [webgl-mochitest/test_webgl_available.html]
 skip-if = toolkit == 'android' #bug 865443- seperate suite - the non_conf* tests pass except for one on armv6 tests
new file mode 100644
--- /dev/null
+++ b/dom/canvas/test/webgl-mochitest/test_implicit_color_buffer_float.html
@@ -0,0 +1,199 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset='utf-8'>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+
+var RGBA32F_EXT = 0x8814;
+var RGBA16F_EXT = 0x881A; // Yep, it's really 4 and A.
+var HALF_FLOAT_OES = 0x8D61;
+
+function IsFormatValidForRB(gl, format) {
+    ok(!gl.getError(), 'Should have no errors here.');
+
+    var rb = gl.createRenderbuffer();
+    gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+    gl.renderbufferStorage(gl.RENDERBUFFER, format, 4, 4);
+
+    var error = gl.getError();
+    if (error == gl.INVALID_ENUM)
+        return false;
+
+    ok(error == gl.NO_ERROR, 'Error should be INVALID_ENUM or NO_ERROR.');
+    return error == gl.NO_ERROR;
+}
+
+function IsFormatValidForTex(gl, format, type) {
+    ok(!gl.getError(), 'Should have no errors here.');
+
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    gl.texImage2D(gl.TEXTURE_2D, 0, format, 4, 4, 0, format, type, null);
+
+    var error = gl.getError();
+    if (error == gl.INVALID_ENUM)
+        return false;
+
+    ok(error == gl.NO_ERROR, 'Error should be INVALID_ENUM or NO_ERROR.');
+    return error == gl.NO_ERROR;
+}
+
+function IsFormatValidForTexFB(gl, format, type) {
+    ok(!gl.getError(), 'Should have no errors here.');
+
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    gl.texImage2D(gl.TEXTURE_2D, 0, format, 4, 4, 0, format, type, null);
+
+    var error = gl.getError();
+    if (error == gl.INVALID_ENUM)
+        return false;
+
+    ok(error == gl.NO_ERROR, 'Error should be INVALID_ENUM or NO_ERROR.');
+
+    var fb = gl.createFramebuffer();
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D,
+                            tex, 0);
+    error = gl.getError();
+    ok(error == gl.NO_ERROR, 'Error should be NO_ERROR.');
+
+    var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+    return status == gl.FRAMEBUFFER_COMPLETE;
+}
+
+function IsFormatValidForTexFBRead(gl, texFormat, texType, readType) {
+    ok(!gl.getError(), 'Should have no errors here.');
+
+    var tex = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D, tex);
+    gl.texImage2D(gl.TEXTURE_2D, 0, texFormat, 4, 4, 0, texFormat, texType,
+                  null);
+
+    var error = gl.getError();
+    if (error == gl.INVALID_ENUM)
+        return false;
+
+    ok(error == gl.NO_ERROR, 'Error should be INVALID_ENUM or NO_ERROR.');
+
+    var fb = gl.createFramebuffer();
+    gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D,
+                            tex, 0);
+    error = gl.getError();
+    ok(error == gl.NO_ERROR, 'Error should be NO_ERROR.');
+
+    var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+    if (status != gl.FRAMEBUFFER_COMPLETE)
+        return false;
+
+    var data;
+    switch (readType) {
+    case gl.UNSIGNED_BYTE:
+        data = new Uint8Array(4);
+        break;
+    case HALF_FLOAT_OES:
+        data = new Uint16Array(4);
+        break;
+    case gl.FLOAT:
+        data = new Float32Array(4);
+        break;
+    default:
+        throw 'Bad `readType`.';
+    }
+    gl.readPixels(0, 0, 1, 1, gl.RGBA, readType, data);
+
+    error = gl.getError();
+    return error == gl.NO_ERROR;
+}
+
+function TestColorBufferExt(gl, rbFormat, texFormat, texType, readType)
+{
+    var isTexFBValid = IsFormatValidForTexFB(gl, texFormat, texType);
+    var isTexFBReadValid = IsFormatValidForTexFBRead(gl, texFormat, texType,
+                                                     readType);
+    var isRBValid = IsFormatValidForRB(gl, rbFormat);
+
+    var validSubsetCount = isTexFBValid + isTexFBReadValid + isRBValid;
+
+    if (validSubsetCount) {
+        ok(isTexFBValid, 'If active, texture-fbs should work.');
+        ok(isTexFBReadValid, 'If active, reading texture-fbs should work.');
+        ok(isRBValid, 'If active, renderbuffers should work.');
+    }
+
+    return validSubsetCount == 3;
+}
+
+function TestImpliedExtension(gl, baseExtName, impliedExtName, rbFormat,
+                              texFormat, texType, readType)
+{
+    ok(true, '========');
+    ok(true, 'Testing if ' + baseExtName + ' implies ' + impliedExtName + '.');
+    ok(true, '--------');
+
+    var baseExt = gl.getExtension(baseExtName);
+    if (!baseExt) {
+        ok(!baseExt, 'Ext \'' + baseExtName + '\' can be unsupported.');
+        return;
+    }
+
+    var isTexValid = IsFormatValidForTex(gl, texFormat, texType);
+    ok(isTexValid, baseExtName + ' should allow float textures.');
+    if (!isTexValid)
+        return;
+
+    var isImplicitlyActive = TestColorBufferExt(gl, rbFormat, texFormat,
+                                                texType, readType);
+
+    if (isImplicitlyActive) {
+        ok(true, 'Activating ' + baseExtName + ' has implicitly activated ' +
+           impliedExtName + '.');
+
+        var impliedExt = gl.getExtension(impliedExtName);
+        ok(impliedExt, 'If ' + impliedExtName + ' is supported implicitly, it' +
+           ' must be supported explicitly as well.');
+        return;
+    }
+
+    ok(true, 'Activating ' + baseExtName + ' has not implicitly activated ' +
+       impliedExtName + '.');
+    ok(true, '--------');
+
+    var impliedExt = gl.getExtension(impliedExtName);
+    if (!impliedExt) {
+        ok(true, impliedExtName + ' can be unsupported.');
+        return;
+    }
+    ok(true, 'Explicit activation of ' + impliedExtName + ' successful.');
+
+    var isFunctional = TestColorBufferExt(gl, rbFormat, texFormat, texType,
+                                          readType);
+    ok(isFunctional, impliedExtName + ' should be fully functional.');
+}
+
+(function() {
+    var canvas = document.createElement('canvas');
+    var gl = canvas.getContext('experimental-webgl');
+    if (!gl) {
+        ok(!gl, 'WebGL can be unsupported.');
+        return;
+    }
+
+    TestImpliedExtension(gl, 'OES_texture_float', 'WEBGL_color_buffer_float',
+                         RGBA32F_EXT, gl.RGBA, gl.FLOAT, gl.FLOAT);
+    TestImpliedExtension(gl, 'OES_texture_half_float',
+                         'EXT_color_buffer_half_float', RGBA16F_EXT, gl.RGBA,
+                         HALF_FLOAT_OES, gl.FLOAT);
+    ok(true, '========');
+    ok(true, 'TEST COMPLETE');
+})();
+
+</script>
+
+</body>
+</html>