Bug 1075195 - WebGL2: implement texStorage2D - r=bjacob,djg
authorDan Glastonbury <dglastonbury@mozilla.com> and Benoit Jacob <bjacob@mozilla.com>
Tue, 15 Jul 2014 09:55:56 +1000
changeset 232113 b80fc984aaa60feec6e44884e9ffb851ddd1a6ba
parent 232112 0ca09645655e6e2c534b49a2a5bb61777b87bd34
child 232114 ef8673b55bb13081809a9e8aa9f13bf967f6a2c3
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbjacob, djg
bugs1075195
milestone35.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 1075195 - WebGL2: implement texStorage2D - r=bjacob,djg
dom/canvas/WebGL2Context.h
dom/canvas/WebGL2ContextTextures.cpp
dom/canvas/WebGLContextGL.cpp
dom/canvas/WebGLContextUtils.cpp
dom/canvas/WebGLTexture.cpp
dom/canvas/WebGLTexture.h
gfx/gl/GLContext.cpp
gfx/gl/GLContext.h
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -238,13 +238,19 @@ public:
     void DeleteVertexArray(WebGLVertexArrayObject* vertexArray);
     bool IsVertexArray(WebGLVertexArrayObject* vertexArray);
     void BindVertexArray(WebGLVertexArrayObject* vertexArray);
 */
 
 private:
 
     WebGL2Context();
+
+    bool ValidateSizedInternalFormat(GLenum internalFormat, const char* info);
+    bool ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat,
+                                GLsizei width, GLsizei height, GLsizei depth,
+                                const char* info);
+
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/canvas/WebGL2ContextTextures.cpp
+++ b/dom/canvas/WebGL2ContextTextures.cpp
@@ -4,23 +4,177 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGL2Context.h"
 #include "GLContext.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+bool
