Bug 1094457 - Implement ReadBuffer and RenderbufferStorageMultisample. - r=kamidphish
authorJeff Gilbert <jgilbert@mozilla.com>
Wed, 18 Feb 2015 16:57:05 -0800
changeset 256870 688f46924277905d06c9576a80d1b04fcc1798e7
parent 256869 f74fcc98ea52950862fd00e117ac6d52bff38c6c
child 256871 5c7e45daf893d5a7de008ef0195c2c208794f7a6
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskamidphish
bugs1094457
milestone38.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 1094457 - Implement ReadBuffer and RenderbufferStorageMultisample. - r=kamidphish
dom/canvas/WebGL2ContextFramebuffers.cpp
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextGL.cpp
dom/canvas/WebGLContextUtils.cpp
dom/canvas/WebGLContextValidate.cpp
dom/canvas/WebGLFramebuffer.cpp
dom/canvas/WebGLFramebuffer.h
dom/canvas/WebGLRenderbuffer.cpp
dom/canvas/WebGLRenderbuffer.h
gfx/gl/GLContext.cpp
gfx/gl/GLContext.h
gfx/gl/GLContextFeatures.cpp
gfx/gl/GLScreenBuffer.cpp
gfx/gl/GLScreenBuffer.h
--- a/dom/canvas/WebGL2ContextFramebuffers.cpp
+++ b/dom/canvas/WebGL2ContextFramebuffers.cpp
@@ -454,17 +454,50 @@ WebGL2Context::InvalidateSubFramebuffer(
         gl->fInvalidateSubFramebuffer(target, attachments.Length(), attachments.Elements(),
                                       x, y, width, height);
     }
 }
 
 void
 WebGL2Context::ReadBuffer(GLenum mode)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    MakeContextCurrent();
+
+    if (mBoundReadFramebuffer) {
+        bool isColorAttachment = (mode >= LOCAL_GL_COLOR_ATTACHMENT0 &&
+                                  mode <= LastColorAttachment());
+        if (mode != LOCAL_GL_NONE &&
+            !isColorAttachment)
+        {
+            ErrorInvalidEnumInfo("readBuffer: If READ_FRAMEBUFFER is non-null,"
+                                 " `mode` must be COLOR_ATTACHMENTN or NONE."
+                                 " Was:", mode);
+            return;
+        }
+
+        gl->fReadBuffer(mode);
+        return;
+    }
+
+    // Operating on the default framebuffer.
+
+    if (mode != LOCAL_GL_NONE &&
+        mode != LOCAL_GL_BACK)
+    {
+        ErrorInvalidEnumInfo("readBuffer: If READ_FRAMEBUFFER is null, `mode`"
+                             " must be BACK or NONE. Was:", mode);
+        return;
+    }
+
+    gl->Screen()->SetReadBuffer(mode);
 }
 
 void
-WebGL2Context::RenderbufferStorageMultisample(GLenum target, GLsizei samples, GLenum internalformat,
+WebGL2Context::RenderbufferStorageMultisample(GLenum target, GLsizei samples,
+                                              GLenum internalFormat,
                                               GLsizei width, GLsizei height)
 {
-    MOZ_CRASH("Not Implemented.");
+    RenderbufferStorage_base("renderbufferStorageMultisample", target, samples,
+                              internalFormat, width, height);
 }
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -198,16 +198,17 @@ WebGLContextOptions::WebGLContextOptions
     // Set default alpha state based on preference.
     if (Preferences::GetBool("webgl.default-no-alpha", false))
         alpha = false;
 }
 
 WebGLContext::WebGLContext()
     : WebGLContextUnchecked(nullptr)
     , mBypassShaderValidation(false)
