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 208898 b80fc984aaa60feec6e44884e9ffb851ddd1a6ba
parent 208897 0ca09645655e6e2c534b49a2a5bb61777b87bd34
child 208899 ef8673b55bb13081809a9e8aa9f13bf967f6a2c3
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersbjacob, djg
bugs1075195
milestone35.0a1
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,