+WebGL2Context::ValidateSizedInternalFormat(GLenum internalformat, const char* info)
+{
+    switch (internalformat) {
+        // Sized Internal Formats
+        // https://www.khronos.org/opengles/sdk/docs/man3/html/glTexStorage2D.xhtml
+    case LOCAL_GL_R8:
+    case LOCAL_GL_R8_SNORM:
+    case LOCAL_GL_R16F:
+    case LOCAL_GL_R32F:
+    case LOCAL_GL_R8UI:
+    case LOCAL_GL_R8I:
+    case LOCAL_GL_R16UI:
+    case LOCAL_GL_R16I:
+    case LOCAL_GL_R32UI:
+    case LOCAL_GL_R32I:
+    case LOCAL_GL_RG8:
+    case LOCAL_GL_RG8_SNORM:
+    case LOCAL_GL_RG16F:
+    case LOCAL_GL_RG32F:
+    case LOCAL_GL_RG8UI:
+    case LOCAL_GL_RG8I:
+    case LOCAL_GL_RG16UI:
+    case LOCAL_GL_RG16I:
+    case LOCAL_GL_RG32UI:
+    case LOCAL_GL_RG32I:
+    case LOCAL_GL_RGB8:
+    case LOCAL_GL_SRGB8:
+    case LOCAL_GL_RGB565:
+    case LOCAL_GL_RGB8_SNORM:
+    case LOCAL_GL_R11F_G11F_B10F:
+    case LOCAL_GL_RGB9_E5:
+    case LOCAL_GL_RGB16F:
+    case LOCAL_GL_RGB32F:
+    case LOCAL_GL_RGB8UI:
+    case LOCAL_GL_RGB8I:
+    case LOCAL_GL_RGB16UI:
+    case LOCAL_GL_RGB16I:
+    case LOCAL_GL_RGB32UI:
+    case LOCAL_GL_RGB32I:
+    case LOCAL_GL_RGBA8:
+    case LOCAL_GL_SRGB8_ALPHA8:
+    case LOCAL_GL_RGBA8_SNORM:
+    case LOCAL_GL_RGB5_A1:
+    case LOCAL_GL_RGBA4:
+    case LOCAL_GL_RGB10_A2:
+    case LOCAL_GL_RGBA16F:
+    case LOCAL_GL_RGBA32F:
+    case LOCAL_GL_RGBA8UI:
+    case LOCAL_GL_RGBA8I:
+    case LOCAL_GL_RGB10_A2UI:
+    case LOCAL_GL_RGBA16UI:
+    case LOCAL_GL_RGBA16I:
+    case LOCAL_GL_RGBA32I:
+    case LOCAL_GL_RGBA32UI:
+    case LOCAL_GL_DEPTH_COMPONENT16:
+    case LOCAL_GL_DEPTH_COMPONENT24:
+    case LOCAL_GL_DEPTH_COMPONENT32F:
+    case LOCAL_GL_DEPTH24_STENCIL8:
+    case LOCAL_GL_DEPTH32F_STENCIL8:
+        return true;
+    }
+
+    if (IsCompressedTextureFormat(internalformat)) {
+        return true;
+    }
+
+    const char* name = EnumName(internalformat);
+    if (name && name[0] != '[')
+        ErrorInvalidEnum("%s: invalid internal format %s", info, name);
+    else
+        ErrorInvalidEnum("%s: invalid internal format 0x%04X", info, internalformat);
+
+    return false;
+}
+
+/** Validates parameters to texStorage{2D,3D} */
+bool
+WebGL2Context::ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat,
+                                      GLsizei width, GLsizei height, GLsizei depth,
+                                      const char* info)
+{
+    // GL_INVALID_OPERATION is generated if the default texture object is curently bound to target.
+    WebGLTexture* tex = activeBoundTextureForTarget(target);
+    if (!tex) {
+        ErrorInvalidOperation("%s: no texture is bound to target %s", info, EnumName(target));
+        return false;
+    }
+
+    // GL_INVALID_OPERATION is generated if the texture object currently bound to target already has
+    // GL_TEXTURE_IMMUTABLE_FORMAT set to GL_TRUE.
+    if (tex->IsImmutable()) {
+        ErrorInvalidOperation("%s: texture bound to target %s is already immutable", info, EnumName(target));
+        return false;
+    }
+
+    // GL_INVALID_ENUM is generated if internalformat is not a valid sized internal format.
+    if (!ValidateSizedInternalFormat(internalformat, info))
+        return false;
+
+    // GL_INVALID_VALUE is generated if width, height or levels are less than 1.
+    if (width < 1)  { ErrorInvalidValue("%s: width is < 1", info);  return false; }
+    if (height < 1) { ErrorInvalidValue("%s: height is < 1", info); return false; }
+    if (depth < 1)  { ErrorInvalidValue("%s: depth is < 1", info);  return false; }
+    if (levels < 1) { ErrorInvalidValue("%s: levels is < 1", info); return false; }
+
+    // The following check via FloorLog2 only requires a depth value if target is TEXTURE_3D.
+    bool is3D = (target != LOCAL_GL_TEXTURE_3D);
+    if (!is3D)
+        depth = 1;
+
+    // GL_INVALID_OPERATION is generated if levels is greater than floor(log2(max(width, height, depth)))+1.
+    if (FloorLog2(std::max(std::max(width, height), depth))+1 < levels) {
+        ErrorInvalidOperation("%s: levels > floor(log2(max(width, height%s)))+1", info, is3D ? ", depth" : "");
+        return false;
+    }
+
+    return true;
+}
+
 // -------------------------------------------------------------------------
 // Texture objects
 
 void
 WebGL2Context::TexStorage2D(GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    // GL_INVALID_ENUM is generated if target is not one of the accepted target enumerants.
+    if (target != LOCAL_GL_TEXTURE_2D && target != LOCAL_GL_TEXTURE_CUBE_MAP)
+        return ErrorInvalidEnum("texStorage2D: target is not TEXTURE_2D or TEXTURE_CUBE_MAP.");
+
+    if (!ValidateTexStorage(target, levels, internalformat, width, height, 1, "texStorage2D"))
+        return;
+
+    WebGLTexture* tex = activeBoundTextureForTarget(target);
+    tex->SetImmutable();
+
+    const size_t facesCount = (target == LOCAL_GL_TEXTURE_2D) ? 1 : 6;
+    GLsizei w = width;
+    GLsizei h = height;
+    for (size_t l = 0; l < size_t(levels); l++) {
+        for (size_t f = 0; f < facesCount; f++) {
+            TexImageTarget imageTarget = TexImageTargetForTargetAndFace(target, f);
+            // FIXME: SetImageInfo wants a type, to go with the internalformat that it stores.
+            // 'type' is deprecated by sized internalformats, which are how TexStorage works.
+            // We must fix WebGLTexture::ImageInfo to store an "effective internalformat",
+            // which in the present case is just the sized internalformat, and drop 'types'
+            // altogether. For now, we just pass LOCAL_GL_UNSIGNED_BYTE, which works For
+            // the most commonly used formats.
+            const GLenum type = LOCAL_GL_UNSIGNED_BYTE;
+            tex->SetImageInfo(imageTarget, l, w, h,
+                              internalformat, type,
+                              WebGLImageDataStatus::UninitializedImageData);
+        }
+        w = std::max(1, w/2);
+        h = std::max(1, h/2);
+    }
+
+    gl->fTexStorage2D(target, levels, internalformat, width, height);
 }
 
 void
 WebGL2Context::TexStorage3D(GLenum target, GLsizei levels, GLenum internalformat,
                             GLsizei width, GLsizei height, GLsizei depth)
 {
     MOZ_CRASH("Not Implemented.");
 }
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -390,16 +390,22 @@ WebGLContext::CopyTexSubImage2D_base(Tex
 
     MakeContextCurrent();
 
     WebGLTexture *tex = activeBoundTextureForTexImageTarget(texImageTarget);
 
     if (!tex)
         return ErrorInvalidOperation("%s: no texture is bound to this target");
 
+    if (tex->IsImmutable()) {
+        if (!sub) {
+            return ErrorInvalidOperation("copyTexImage2D: disallowed because the texture bound to this target has already been made immutable by texStorage2D");
+        }
+    }
+
     if (CanvasUtils::CheckSaneSubrectSize(x, y, width, height, framebufferWidth, framebufferHeight)) {
         if (sub)
             gl->fCopyTexSubImage2D(texImageTarget.get(), level, xoffset, yoffset, x, y, width, height);
         else
             gl->fCopyTexImage2D(texImageTarget.get(), level, internalformat, x, y, width, height, 0);
     } else {
 
         // the rect doesn't fit in the framebuffer
@@ -3331,20 +3337,27 @@ WebGLContext::CompressedTexImage2D(GLenu
 
     if (!ValidateCompTexImageSize(level, internalformat, 0, 0, width, height, width, height, func))
     {
         return;
     }
 
     const TexImageTarget texImageTarget(rawTexImgTarget);
 
+    WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);
+    MOZ_ASSERT(tex);
+    if (tex->IsImmutable()) {
+        return ErrorInvalidOperation(
+            "compressedTexImage2D: disallowed because the texture bound to "
+            "this target has already been made immutable by texStorage2D");
+    }
+
     MakeContextCurrent();
     gl->fCompressedTexImage2D(texImageTarget.get(), level, internalformat, width, height, border, byteLength, view.Data());
-    WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);
-    MOZ_ASSERT(tex);
+
     tex->SetImageInfo(texImageTarget, level, width, height, internalformat, LOCAL_GL_UNSIGNED_BYTE,
                       WebGLImageDataStatus::InitializedImageData);
 }
 
 void
 WebGLContext::CompressedTexSubImage2D(GLenum rawTexImgTarget, GLint level, GLint xoffset,
                                       GLint yoffset, GLsizei width, GLsizei height,
                                       GLenum internalformat,
@@ -3682,16 +3695,21 @@ WebGLContext::TexImage2D_base(TexImageTa
         return ErrorInvalidOperation("texImage2D: not enough data for operation (need %d, have %d)",
                                  bytesNeeded, byteLength);
 
     WebGLTexture *tex = activeBoundTextureForTexImageTarget(texImageTarget);
 
     if (!tex)
         return ErrorInvalidOperation("texImage2D: no texture is bound to this target");
 
+    if (tex->IsImmutable()) {
+        return ErrorInvalidOperation(
+            "texImage2D: disallowed because the texture "
+            "bound to this target has already been made immutable by texStorage2D");
+    }
     MakeContextCurrent();
 
     nsAutoArrayPtr<uint8_t> convertedData;
     void* pixels = nullptr;
     WebGLImageDataStatus imageInfoStatusIfSuccess = WebGLImageDataStatus::UninitializedImageData;
 
     if (byteLength) {
         size_t   srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value();
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -501,16 +501,26 @@ WebGLContext::IsCompressedTextureFormat(
         case LOCAL_GL_ATC_RGB:
         case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
         case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
         case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
         case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1:
         case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
         case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
         case LOCAL_GL_ETC1_RGB8_OES:
+        case LOCAL_GL_COMPRESSED_R11_EAC:
+        case LOCAL_GL_COMPRESSED_SIGNED_R11_EAC:
+        case LOCAL_GL_COMPRESSED_RG11_EAC:
+        case LOCAL_GL_COMPRESSED_SIGNED_RG11_EAC:
+        case LOCAL_GL_COMPRESSED_RGB8_ETC2:
+        case LOCAL_GL_COMPRESSED_SRGB8_ETC2:
+        case LOCAL_GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+        case LOCAL_GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
+        case LOCAL_GL_COMPRESSED_RGBA8_ETC2_EAC:
+        case LOCAL_GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
             return true;
         default:
             return false;
     }
 }
 
 
 bool
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -27,16 +27,17 @@ WebGLTexture::WebGLTexture(WebGLContext 
     , WebGLContextBoundObject(context)
     , mMinFilter(LOCAL_GL_NEAREST_MIPMAP_LINEAR)
     , mMagFilter(LOCAL_GL_LINEAR)
     , mWrapS(LOCAL_GL_REPEAT)
     , mWrapT(LOCAL_GL_REPEAT)
     , mFacesCount(0)
     , mMaxLevelWithCustomImages(0)
     , mHaveGeneratedMipmap(false)
+    , mImmutable(false)
     , mFakeBlackStatus(WebGLTextureFakeBlackStatus::IncompleteTexture)
 {
     SetIsDOMBinding();
     mContext->MakeContextCurrent();
     mContext->gl->fGenTextures(1, &mGLName);
     mContext->mTextures.insertBack(this);
 }
 
@@ -222,29 +223,22 @@ WebGLTexture::IsCubeComplete() const {
     if (mTarget != LOCAL_GL_TEXTURE_CUBE_MAP)
         return false;
     const ImageInfo &first = ImageInfoAt(LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0);
     if (!first.IsPositive() || !first.IsSquare())
         return false;
     return AreAllLevel0ImageInfosEqual();
 }
 
-static TexImageTarget
-GLCubeMapFaceById(int id)
-{
-    // Correctness is checked by the constructor for TexImageTarget
-    return TexImageTarget(LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + id);
-}
-
 bool
 WebGLTexture::IsMipmapCubeComplete() const {
     if (!IsCubeComplete()) // in particular, this checks that this is a cube map
         return false;
     for (int i = 0; i < 6; i++) {
-        const TexImageTarget face = GLCubeMapFaceById(i);
+        const TexImageTarget face = TexImageTargetForTargetAndFace(LOCAL_GL_TEXTURE_CUBE_MAP, i);
         if (!DoesTexture2DMipmapHaveAllLevelsConsistentlyDefined(face))
             return false;
     }
     return true;
 }
 
 WebGLTextureFakeBlackStatus
 WebGLTexture::ResolvedFakeBlackStatus() {
@@ -404,19 +398,17 @@ WebGLTexture::ResolvedFakeBlackStatus() 
         if (hasAnyInitializedImageData) {
             // The texture contains some initialized image data, and some uninitialized image data.
             // In this case, we have no choice but to initialize all image data now. Fortunately,
             // in this case we know that we can't be dealing with a depth texture per WEBGL_depth_texture
             // and ANGLE_depth_texture (which allow only one image per texture) so we can assume that
             // glTexImage2D is able to upload data to images.
             for (size_t level = 0; level <= mMaxLevelWithCustomImages; ++level) {
                 for (size_t face = 0; face < mFacesCount; ++face) {
-                    TexImageTarget imageTarget = mTarget == LOCAL_GL_TEXTURE_2D
-                                                 ? LOCAL_GL_TEXTURE_2D
-                                                 : LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
+                    TexImageTarget imageTarget = TexImageTargetForTargetAndFace(mTarget, face);
                     const ImageInfo& imageInfo = ImageInfoAt(imageTarget, level);
                     if (imageInfo.mImageDataStatus == WebGLImageDataStatus::UninitializedImageData) {
                         DoDeferredImageInitialization(imageTarget, level);
                     }
                 }
             }
             mFakeBlackStatus = WebGLTextureFakeBlackStatus::NotNeeded;
         } else {
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -197,17 +197,19 @@ protected:
 
     TexMinFilter mMinFilter;
     TexMagFilter mMagFilter;
     TexWrap mWrapS, mWrapT;
 
     size_t mFacesCount, mMaxLevelWithCustomImages;
     nsTArray<ImageInfo> mImageInfos;
 
-    bool mHaveGeneratedMipmap;
+    bool mHaveGeneratedMipmap; // set by generateMipmap
+    bool mImmutable; // set by texStorage*
+
     WebGLTextureFakeBlackStatus mFakeBlackStatus;
 
     void EnsureMaxLevelWithCustomImagesAtLeast(size_t aMaxLevelWithCustomImages) {
         mMaxLevelWithCustomImages = std::max(mMaxLevelWithCustomImages, aMaxLevelWithCustomImages);
         mImageInfos.EnsureLengthAtLeast((mMaxLevelWithCustomImages + 1) * mFacesCount);
     }
 
     bool CheckFloatTextureFilterParams() const {
@@ -266,16 +268,29 @@ public:
     bool IsMipmapTexture2DComplete() const;
 
     bool IsCubeComplete() const;
 
     bool IsMipmapCubeComplete() const;
 
     void SetFakeBlackStatus(WebGLTextureFakeBlackStatus x);
 
+    bool IsImmutable() const { return mImmutable; }
+    void SetImmutable() { mImmutable = true; }
+
+    size_t MaxLevelWithCustomImages() const { return mMaxLevelWithCustomImages; }
+
     // Returns the current fake-black-status, except if it was Unknown,
     // in which case this function resolves it first, so it never returns Unknown.
     WebGLTextureFakeBlackStatus ResolvedFakeBlackStatus();
 };
 
+inline TexImageTarget
+TexImageTargetForTargetAndFace(TexTarget target, size_t face)
+{
+    return target == LOCAL_GL_TEXTURE_2D
+           ? LOCAL_GL_TEXTURE_2D
+           : LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
+}
+
 } // namespace mozilla
 
 #endif
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -121,16 +121,17 @@ static const char *sExtensionNames[] = {
     "GL_EXT_sRGB",
     "GL_EXT_shader_texture_lod",
     "GL_EXT_texture3D",
     "GL_EXT_texture_compression_dxt1",
     "GL_EXT_texture_compression_s3tc",
     "GL_EXT_texture_filter_anisotropic",
     "GL_EXT_texture_format_BGRA8888",
     "GL_EXT_texture_sRGB",
+    "GL_EXT_texture_storage",
     "GL_EXT_transform_feedback",
     "GL_EXT_unpack_subimage",
     "GL_IMG_read_format",
     "GL_IMG_texture_compression_pvrtc",
     "GL_IMG_texture_npot",
     "GL_KHR_debug",
     "GL_NV_draw_instanced",
     "GL_NV_fence",
@@ -904,16 +905,39 @@ GLContext::InitWithPrefix(const char *pr
             if (!LoadSymbols(useCore ? coreSymbols : extSymbols, trygl, prefix)) {
                 NS_ERROR("GL supports array instanced without supplying it function.");
 
                 MarkUnsupported(GLFeature::instanced_arrays);
                 ClearSymbols(coreSymbols);
             }
         }
 
+        if (IsSupported(GLFeature::texture_storage)) {
+            SymLoadStruct coreSymbols[] = {
+                { (PRFuncPtr*) &mSymbols.fTexStorage2D, { "TexStorage2D", nullptr } },
+                { (PRFuncPtr*) &mSymbols.fTexStorage3D, { "TexStorage3D", nullptr } },
+                END_SYMBOLS
+            };
+
+            SymLoadStruct extSymbols[] = {
+                { (PRFuncPtr*) &mSymbols.fTexStorage2D, { "TexStorage2DEXT", nullptr } },
+                { (PRFuncPtr*) &mSymbols.fTexStorage3D, { "TexStorage3DEXT", nullptr } },
+                END_SYMBOLS
+            };
+
+            bool useCore = IsFeatureProvidedByCoreSymbols(GLFeature::texture_storage);
+            if (!LoadSymbols(useCore ? coreSymbols : extSymbols, trygl, prefix)) {
+                NS_ERROR("GL supports texture storage without supplying its functions.");
+
+                MarkUnsupported(GLFeature::texture_storage);
+                MarkExtensionSupported(useCore ? ARB_texture_storage : EXT_texture_storage);
+                ClearSymbols(coreSymbols);
+            }
+        }
+
         if (IsSupported(GLFeature::sampler_objects)) {
             SymLoadStruct samplerObjectsSymbols[] = {
                 { (PRFuncPtr*) &mSymbols.fGenSamplers, { "GenSamplers", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fDeleteSamplers, { "DeleteSamplers", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fIsSampler, { "IsSampler", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fBindSampler, { "BindSampler", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fSamplerParameteri, { "SamplerParameteri", nullptr } },
                 { (PRFuncPtr*) &mSymbols.fSamplerParameteriv, { "SamplerParameteriv", nullptr } },
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -403,16 +403,17 @@ public:
         EXT_sRGB,
         EXT_shader_texture_lod,
         EXT_texture3D,
         EXT_texture_compression_dxt1,
         EXT_texture_compression_s3tc,
         EXT_texture_filter_anisotropic,
         EXT_texture_format_BGRA8888,
         EXT_texture_sRGB,
+        EXT_texture_storage,
         EXT_transform_feedback,
         EXT_unpack_subimage,
         IMG_read_format,
         IMG_texture_compression_pvrtc,
         IMG_texture_npot,
         KHR_debug,
         NV_draw_instanced,
         NV_fence,