+    , mGLMaxSamples(1)
     , mNeedsFakeNoAlpha(false)
 {
     mGeneration = 0;
     mInvalidated = false;
     mShouldPresent = true;
     mResetLayer = true;
     mOptionsFrozen = false;
 
@@ -1716,16 +1717,17 @@ WebGLContext::GetSurfaceSnapshot(bool* o
     if (NS_WARN_IF(!surf)) {
         return nullptr;
     }
 
     gl->MakeCurrent();
     {
         ScopedBindFramebuffer autoFB(gl, 0);
         ClearBackbufferIfNeeded();
+        // TODO: Save, override, then restore glReadBuffer if present.
         ReadPixelsIntoDataSurface(gl, surf);
     }
 
     if (out_premultAlpha) {
         *out_premultAlpha = true;
     }
     bool srcPremultAlpha = mOptions.premultipliedAlpha;
     if (!srcPremultAlpha) {
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -249,17 +249,19 @@ public:
 
     void SynthesizeGLError(GLenum err);
     void SynthesizeGLError(GLenum err, const char* fmt, ...);
 
     void ErrorInvalidEnum(const char* fmt = 0, ...);
     void ErrorInvalidOperation(const char* fmt = 0, ...);
     void ErrorInvalidValue(const char* fmt = 0, ...);
     void ErrorInvalidFramebufferOperation(const char* fmt = 0, ...);
-    void ErrorInvalidEnumInfo(const char* info, GLenum enumvalue);
+    void ErrorInvalidEnumInfo(const char* info, GLenum enumValue);
+    void ErrorInvalidEnumInfo(const char* info, const char* funcName,
+                              GLenum enumValue);
     void ErrorOutOfMemory(const char* fmt = 0, ...);
 
     const char* ErrorName(GLenum error);
 
     /**
      * Return displayable name for GLenum.
      * This version is like gl::GLenumToStr but with out the GL_ prefix to
      * keep consistency with how errors are reported from WebGL.
@@ -526,16 +528,21 @@ public:
     void PixelStorei(GLenum pname, GLint param);
     void PolygonOffset(GLfloat factor, GLfloat units);
     void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
                     GLenum format, GLenum type,
                     const Nullable<dom::ArrayBufferView>& pixels,
                     ErrorResult& rv);
     void RenderbufferStorage(GLenum target, GLenum internalFormat,
                              GLsizei width, GLsizei height);
+protected:
+    void RenderbufferStorage_base(const char* funcName, GLenum target,
+                                  GLsizei samples, GLenum internalformat,
+                                  GLsizei width, GLsizei height);
+public:
     void SampleCoverage(GLclampf value, WebGLboolean invert);
     void Scissor(GLint x, GLint y, GLsizei width, GLsizei height);
     void ShaderSource(WebGLShader* shader, const nsAString& source);
     void StencilFunc(GLenum func, GLint ref, GLuint mask);
     void StencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask);
     void StencilMask(GLuint mask);
     void StencilMaskSeparate(GLenum face, GLuint mask);
     void StencilOp(GLenum sfail, GLenum dpfail, GLenum dppass);
@@ -1141,18 +1148,19 @@ protected:
     int32_t mGLMaxRenderbufferSize;
     int32_t mGLMaxTextureImageUnits;
     int32_t mGLMaxVertexTextureImageUnits;
     int32_t mGLMaxVaryingVectors;
     int32_t mGLMaxFragmentUniformVectors;
     int32_t mGLMaxVertexUniformVectors;
     int32_t mGLMaxColorAttachments;
     int32_t mGLMaxDrawBuffers;
-    GLuint  mGLMaxTransformFeedbackSeparateAttribs;
+    uint32_t  mGLMaxTransformFeedbackSeparateAttribs;
     GLuint  mGLMaxUniformBufferBindings;
+    GLsizei mGLMaxSamples;
 
 public:
     GLuint MaxVertexAttribs() const {
         return mGLMaxVertexAttribs;
     }
 
     GLuint GLMaxTextureUnits() const {
         return mGLMaxTextureUnits;
@@ -1417,16 +1425,20 @@ protected:
     nsTArray<WebGLRefPtr<WebGLTexture> > mBoundCubeMapTextures;
     nsTArray<WebGLRefPtr<WebGLTexture> > mBound3DTextures;
 
     WebGLRefPtr<WebGLProgram> mCurrentProgram;
     RefPtr<const webgl::LinkedProgramInfo> mActiveProgramLinkInfo;
 
     uint32_t mMaxFramebufferColorAttachments;
 
+    GLenum LastColorAttachment() const {
+        return LOCAL_GL_COLOR_ATTACHMENT0 + mMaxFramebufferColorAttachments - 1;
+    }
+
     bool ValidateFramebufferTarget(GLenum target, const char* const info);
 
     WebGLRefPtr<WebGLFramebuffer> mBoundDrawFramebuffer;
     WebGLRefPtr<WebGLFramebuffer> mBoundReadFramebuffer;
     WebGLRefPtr<WebGLRenderbuffer> mBoundRenderbuffer;
     WebGLRefPtr<WebGLTransformFeedback> mBoundTransformFeedback;
     WebGLRefPtr<WebGLVertexArray> mBoundVertexArray;
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -1965,43 +1965,38 @@ WebGLContext::ReadPixels(GLint x, GLint 
         return ErrorInvalidOperation("readPixels: buffer too small");
 
     void* data = pixbuf.Data();
     if (!data) {
         ErrorOutOfMemory("readPixels: buffer storage is null. Did we run out of memory?");
         return rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     }
 
-    bool isSourceTypeFloat = false;
-    if (mBoundReadFramebuffer &&
-        mBoundReadFramebuffer->ColorAttachmentCount() &&
-        mBoundReadFramebuffer->ColorAttachment(0).IsDefined())
-    {
-        isSourceTypeFloat = mBoundReadFramebuffer->ColorAttachment(0).IsReadableFloat();
+    MakeContextCurrent();
+
+    bool isSourceTypeFloat;
+    if (mBoundReadFramebuffer) {
+        TexInternalFormat srcFormat;
+        if (!mBoundReadFramebuffer->ValidateForRead("readPixels", &srcFormat))
+            return;
+
+        MOZ_ASSERT(srcFormat != LOCAL_GL_NONE);
+        TexType type = TypeFromInternalFormat(srcFormat);
+        isSourceTypeFloat = (type == LOCAL_GL_FLOAT ||
+                             type == LOCAL_GL_HALF_FLOAT);
+    } else {
+        ClearBackbufferIfNeeded();
+
+        isSourceTypeFloat = false;
     }
 
     if (isReadTypeFloat != isSourceTypeFloat)
         return ErrorInvalidOperation("readPixels: Invalid type floatness");
 
     // Check the format and type params to assure they are an acceptable pair (as per spec)
-    MakeContextCurrent();
-
-    if (mBoundReadFramebuffer) {
-        // prevent readback of arbitrary video memory through uninitialized renderbuffers!
-        if (!mBoundReadFramebuffer->CheckAndInitializeAttachments())
-            return ErrorInvalidFramebufferOperation("readPixels: incomplete framebuffer");
-
-        GLenum readPlaneBits = LOCAL_GL_COLOR_BUFFER_BIT;
-        if (!mBoundReadFramebuffer->HasCompletePlanes(readPlaneBits)) {
-            return ErrorInvalidOperation("readPixels: Read source attachment doesn't have the"
-                                         " correct color/depth/stencil type.");
-        }
-    } else {
-      ClearBackbufferIfNeeded();
-    }
 
     bool isFormatAndTypeValid = false;
 
     // OpenGL ES 2.0 $4.3.1 - IMPLEMENTATION_COLOR_READ_{TYPE/FORMAT} is a valid
     // combination for glReadPixels().
     if (gl->IsSupported(gl::GLFeature::ES2_compatibility)) {
         GLenum implType = 0;
         GLenum implFormat = 0;
@@ -2123,106 +2118,167 @@ WebGLContext::ReadPixels(GLint x, GLint 
 
     size_t stride = checked_alignedRowSize.value(); // In bytes!
     if (!SetFullAlpha(data, format, type, width, height, stride)) {
         return rv.Throw(NS_ERROR_FAILURE);
     }
 }
 
 void
-WebGLContext::RenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height)
+WebGLContext::RenderbufferStorage_base(const char* funcName, GLenum target,
+                                       GLsizei samples,
+                                       GLenum internalFormat, GLsizei width,
+                                       GLsizei height)
 {
     if (IsContextLost())
         return;
 
-    if (!mBoundRenderbuffer)
-        return ErrorInvalidOperation("renderbufferStorage called on renderbuffer 0");
-
-    if (target != LOCAL_GL_RENDERBUFFER)
-        return ErrorInvalidEnumInfo("renderbufferStorage: target", target);
-
-    if (width < 0 || height < 0)
-        return ErrorInvalidValue("renderbufferStorage: width and height must be >= 0");
-
-    if (width > mGLMaxRenderbufferSize || height > mGLMaxRenderbufferSize)
-        return ErrorInvalidValue("renderbufferStorage: width or height exceeds maximum renderbuffer size");
+    if (!mBoundRenderbuffer) {
+        ErrorInvalidOperation("%s: Called on renderbuffer 0.", funcName);
+        return;
+    }
+
+    if (target != LOCAL_GL_RENDERBUFFER) {
+        ErrorInvalidEnumInfo("`target`", funcName, target);
+        return;
+    }
+
+    if (samples < 0 || samples > mGLMaxSamples) {
+        ErrorInvalidValue("%s: `samples` is out of the valid range.", funcName);
+        return;
+    }
+
+    if (width < 0 || height < 0) {
+        ErrorInvalidValue("%s: Width and height must be >= 0.", funcName);
+        return;
+    }
+
+    if (width > mGLMaxRenderbufferSize || height > mGLMaxRenderbufferSize) {
+        ErrorInvalidValue("%s: Width or height exceeds maximum renderbuffer"
+                          " size.", funcName);
+        return;
+    }
+
+    bool isFormatValid = false;
+    switch (internalFormat) {
+    case LOCAL_GL_RGBA4:
+    case LOCAL_GL_RGB5_A1:
+    case LOCAL_GL_RGB565:
+    case LOCAL_GL_DEPTH_COMPONENT16:
+    case LOCAL_GL_STENCIL_INDEX8:
+    case LOCAL_GL_DEPTH_STENCIL:
+        isFormatValid = true;
+        break;
+
+    case LOCAL_GL_SRGB8_ALPHA8_EXT:
+        if (IsExtensionEnabled(WebGLExtensionID::EXT_sRGB))
+            isFormatValid = true;
+        break;
+
+    case LOCAL_GL_RGB16F:
+    case LOCAL_GL_RGBA16F:
+        if (IsExtensionEnabled(WebGLExtensionID::OES_texture_half_float) &&
+            IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float))
+        {
+            isFormatValid = true;
+        }
+        break;
+
+    case LOCAL_GL_RGB32F:
+    case LOCAL_GL_RGBA32F:
+        if (IsExtensionEnabled(WebGLExtensionID::OES_texture_float) &&
+            IsExtensionEnabled(WebGLExtensionID::WEBGL_color_buffer_float))
+        {
+            isFormatValid = true;
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    if (!isFormatValid) {
+        ErrorInvalidEnumInfo("`internalFormat`", funcName, internalFormat);
+        return;
+    }
 
     // certain OpenGL ES renderbuffer formats may not exist on desktop OpenGL
-    GLenum internalformatForGL = internalformat;
-
-    switch (internalformat) {
+    GLenum internalFormatForGL = internalFormat;
+
+    switch (internalFormat) {
     case LOCAL_GL_RGBA4:
     case LOCAL_GL_RGB5_A1:
         // 16-bit RGBA formats are not supported on desktop GL
-        if (!gl->IsGLES()) internalformatForGL = LOCAL_GL_RGBA8;
+        if (!gl->IsGLES())
+            internalFormatForGL = LOCAL_GL_RGBA8;
         break;
+
     case LOCAL_GL_RGB565:
         // the RGB565 format is not supported on desktop GL
-        if (!gl->IsGLES()) internalformatForGL = LOCAL_GL_RGB8;
+        if (!gl->IsGLES())
+            internalFormatForGL = LOCAL_GL_RGB8;
         break;
+
     case LOCAL_GL_DEPTH_COMPONENT16:
         if (!gl->IsGLES() || gl->IsExtensionSupported(gl::GLContext::OES_depth24))
-            internalformatForGL = LOCAL_GL_DEPTH_COMPONENT24;
+            internalFormatForGL = LOCAL_GL_DEPTH_COMPONENT24;
         else if (gl->IsExtensionSupported(gl::GLContext::OES_packed_depth_stencil))
-            internalformatForGL = LOCAL_GL_DEPTH24_STENCIL8;
+            internalFormatForGL = LOCAL_GL_DEPTH24_STENCIL8;
         break;
-    case LOCAL_GL_STENCIL_INDEX8:
-        break;
+
     case LOCAL_GL_DEPTH_STENCIL:
         // We emulate this in WebGLRenderbuffer if we don't have the requisite extension.
-        internalformatForGL = LOCAL_GL_DEPTH24_STENCIL8;
-        break;
-    case LOCAL_GL_SRGB8_ALPHA8_EXT:
+        internalFormatForGL = LOCAL_GL_DEPTH24_STENCIL8;
         break;
-    case LOCAL_GL_RGB16F:
-    case LOCAL_GL_RGBA16F: {
-        bool hasExtensions = IsExtensionEnabled(WebGLExtensionID::OES_texture_half_float) &&
-                             IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float);
-        if (!hasExtensions)
-            return ErrorInvalidEnumInfo("renderbufferStorage: internalformat", internalformat);
+
+    default:
         break;
     }
-    case LOCAL_GL_RGB32F:
-    case LOCAL_GL_RGBA32F: {
-        bool hasExtensions = IsExtensionEnabled(WebGLExtensionID::OES_texture_float) &&
-                             IsExtensionEnabled(WebGLExtensionID::WEBGL_color_buffer_float);
-        if (!hasExtensions)
-            return ErrorInvalidEnumInfo("renderbufferStorage: internalformat", internalformat);
-        break;
-    }
-    default:
-        return ErrorInvalidEnumInfo("renderbufferStorage: internalformat", internalformat);
-    }
+
+    // Validation complete.
 
     MakeContextCurrent();
 
-    bool sizeChanges = width != mBoundRenderbuffer->Width() ||
-                       height != mBoundRenderbuffer->Height() ||
-                       internalformat != mBoundRenderbuffer->InternalFormat();
-    if (sizeChanges) {
+    bool willRealloc = samples != mBoundRenderbuffer->Samples() ||
+                       internalFormat != mBoundRenderbuffer->InternalFormat() ||
+                       width != mBoundRenderbuffer->Width() ||
+                       height != mBoundRenderbuffer->Height();
+
+    if (willRealloc) {
         // Invalidate framebuffer status cache
         mBoundRenderbuffer->NotifyFBsStatusChanged();
         GetAndFlushUnderlyingGLErrors();
-        mBoundRenderbuffer->RenderbufferStorage(internalformatForGL, width, height);
+        mBoundRenderbuffer->RenderbufferStorage(samples, internalFormatForGL,
+                                                width, height);
         GLenum error = GetAndFlushUnderlyingGLErrors();
         if (error) {
-            GenerateWarning("renderbufferStorage generated error %s", ErrorName(error));
+            GenerateWarning("%s generated error %s", funcName,
+                            ErrorName(error));
             return;
         }
     } else {
-        mBoundRenderbuffer->RenderbufferStorage(internalformatForGL, width, height);
+        mBoundRenderbuffer->RenderbufferStorage(samples, internalFormatForGL,
+                                                width, height);
     }
 
-    mBoundRenderbuffer->SetInternalFormat(internalformat);
-    mBoundRenderbuffer->SetInternalFormatForGL(internalformatForGL);
+    mBoundRenderbuffer->SetSamples(samples);
+    mBoundRenderbuffer->SetInternalFormat(internalFormat);
+    mBoundRenderbuffer->SetInternalFormatForGL(internalFormatForGL);
     mBoundRenderbuffer->setDimensions(width, height);
     mBoundRenderbuffer->SetImageDataStatus(WebGLImageDataStatus::UninitializedImageData);
 }
 
 void
+WebGLContext::RenderbufferStorage(GLenum target, GLenum internalFormat, GLsizei width, GLsizei height)
+{
+    RenderbufferStorage_base("renderbufferStorage", target, 0,
+                             internalFormat, width, height);
+}
+
+void
 WebGLContext::Scissor(GLint x, GLint y, GLsizei width, GLsizei height)
 {
     if (IsContextLost())
         return;
 
     if (width < 0 || height < 0)
         return ErrorInvalidValue("scissor: negative size");
 
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -496,22 +496,33 @@ WebGLContext::ErrorInvalidEnum(const cha
     va_start(va, fmt);
     GenerateWarning(fmt, va);
     va_end(va);
 
     return SynthesizeGLError(LOCAL_GL_INVALID_ENUM);
 }
 
 void
-WebGLContext::ErrorInvalidEnumInfo(const char* info, GLenum enumvalue)
+WebGLContext::ErrorInvalidEnumInfo(const char* info, GLenum enumValue)
 {
     nsCString name;
-    EnumName(enumvalue, &name);
+    EnumName(enumValue, &name);
+
+    return ErrorInvalidEnum("%s: invalid enum value %s", info, name.BeginReading());
+}
 
-    return ErrorInvalidEnum("%s: invalid enum value %s", info, name.get());
+void
+WebGLContext::ErrorInvalidEnumInfo(const char* info, const char* funcName,
+                                   GLenum enumValue)
+{
+    nsCString name;
+    EnumName(enumValue, &name);
+
+    ErrorInvalidEnum("%s: %s: Invalid enum: 0x%04x (%s).", funcName, info,
+                     enumValue, name.BeginReading());
 }
 
 void
 WebGLContext::ErrorInvalidOperation(const char* fmt, ...)
 {
     va_list va;
     va_start(va, fmt);
     GenerateWarning(fmt, va);
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -1281,35 +1281,21 @@ WebGLContext::ValidateCopyTexImage(GLenu
                                    WebGLTexDimensions dims)
 {
     MOZ_ASSERT(IsCopyFunc(func));
 
     // Default framebuffer format
     GLenum fboFormat = mOptions.alpha ? LOCAL_GL_RGBA : LOCAL_GL_RGB;
 
     if (mBoundReadFramebuffer) {
-        if (!mBoundReadFramebuffer->CheckAndInitializeAttachments()) {
-            ErrorInvalidFramebufferOperation("%s: Incomplete framebuffer.",
-                                             InfoFrom(func, dims));
+        TexInternalFormat srcFormat;
+        if (!mBoundReadFramebuffer->ValidateForRead(InfoFrom(func, dims), &srcFormat))
             return false;
-        }
 
-        GLenum readPlaneBits = LOCAL_GL_COLOR_BUFFER_BIT;
-        if (!mBoundReadFramebuffer->HasCompletePlanes(readPlaneBits)) {
-            ErrorInvalidOperation("%s: Read source attachment doesn't have the"
-                                  " correct color/depth/stencil type.",
-                                  InfoFrom(func, dims));
-            return false;
-        }
-
-        // Get the correct format for the framebuffer, as it's not the default one.
-        const WebGLFramebuffer::Attachment& color0 =
-            mBoundReadFramebuffer->GetAttachment(LOCAL_GL_COLOR_ATTACHMENT0);
-
-        fboFormat = mBoundReadFramebuffer->GetFormatForAttachment(color0);
+        fboFormat = srcFormat.get();
     }
 
     // Make sure the format of the framebuffer is a superset of the format
     // requested by the CopyTex[Sub]Image2D functions.
     const GLComponents formatComps = GLComponents(format);
     const GLComponents fboComps = GLComponents(fboFormat);
     if (!formatComps.IsSubsetOf(fboComps)) {
         ErrorInvalidOperation("%s: Format %s is not a subset of the current"
@@ -1813,22 +1799,26 @@ WebGLContext::InitAndValidateGL()
     mBound3DTextures.SetLength(mGLMaxTextureUnits);
 
     if (MinCapabilityMode()) {
         mGLMaxTextureSize = MINVALUE_GL_MAX_TEXTURE_SIZE;
         mGLMaxCubeMapTextureSize = MINVALUE_GL_MAX_CUBE_MAP_TEXTURE_SIZE;
         mGLMaxRenderbufferSize = MINVALUE_GL_MAX_RENDERBUFFER_SIZE;
         mGLMaxTextureImageUnits = MINVALUE_GL_MAX_TEXTURE_IMAGE_UNITS;
         mGLMaxVertexTextureImageUnits = MINVALUE_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS;
+        mGLMaxSamples = 1;
     } else {
         gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &mGLMaxTextureSize);
         gl->fGetIntegerv(LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE, &mGLMaxCubeMapTextureSize);
         gl->fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, &mGLMaxRenderbufferSize);
         gl->fGetIntegerv(LOCAL_GL_MAX_TEXTURE_IMAGE_UNITS, &mGLMaxTextureImageUnits);
         gl->fGetIntegerv(LOCAL_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mGLMaxVertexTextureImageUnits);
+
+        if (!gl->GetPotentialInteger(LOCAL_GL_MAX_SAMPLES, (GLint*)&mGLMaxSamples))
+            mGLMaxSamples = 1;
     }
 
     // Calculate log2 of mGLMaxTextureSize and mGLMaxCubeMapTextureSize
     mGLMaxTextureSizeLog2 = 0;
     int32_t tempSize = mGLMaxTextureSize;
     while (tempSize >>= 1) {
         ++mGLMaxTextureSizeLog2;
     }
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -23,16 +23,17 @@ WebGLFramebuffer::WrapObject(JSContext* 
 
 WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo)
     : WebGLBindableName<FBTarget>(fbo)
     , WebGLContextBoundObject(webgl)
     , mStatus(0)
     , mDepthAttachment(LOCAL_GL_DEPTH_ATTACHMENT)
     , mStencilAttachment(LOCAL_GL_STENCIL_ATTACHMENT)
     , mDepthStencilAttachment(LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
+    , mReadBufferMode(LOCAL_GL_COLOR_ATTACHMENT0)
 {
     mContext->mFramebuffers.insertBack(this);
 
     mColorAttachments.SetLength(1);
     mColorAttachments[0].mAttachmentPoint = LOCAL_GL_COLOR_ATTACHMENT0;
 }
 
 WebGLFramebuffer::Attachment::Attachment(FBAttachment attachmentPoint)
@@ -972,16 +973,48 @@ WebGLFramebuffer::FinalizeAttachments() 
     DepthAttachment().FinalizeAttachment(gl, LOCAL_GL_DEPTH_ATTACHMENT);
     StencilAttachment().FinalizeAttachment(gl, LOCAL_GL_STENCIL_ATTACHMENT);
     DepthStencilAttachment().FinalizeAttachment(gl,
                                                 LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
 
     FinalizeDrawAndReadBuffers(gl, ColorAttachment(0).IsDefined());
 }
 
+bool
+WebGLFramebuffer::ValidateForRead(const char* info, TexInternalFormat* const out_format)
+{
+    if (mReadBufferMode == LOCAL_GL_NONE) {
+        mContext->ErrorInvalidOperation("%s: Read buffer mode must not be"
+                                        " NONE.", info);
+        return false;
+    }
+
+    const auto& attachment = GetAttachment(mReadBufferMode);
+
+    if (!CheckAndInitializeAttachments()) {
+        mContext->ErrorInvalidFramebufferOperation("readPixels: incomplete framebuffer");
+        return false;
+    }
+
+    GLenum readPlaneBits = LOCAL_GL_COLOR_BUFFER_BIT;
+    if (!HasCompletePlanes(readPlaneBits)) {
+        mContext->ErrorInvalidOperation("readPixels: Read source attachment doesn't have the"
+                                        " correct color/depth/stencil type.");
+        return false;
+    }
+
+    if (!attachment.IsDefined()) {
+        mContext->ErrorInvalidOperation("readPixels: ");
+        return false;
+    }
+
+    *out_format = attachment.EffectiveInternalFormat();
+    return true;
+}
+
 inline void
 ImplCycleCollectionUnlink(mozilla::WebGLFramebuffer::Attachment& field)
 {
     field.mTexturePtr = nullptr;
     field.mRenderbufferPtr = nullptr;
 }
 
 inline void
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -169,26 +169,29 @@ public:
 
     bool CheckColorAttachmentNumber(FBAttachment attachment,
                                     const char* funcName) const;
 
     void EnsureColorAttachments(size_t colorAttachmentId);
 
     void NotifyAttachableChanged() const;
 
+    bool ValidateForRead(const char* info, TexInternalFormat* const out_format);
+
 private:
     ~WebGLFramebuffer() {
         DeleteOnce();
     }
 
     mutable GLenum mStatus;
 
     // we only store pointers to attached renderbuffers, not to attached textures, because
     // we will only need to initialize renderbuffers. Textures are already initialized.
     nsTArray<Attachment> mColorAttachments;
     Attachment mDepthAttachment;
     Attachment mStencilAttachment;
     Attachment mDepthStencilAttachment;
+    GLenum mReadBufferMode;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_FRAMEBUFFER_H_
--- a/dom/canvas/WebGLRenderbuffer.cpp
+++ b/dom/canvas/WebGLRenderbuffer.cpp
@@ -49,16 +49,17 @@ WebGLRenderbuffer::WrapObject(JSContext*
 WebGLRenderbuffer::WebGLRenderbuffer(WebGLContext* webgl)
     : WebGLBindable<RBTarget>()
     , WebGLContextBoundObject(webgl)
     , mPrimaryRB(0)
     , mSecondaryRB(0)
     , mInternalFormat(0)
     , mInternalFormatForGL(0)
     , mImageDataStatus(WebGLImageDataStatus::NoImageData)
+    , mSamples(1)
 {
     mContext->MakeContextCurrent();
 
     mContext->gl->fGenRenderbuffers(1, &mPrimaryRB);
     if (!SupportsDepthStencil(mContext->gl))
         mContext->gl->fGenRenderbuffers(1, &mSecondaryRB);
 
     mContext->mRenderbuffers.insertBack(this);
@@ -151,45 +152,66 @@ WebGLRenderbuffer::BindRenderbuffer() co
      * the depth RB. When we need to ask about the stencil buffer (say, how many
      * stencil bits we have), we temporarily bind the stencil RB, so that it
      * looks like we're just asking the question of a combined DEPTH_STENCIL
      * buffer.
      */
     mContext->gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, mPrimaryRB);
 }
 
+static void
+RenderbufferStorageMaybeMultisample(gl::GLContext* gl, GLsizei samples,
+                                    GLenum internalFormat, GLsizei width,
+                                    GLsizei height)
+{
+    MOZ_ASSERT_IF(samples >= 1, gl->IsSupported(gl::GLFeature::framebuffer_multisample));
+    MOZ_ASSERT(samples >= 0);
+    MOZ_ASSERT(samples <= gl->MaxSamples());
+
+    if (samples > 0) {
+        gl->fRenderbufferStorageMultisample(LOCAL_GL_RENDERBUFFER, samples,
+                                            internalFormat, width, height);
+    } else {
+        gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, internalFormat, width,
+                                 height);
+    }
+}
+
 void
