WebGL2: support TEXTURE_BASE_LEVEL and TEXTURE_MAX_LEVEL (
bug 1080957, r=jgilbert).
--- 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