WebGL2: support TEXTURE_BASE_LEVEL and TEXTURE_MAX_LEVEL (bug 1080957, r=jgilbert).
authorDavid Anderson <danderson@mozilla.com>
Thu, 16 Oct 2014 13:07:01 -0700
changeset 210821 4bdbd3e97f0062ca24ad76acf028f4fd21390001
parent 210820 160f1778cd6851399447bc3d8fa32097b30a65aa
child 210822 9951cc945fcbade4b818828368dcf894b7c1e630
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersjgilbert
bugs1080957
milestone36.0a1
WebGL2: support TEXTURE_BASE_LEVEL and TEXTURE_MAX_LEVEL (bug 1080957, r=jgilbert).
dom/canvas/WebGL2ContextTextures.cpp
dom/canvas/WebGLContextGL.cpp
dom/canvas/WebGLTexture.cpp
dom/canvas/WebGLTexture.h
--- a/dom/canvas/WebGL2ContextTextures.cpp
+++ b/dom/canvas/WebGL2ContextTextures.cpp
@@ -346,16 +346,18 @@ WebGL2Context::CompressedTexSubImage3D(G
     MOZ_CRASH("Not Implemented.");
 }
 
 JS::Value
 WebGL2Context::GetTexParameterInternal(const TexTarget& target, GLenum pname)
 {
     switch (pname) {
         case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
+        case LOCAL_GL_TEXTURE_BASE_LEVEL:
+        case LOCAL_GL_TEXTURE_MAX_LEVEL:
         {
             GLint i = 0;
             gl->fGetTexParameteriv(target.get(), pname, &i);
             return JS::NumberValue(uint32_t(i));
         }
     }
     return WebGLContext::GetTexParameterInternal(target, pname);
 }
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -917,17 +917,21 @@ WebGLContext::GenerateMipmap(GLenum rawT
     WebGLTexture *tex = activeBoundTextureForTarget(target);
 
     if (!tex)
         return ErrorInvalidOperation("generateMipmap: No texture is bound to this target.");
 
     const TexImageTarget imageTarget = (target == LOCAL_GL_TEXTURE_2D)
                                                   ? LOCAL_GL_TEXTURE_2D
                                                   : LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X;
-    if (!tex->HasImageInfoAt(imageTarget, 0))
+    if (!tex->IsMipmapRangeValid())
+    {
+        return ErrorInvalidOperation("generateMipmap: Texture does not have a valid mipmap range.");
+    }
+    if (!tex->HasImageInfoAt(imageTarget, tex->EffectiveBaseMipmapLevel()))
     {
         return ErrorInvalidOperation("generateMipmap: Level zero of texture is not defined.");
     }
 
     if (!IsWebGL2() && !tex->IsFirstImagePowerOfTwo())
         return ErrorInvalidOperation("generateMipmap: Level zero of texture does not have power-of-two width and height.");
 
     TexInternalFormat internalformat = tex->ImageInfoAt(imageTarget, 0).EffectiveInternalFormat();
@@ -1505,16 +1509,29 @@ void WebGLContext::TexParameter_base(GLe
     WebGLTexture *tex = activeBoundTextureForTarget(texTarget);
     if (!tex)
         return ErrorInvalidOperation("texParameter: no texture is bound to this target");
 
     bool pnameAndParamAreIncompatible = false;
     bool paramValueInvalid = false;
 
     switch (pname) {
+        case LOCAL_GL_TEXTURE_BASE_LEVEL:
+        case LOCAL_GL_TEXTURE_MAX_LEVEL:
+            if (!IsWebGL2())
+                return ErrorInvalidEnumInfo("texParameter: pname", pname);
+            if (intParam < 0) {
+                paramValueInvalid = true;
+                break;
+            }
+            if (pname == LOCAL_GL_TEXTURE_BASE_LEVEL)
+                tex->SetBaseMipmapLevel(intParam);
+            else
+                tex->SetMaxMipmapLevel(intParam);
+            break;
         case LOCAL_GL_TEXTURE_MIN_FILTER:
             switch (intParam) {
                 case LOCAL_GL_NEAREST:
                 case LOCAL_GL_LINEAR:
                 case LOCAL_GL_NEAREST_MIPMAP_NEAREST:
                 case LOCAL_GL_LINEAR_MIPMAP_NEAREST:
                 case LOCAL_GL_NEAREST_MIPMAP_LINEAR:
                 case LOCAL_GL_LINEAR_MIPMAP_LINEAR:
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "mozilla/Scoped.h"
 #include "ScopedGLHelpers.h"
 #include "WebGLContext.h"
 #include "WebGLContextUtils.h"
 #include "WebGLTexelConversions.h"
 
 #include <algorithm>
+#include "mozilla/MathAlgorithms.h"
 
 using namespace mozilla;
 
 JSObject*
 WebGLTexture::WrapObject(JSContext *cx) {
     return dom::WebGLTextureBinding::Wrap(cx, this);
 }
 
@@ -28,16 +29,18 @@ WebGLTexture::WebGLTexture(WebGLContext 
     , 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)
+    , mBaseMipmapLevel(0)
+    , mMaxMipmapLevel(1000)
     , mFakeBlackStatus(WebGLTextureFakeBlackStatus::IncompleteTexture)
 {
     mContext->MakeContextCurrent();
     mContext->gl->fGenTextures(1, &mGLName);
     mContext->mTextures.insertBack(this);
 }
 
 void
@@ -57,67 +60,75 @@ WebGLTexture::ImageInfo::MemoryUsage() c
 }
 
 size_t
 WebGLTexture::MemoryUsage() const {
     if (IsDeleted())
         return 0;
     size_t result = 0;
     for(size_t face = 0; face < mFacesCount; face++) {
-        if (mHaveGeneratedMipmap) {
-            size_t level0MemoryUsage = ImageInfoAtFace(face, 0).MemoryUsage();
-            // Each mipmap level is 1/(2^d) the size of the previous level,
-            // where d is 2 or 3 depending on whether the images are 2D or 3D
-            // 1 + x + x^2 + ... = 1/(1-x)
-            // for x = 1/(2^2), we get 1/(1-1/4) = 4/3
-            // for x = 1/(2^3), we get 1/(1-1/8) = 8/7
-            size_t allLevelsMemoryUsage =
-                mTarget == LOCAL_GL_TEXTURE_3D
-                ? level0MemoryUsage * 8 / 7
-                : level0MemoryUsage * 4 / 3;
-            result += allLevelsMemoryUsage;
-        } else {
             for(size_t level = 0; level <= mMaxLevelWithCustomImages; level++)
                 result += ImageInfoAtFace(face, level).MemoryUsage();
         }
-    }
     return result;
 }
 
+static inline size_t
+MipmapLevelsForSize(const WebGLTexture::ImageInfo &info)
+{
+    GLsizei size = std::max(std::max(info.Width(), info.Height()), info.Depth());
+
+    // Find floor(log2(size)). (ES 3.0.4, 3.8 - Mipmapping).
+    return mozilla::FloorLog2(size);
+}
+
 bool
 WebGLTexture::DoesMipmapHaveAllLevelsConsistentlyDefined(TexImageTarget texImageTarget) const
 {
+    // We could not have generated a mipmap if the base image wasn't defined.
     if (mHaveGeneratedMipmap)
         return true;
 
+    if (!IsMipmapRangeValid())
+        return false;
+
     // We want a copy here so we can modify it temporarily.
-    ImageInfo expected = ImageInfoAt(texImageTarget, 0);
+    ImageInfo expected = ImageInfoAt(texImageTarget, EffectiveBaseMipmapLevel());
+    if (!expected.IsPositive())
+        return false;
 
-    // checks if custom level>0 images are all defined up to the highest level defined
-    // and have the expected dimensions
-    for (size_t level = 0; level <= mMaxLevelWithCustomImages; ++level) {
+    // If Level{max} is > mMaxLevelWithCustomImages, then check if we are
+    // missing any image levels.
+    if (mMaxMipmapLevel > mMaxLevelWithCustomImages) {
+        if (MipmapLevelsForSize(expected) > mMaxLevelWithCustomImages)
+            return false;
+    }
+
+    // Checks if custom images are all defined up to the highest level and
+    // have the expected dimensions.
+    for (size_t level = EffectiveBaseMipmapLevel(); level <= EffectiveMaxMipmapLevel(); ++level) {
         const ImageInfo& actual = ImageInfoAt(texImageTarget, level);
         if (actual != expected)
             return false;
+
         expected.mWidth = std::max(1, expected.mWidth / 2);
         expected.mHeight = std::max(1, expected.mHeight / 2);
         expected.mDepth = std::max(1, expected.mDepth / 2);
 
         // if the current level has size 1x1, we can stop here: the spec doesn't seem to forbid the existence
         // of extra useless levels.
         if (actual.mWidth == 1 &&
             actual.mHeight == 1 &&
             actual.mDepth == 1)
         {
             return true;
         }
     }
 
-    // if we're here, we've exhausted all levels without finding a 1x1 image
-    return false;
+    return true;
 }
 
 void
 WebGLTexture::Bind(TexTarget aTexTarget) {
     // this function should only be called by bindTexture().
     // it assumes that the GL context is already current.
 
     bool firstTimeThisTextureIsBound = !HasEverBeenBound();
@@ -175,35 +186,29 @@ WebGLTexture::SetGeneratedMipmap() {
         mHaveGeneratedMipmap = true;
         SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown);
     }
 }
 
 void
 WebGLTexture::SetCustomMipmap() {
     if (mHaveGeneratedMipmap) {
-        // if we were in GeneratedMipmap mode and are now switching to CustomMipmap mode,
-        // we need to compute now all the mipmap image info.
+        if (!IsMipmapRangeValid())
+            return;
 
-        // since we were in GeneratedMipmap mode, we know that the level 0 images all have the same info,
-        // and are power-of-two.
-        ImageInfo imageInfo = ImageInfoAtFace(0, 0);
+        // If we were in GeneratedMipmap mode and are now switching to CustomMipmap mode,
+        // we now need to compute all the mipmap image info.
+        ImageInfo imageInfo = ImageInfoAtFace(0, EffectiveBaseMipmapLevel());
         NS_ASSERTION(mContext->IsWebGL2() || imageInfo.IsPowerOfTwo(),
                      "this texture is NPOT, so how could GenerateMipmap() ever accept it?");
 
-        GLsizei size = std::max(std::max(imageInfo.mWidth, imageInfo.mHeight), imageInfo.mDepth);
+        size_t maxLevel = MipmapLevelsForSize(imageInfo);
+        EnsureMaxLevelWithCustomImagesAtLeast(EffectiveBaseMipmapLevel() + maxLevel);
 
-        // Find floor(log2(size)). (ES 3.0.4, 3.8 - Mipmapping).
-        size_t maxLevel = 0;
-        for (GLsizei n = size; n > 1; n >>= 1)
-            ++maxLevel;
-
-        EnsureMaxLevelWithCustomImagesAtLeast(maxLevel);
-
-        for (size_t level = 1; level <= maxLevel; ++level) {
+        for (size_t level = EffectiveBaseMipmapLevel() + 1; level <= EffectiveMaxMipmapLevel(); ++level) {
             imageInfo.mWidth = std::max(imageInfo.mWidth / 2, 1);
             imageInfo.mHeight = std::max(imageInfo.mHeight / 2, 1);
             imageInfo.mDepth = std::max(imageInfo.mDepth / 2, 1);
             for(size_t face = 0; face < mFacesCount; ++face)
                 ImageInfoAtFace(face, level) = imageInfo;
         }
     }
     mHaveGeneratedMipmap = false;
@@ -217,21 +222,16 @@ WebGLTexture::AreAllLevel0ImageInfosEqua
     }
     return true;
 }
 
 bool
 WebGLTexture::IsMipmapComplete() const {
     MOZ_ASSERT(mTarget == LOCAL_GL_TEXTURE_2D ||
                mTarget == LOCAL_GL_TEXTURE_3D);
-
-    if (!ImageInfoAtFace(0, 0).IsPositive())
-        return false;
-    if (mHaveGeneratedMipmap)
-        return true;
     return DoesMipmapHaveAllLevelsConsistentlyDefined(LOCAL_GL_TEXTURE_2D);
 }
 
 bool
 WebGLTexture::IsCubeComplete() const {
     MOZ_ASSERT(mTarget == LOCAL_GL_TEXTURE_CUBE_MAP);
 
     const ImageInfo &first = ImageInfoAt(LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0);
@@ -247,27 +247,42 @@ WebGLTexture::IsMipmapCubeComplete() con
     for (int i = 0; i < 6; i++) {
         const TexImageTarget face = TexImageTargetForTargetAndFace(LOCAL_GL_TEXTURE_CUBE_MAP, i);
         if (!DoesMipmapHaveAllLevelsConsistentlyDefined(face))
             return false;
     }
     return true;
 }
 
+bool
+WebGLTexture::IsMipmapRangeValid() const
+{
+    // In ES3, if a texture is immutable, the mipmap levels are clamped.
+    if (IsImmutable())
+        return true;
+    if (mBaseMipmapLevel > std::min(mMaxLevelWithCustomImages, mMaxMipmapLevel))
+        return false;
+    return true;
+}
+
 WebGLTextureFakeBlackStatus
 WebGLTexture::ResolvedFakeBlackStatus() {
     if (MOZ_LIKELY(mFakeBlackStatus != WebGLTextureFakeBlackStatus::Unknown)) {
         return mFakeBlackStatus;
     }
 
     // Determine if the texture needs to be faked as a black texture.
-    // See 3.8.2 Shader Execution in the OpenGL ES 2.0.24 spec.
-
+    // See 3.8.2 Shader Execution in the OpenGL ES 2.0.24 spec, and 3.8.13 in
+    // the OpenGL ES 3.0.4 spec.
+    if (!IsMipmapRangeValid()) {
+        mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
+        return mFakeBlackStatus;
+    }
     for (size_t face = 0; face < mFacesCount; ++face) {
-        if (ImageInfoAtFace(face, 0).mImageDataStatus == WebGLImageDataStatus::NoImageData) {
+        if (ImageInfoAtFace(face, EffectiveBaseMipmapLevel()).mImageDataStatus == WebGLImageDataStatus::NoImageData) {
             // In case of undefined texture image, we don't print any message because this is a very common
             // and often legitimate case (asynchronous texture loading).
             mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
             return mFakeBlackStatus;
         }
     }
 
     const char *msg_rendering_as_black
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -12,16 +12,17 @@
 #include "WebGLStrongTypes.h"
 
 #include "nsWrapperCache.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Assertions.h"
 #include <algorithm>
+#include "nsAlgorithm.h"
 
 namespace mozilla {
 
 // Zero is not an integer power of two.
 inline bool is_pot_assuming_nonnegative(GLsizei x)
 {
     return x && (x & (x-1)) == 0;
 }
@@ -208,16 +209,19 @@ protected:
     TexWrap mWrapS, mWrapT;
 
     size_t mFacesCount, mMaxLevelWithCustomImages;
     nsTArray<ImageInfo> mImageInfos;
 
     bool mHaveGeneratedMipmap; // set by generateMipmap
     bool mImmutable; // set by texStorage*
 
+    size_t mBaseMipmapLevel; // set by texParameter (defaults to 0)
+    size_t mMaxMipmapLevel;  // set by texParameter (defaults to 1000)
+
     WebGLTextureFakeBlackStatus mFakeBlackStatus;
 
     void EnsureMaxLevelWithCustomImagesAtLeast(size_t aMaxLevelWithCustomImages) {
         mMaxLevelWithCustomImages = std::max(mMaxLevelWithCustomImages, aMaxLevelWithCustomImages);
         mImageInfos.EnsureLengthAtLeast((mMaxLevelWithCustomImages + 1) * mFacesCount);
     }
 
     bool CheckFloatTextureFilterParams() const {
@@ -278,16 +282,33 @@ public:
 
     bool IsMipmapCubeComplete() const;
 
     void SetFakeBlackStatus(WebGLTextureFakeBlackStatus x);
 
     bool IsImmutable() const { return mImmutable; }
     void SetImmutable() { mImmutable = true; }
 
+    void SetBaseMipmapLevel(size_t level) { mBaseMipmapLevel = level; }
+    void SetMaxMipmapLevel(size_t level) { mMaxMipmapLevel = level; }
+
+    // Clamping (from ES 3.0.4, section 3.8 - Texturing). When not immutable,
+    // the ranges must be guarded.
+    size_t EffectiveBaseMipmapLevel() const {
+        if (IsImmutable())
+            return std::min(mBaseMipmapLevel, mMaxLevelWithCustomImages);
+        return mBaseMipmapLevel;
+    }
+    size_t EffectiveMaxMipmapLevel() const {
+        if (IsImmutable())
+            return mozilla::clamped(mMaxMipmapLevel, EffectiveBaseMipmapLevel(), mMaxLevelWithCustomImages);
+        return std::min(mMaxMipmapLevel, mMaxLevelWithCustomImages);
+    }
+    bool IsMipmapRangeValid() const;
+
     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