-WebGLRenderbuffer::RenderbufferStorage(GLenum internalFormat, GLsizei width,
-                                       GLsizei height) const
+WebGLRenderbuffer::RenderbufferStorage(GLsizei samples, GLenum internalFormat,
+                                       GLsizei width, GLsizei height) const
 {
     gl::GLContext* gl = mContext->gl;
+    MOZ_ASSERT(samples >= 0 && samples <= 256); // Sanity check.
 
     GLenum primaryFormat = internalFormat;
     GLenum secondaryFormat = 0;
 
     if (NeedsDepthStencilEmu(mContext->gl, primaryFormat)) {
         primaryFormat = DepthStencilDepthFormat(gl);
         secondaryFormat = LOCAL_GL_STENCIL_INDEX8;
     }
 
-    gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, primaryFormat, width, height);
+    RenderbufferStorageMaybeMultisample(gl, samples, primaryFormat, width,
+                                        height);
 
     if (!mSecondaryRB) {
         MOZ_ASSERT(!secondaryFormat);
         return;
     }
     // We can't leave the secondary RB unspecified either, since we should
     // handle the case where we attach a non-depth-stencil RB to a
     // depth-stencil attachment point, or attach this depth-stencil RB to a
     // non-depth-stencil attachment point.
     gl::ScopedBindRenderbuffer autoRB(gl, mSecondaryRB);
     if (secondaryFormat) {
-        gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, secondaryFormat, width, height);
+        RenderbufferStorageMaybeMultisample(gl, samples, secondaryFormat, width,
+                                            height);
     } else {
-        gl->fRenderbufferStorage(LOCAL_GL_RENDERBUFFER, LOCAL_GL_RGBA4, 1, 1);
+        RenderbufferStorageMaybeMultisample(gl, samples, LOCAL_GL_RGBA4, 1, 1);
     }
 }
 
 void
 WebGLRenderbuffer::FramebufferRenderbuffer(FBAttachment attachment) const
 {
     gl::GLContext* gl = mContext->gl;
     if (attachment != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
--- a/dom/canvas/WebGLRenderbuffer.h
+++ b/dom/canvas/WebGLRenderbuffer.h
@@ -33,16 +33,19 @@ public:
     }
     void SetImageDataStatus(WebGLImageDataStatus x) {
         // there is no way to go from having image data to not having any
         MOZ_ASSERT(x != WebGLImageDataStatus::NoImageData ||
                    mImageDataStatus == WebGLImageDataStatus::NoImageData);
         mImageDataStatus = x;
     }
 
+    GLsizei Samples() const { return mSamples; }
+    void SetSamples(GLsizei samples) { mSamples = samples; }
+
     GLenum InternalFormat() const { return mInternalFormat; }
     void SetInternalFormat(GLenum internalFormat) {
         mInternalFormat = internalFormat;
     }
 
     GLenum InternalFormatForGL() const { return mInternalFormatForGL; }
     void SetInternalFormatForGL(GLenum internalFormatForGL) {
         mInternalFormatForGL = internalFormatForGL;
@@ -50,18 +53,18 @@ public:
 
     int64_t MemoryUsage() const;
 
     WebGLContext* GetParentObject() const {
         return Context();
     }
 
     void BindRenderbuffer() const;
-    void RenderbufferStorage(GLenum internalFormat, GLsizei width,
-                             GLsizei height) const;
+    void RenderbufferStorage(GLsizei samples, GLenum internalFormat,
+                             GLsizei width, GLsizei height) const;
     void FramebufferRenderbuffer(FBAttachment attachment) const;
     // Only handles a subset of `pname`s.
     GLint GetRenderbufferParameter(RBTarget target, RBParam pname) const;
 
     virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLRenderbuffer)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLRenderbuffer)
@@ -71,15 +74,16 @@ protected:
         DeleteOnce();
     }
 
     GLuint mPrimaryRB;
     GLuint mSecondaryRB;
     GLenum mInternalFormat;
     GLenum mInternalFormatForGL;
     WebGLImageDataStatus mImageDataStatus;
+    GLsizei mSamples;
 
     friend class WebGLFramebuffer;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_RENDERBUFFER_H_
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -1407,16 +1407,30 @@ GLContext::InitWithPrefix(const char *pr
             if (!LoadSymbols(&extSymbols[0], trygl, prefix)) {
                 NS_ERROR("GL supports NV_fence without supplying its functions.");
 
                 MarkExtensionUnsupported(NV_fence);
                 ClearSymbols(extSymbols);
             }
         }
 
+        if (IsSupported(GLFeature::read_buffer)) {
+            SymLoadStruct extSymbols[] = {
+                { (PRFuncPtr*) &mSymbols.fReadBuffer, { "ReadBuffer",    nullptr } },
+                END_SYMBOLS
+            };
+
+            if (!LoadSymbols(&extSymbols[0], trygl, prefix)) {
+                NS_ERROR("GL supports read_buffer without supplying its functions.");
+
+                MarkUnsupported(GLFeature::read_buffer);
+                ClearSymbols(extSymbols);
+            }
+        }
+
         // Load developer symbols, don't fail if we can't find them.
         SymLoadStruct auxSymbols[] = {
                 { (PRFuncPtr*) &mSymbols.fGetTexImage, { "GetTexImage", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fGetTexLevelParameteriv, { "GetTexLevelParameteriv", nullptr } },
                 END_SYMBOLS
         };
         bool warnOnFailures = DebugMode();
         LoadSymbols(&auxSymbols[0], trygl, prefix, warnOnFailures);
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -110,16 +110,17 @@ enum class GLFeature {
     instanced_non_arrays,
     invalidate_framebuffer,
     map_buffer_range,
     occlusion_query,
     occlusion_query_boolean,
     occlusion_query2,
     packed_depth_stencil,
     query_objects,
+    read_buffer,
     renderbuffer_color_float,
     renderbuffer_color_half_float,
     robustness,
     sRGB,
     sampler_objects,
     standard_derivatives,
     texture_3D,
     texture_3D_compressed,
--- a/gfx/gl/GLContextFeatures.cpp
+++ b/gfx/gl/GLContextFeatures.cpp
@@ -404,16 +404,25 @@ static const FeatureInfo sFeatureInfoArr
         }
         /*
          * XXX_query_objects only provide entry points commonly supported by
          * ARB_occlusion_query (added in OpenGL 2.0) and EXT_occlusion_query_boolean
          * (added in OpenGL ES 3.0)
          */
     },
     {
+        "read_buffer",
+        GLVersion::GL2,
+        GLESVersion::ES3,
+        GLContext::Extension_None,
+        {
+            GLContext::Extensions_End
+        }
+    },
+    {
         "renderbuffer_color_float",
         GLVersion::GL3,
         GLESVersion::ES3,
         GLContext::Extension_None,
         {
             GLContext::ARB_texture_float,
             GLContext::EXT_color_buffer_float,
             GLContext::Extensions_End
--- a/gfx/gl/GLScreenBuffer.cpp
+++ b/gfx/gl/GLScreenBuffer.cpp
@@ -416,16 +416,22 @@ GLScreenBuffer::Attach(SharedSurface* su
 
         mDraw = Move(draw);
         mRead = Move(read);
     }
 
     // Check that we're all set up.
     MOZ_ASSERT(SharedSurf() == surf);
 
+    // Update the ReadBuffer mode.
+    if (mGL->IsSupported(gl::GLFeature::read_buffer)) {
+        BindFB(0);
+        mRead->SetReadBuffer(mUserReadBufferMode);
+    }
+
     return true;
 }
 
 bool
 GLScreenBuffer::Swap(const gfx::IntSize& size)
 {
     RefPtr<ShSurfHandle> newBack = mFactory->NewShSurfHandle(size);
     if (!newBack)
@@ -512,16 +518,26 @@ GLScreenBuffer::CreateRead(SharedSurface
 {
     GLContext* gl = mFactory->mGL;
     const GLFormats& formats = mFactory->mFormats;
     const SurfaceCaps& caps = mFactory->ReadCaps();
 
     return ReadBuffer::Create(gl, caps, formats, surf);
 }
 
+void
+GLScreenBuffer::SetReadBuffer(GLenum mode)
+{
+    MOZ_ASSERT(mGL->IsSupported(gl::GLFeature::read_buffer));
+    MOZ_ASSERT(GetReadFB() == 0);
+
+    mUserReadBufferMode = mode;
+    mRead->SetReadBuffer(mUserReadBufferMode);
+}
+
 bool
 GLScreenBuffer::IsDrawFramebufferDefault() const
 {
     if (!mDraw)
         return IsReadFramebufferDefault();
     return mDraw->mFB == 0;
 }
 
@@ -631,17 +647,16 @@ ReadBuffer::Create(GLContext* gl,
                    const SurfaceCaps& caps,
                    const GLFormats& formats,
                    SharedSurface* surf)
 {
     MOZ_ASSERT(surf);
 
     if (surf->mAttachType == AttachmentType::Screen) {
         // Don't need anything. Our read buffer will be the 'screen'.
-
         return UniquePtr<ReadBuffer>( new ReadBuffer(gl, 0, 0, 0,
                                                      surf) );
     }
 
     GLuint depthRB = 0;
     GLuint stencilRB = 0;
 
     GLuint* pDepthRB   = caps.depth   ? &depthRB   : nullptr;
@@ -735,10 +750,36 @@ ReadBuffer::Attach(SharedSurface* surf)
 }
 
 const gfx::IntSize&
 ReadBuffer::Size() const
 {
     return mSurf->mSize;
 }
 
+void
+ReadBuffer::SetReadBuffer(GLenum userMode) const
+{
+    if (!mGL->IsSupported(GLFeature::read_buffer))
+        return;
+
+    GLenum internalMode;
+
+    switch (userMode) {
+    case LOCAL_GL_BACK:
+        internalMode = (mFB == 0) ? LOCAL_GL_BACK
+                                  : LOCAL_GL_COLOR_ATTACHMENT0;
+        break;
+
+    case LOCAL_GL_NONE:
+        internalMode = LOCAL_GL_NONE;
+        break;
+
+    default:
+        MOZ_CRASH("Bad value.");
+    }
+
+    mGL->MakeCurrent();
+    mGL->fReadBuffer(internalMode);
+}
+
 } /* namespace gl */
 } /* namespace mozilla */
--- a/gfx/gl/GLScreenBuffer.h
+++ b/gfx/gl/GLScreenBuffer.h
@@ -111,16 +111,18 @@ public:
     // Cannot attach a surf of a different AttachType or Size than before.
     void Attach(SharedSurface* surf);
 
     const gfx::IntSize& Size() const;
 
     SharedSurface* SharedSurf() const {
         return mSurf;
     }
+
+    void SetReadBuffer(GLenum mode) const;
 };
 
 
 class GLScreenBuffer
 {
 public:
     // Infallible.
     static UniquePtr<GLScreenBuffer> Create(GLContext* gl,
@@ -137,16 +139,18 @@ protected:
     RefPtr<ShSurfHandle> mBack;
     RefPtr<ShSurfHandle> mFront;
 
     UniquePtr<DrawBuffer> mDraw;
     UniquePtr<ReadBuffer> mRead;
 
     bool mNeedsBlit;
 
+    GLenum mUserReadBufferMode;
+
     // Below are the parts that help us pretend to be framebuffer 0:
     GLuint mUserDrawFB;
     GLuint mUserReadFB;
     GLuint mInternalDrawFB;
     GLuint mInternalReadFB;
 
 #ifdef DEBUG
     bool mInInternalMode_DrawFB;
@@ -155,16 +159,17 @@ protected:
 
     GLScreenBuffer(GLContext* gl,
                    const SurfaceCaps& caps,
                    UniquePtr<SurfaceFactory> factory)
         : mGL(gl)
         , mCaps(caps)
         , mFactory(Move(factory))
         , mNeedsBlit(true)
+        , mUserReadBufferMode(LOCAL_GL_BACK)
         , mUserDrawFB(0)
         , mUserReadFB(0)
         , mInternalDrawFB(0)
         , mInternalReadFB(0)
 #ifdef DEBUG
         , mInInternalMode_DrawFB(true)
         , mInInternalMode_ReadFB(true)
 #endif
@@ -218,16 +223,18 @@ public:
 
     void BindAsFramebuffer(GLContext* const gl, GLenum target) const;
 
     void RequireBlit();
     void AssureBlitted();
     void AfterDrawCall();
     void BeforeReadCall();
 
+    void SetReadBuffer(GLenum userMode);
+
     /**
      * Attempts to read pixels from the current bound framebuffer, if
      * it is backed by a SharedSurface.
      *
      * Returns true if the pixel data has been read back, false
      * otherwise.
      */
     bool ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,