From b8ecdcd3634b7721e955ace68ef666c72002722b Mon Sep 17 00:00:00 2001 From: Benoit Jacob Date: Mon, 23 Aug 2010 17:03:53 -0400 Subject: [PATCH] Bug 586811 - Handle incomplete and NPOT textures - r=vladimir a=blocking2.0 --- content/canvas/src/WebGLContext.cpp | 9 + content/canvas/src/WebGLContext.h | 353 +++++++++++++++++++++++++- content/canvas/src/WebGLContextGL.cpp | 159 ++++++++++-- 3 files changed, 495 insertions(+), 26 deletions(-) diff --git a/content/canvas/src/WebGLContext.cpp b/content/canvas/src/WebGLContext.cpp index 183faaa63137..5b4f9dfbc177 100644 --- a/content/canvas/src/WebGLContext.cpp +++ b/content/canvas/src/WebGLContext.cpp @@ -99,6 +99,9 @@ WebGLContext::WebGLContext() mMapShaders.Init(); mMapFramebuffers.Init(); mMapRenderbuffers.Init(); + + mBlackTexturesAreInitialized = PR_FALSE; + mFakeBlackStatus = DoNotNeedFakeBlack; } WebGLContext::~WebGLContext() @@ -198,6 +201,12 @@ WebGLContext::DestroyResourcesAndContext() mMapRenderbuffers.EnumerateRead(DeleteRenderbufferFunction, gl); mMapRenderbuffers.Clear(); + if (mBlackTexturesAreInitialized) { + gl->fDeleteTextures(1, &mBlackTexture2D); + gl->fDeleteTextures(1, &mBlackTextureCubeMap); + mBlackTexturesAreInitialized = PR_FALSE; + } + // We just got rid of everything, so the context had better // have been going away. printf_stderr("--- WebGL context destroyed: %p\n", gl.get()); diff --git a/content/canvas/src/WebGLContext.h b/content/canvas/src/WebGLContext.h index 8e19db2e5206..1af669443a41 100644 --- a/content/canvas/src/WebGLContext.h +++ b/content/canvas/src/WebGLContext.h @@ -81,6 +81,13 @@ class WebGLUniformLocation; class WebGLZeroingObject; class WebGLContextBoundObject; +enum FakeBlackStatus { DoNotNeedFakeBlack, DoNeedFakeBlack, DontKnowIfNeedFakeBlack }; + +inline PRBool is_pot_assuming_nonnegative(WebGLsizei x) +{ + return (x & (x-1)) == 0; +} + class WebGLObjectBaseRefPtr { protected: @@ -318,6 +325,16 @@ public: // a number that increments every time we have an event that causes // all context resources to be lost. PRUint32 Generation() { return mGeneration.value(); } + + void SetDontKnowIfNeedFakeBlack() { + mFakeBlackStatus = DontKnowIfNeedFakeBlack; + } + + PRBool NeedFakeBlack(); + + void BindFakeBlackTextures(); + void UnbindFakeBlackTextures(); + protected: nsCOMPtr mCanvasElement; nsHTMLCanvasElement *HTMLCanvasElement() { @@ -453,10 +470,17 @@ protected: // WebGL-specific PixelStore parameters PRBool mPixelStoreFlipY, mPixelStorePremultiplyAlpha; + FakeBlackStatus mFakeBlackStatus; + + WebGLuint mBlackTexture2D, mBlackTextureCubeMap; + PRBool mBlackTexturesAreInitialized; + public: // console logging helpers static void LogMessage (const char *fmt, ...); static void LogMessage(const char *fmt, va_list ap); + + friend class WebGLTexture; }; // this class is a mixin for the named type wrappers, and is used @@ -652,8 +676,17 @@ public: WebGLTexture(WebGLContext *context, WebGLuint name) : WebGLContextBoundObject(context), - mName(name), mDeleted(PR_FALSE) - { } + mDeleted(PR_FALSE), mName(name), + mTarget(0), + mMinFilter(LOCAL_GL_NEAREST_MIPMAP_LINEAR), + mMagFilter(LOCAL_GL_LINEAR), + mWrapS(LOCAL_GL_REPEAT), + mWrapT(LOCAL_GL_REPEAT), + mFacesCount(0), + mMaxLevelWithCustomImages(0), + mHaveGeneratedMipmap(PR_FALSE), + mFakeBlackStatus(DontKnowIfNeedFakeBlack) + {} void Delete() { if (mDeleted) @@ -667,9 +700,323 @@ public: NS_DECL_ISUPPORTS NS_DECL_NSIWEBGLTEXTURE + protected: - WebGLuint mName; PRBool mDeleted; + WebGLuint mName; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/////// everything below that point is only used for the texture completeness/npot business +/////// (sections 3.7.10 and 3.8.2 in GL ES 2.0.24 spec) +//////////////////////////////////////////////////////////////////////////////////////////////////// + + struct ImageInfo { + ImageInfo() : mWidth(0), mHeight(0), mFormat(0), mType(0), mIsDefined(PR_FALSE) {} + PRBool operator==(const ImageInfo& a) const { + return mWidth == a.mWidth && mHeight == a.mHeight && + mFormat == a.mFormat && mType == a.mType; + } + PRBool operator!=(const ImageInfo& a) const { + return !(*this == a); + } + PRBool IsSquare() const { + return mWidth == mHeight; + } + PRBool IsPositive() const { + return mWidth > 0 && mHeight > 0; + } + PRBool IsPowerOfTwo() const { + return is_pot_assuming_nonnegative(mWidth) && + is_pot_assuming_nonnegative(mHeight); // negative sizes should never happen (caught in texImage2D...) + } + WebGLsizei mWidth, mHeight; + WebGLenum mFormat, mType; + PRBool mIsDefined; + }; + + ImageInfo& ImageInfoAt(size_t level, size_t face) { +#ifdef DEBUG + if (face >= mFacesCount) + NS_ERROR("wrong face index, must be 0 for TEXTURE_2D and at most 5 for cube maps"); +#endif + // no need to check level as a wrong value would be caught by ElementAt(). + return mImageInfos.ElementAt(level * mFacesCount + face); + } + + const ImageInfo& ImageInfoAt(size_t level, size_t face) const { + return const_cast(this)->ImageInfoAt(level, face); + } + + WebGLenum mTarget; + WebGLenum mMinFilter, mMagFilter, mWrapS, mWrapT; + + size_t mFacesCount, mMaxLevelWithCustomImages; + nsTArray mImageInfos; + + PRBool mHaveGeneratedMipmap; + FakeBlackStatus mFakeBlackStatus; + + void EnsureMaxLevelWithCustomImagesAtLeast(size_t aMaxLevelWithCustomImages) { + mMaxLevelWithCustomImages = PR_MAX(mMaxLevelWithCustomImages, aMaxLevelWithCustomImages); + mImageInfos.EnsureLengthAtLeast((mMaxLevelWithCustomImages + 1) * mFacesCount); + } + + PRBool DoesMinFilterRequireMipmap() const { + return !(mMinFilter == LOCAL_GL_NEAREST || mMinFilter == LOCAL_GL_LINEAR); + } + + PRBool AreBothWrapModesClampToEdge() const { + return mWrapS == LOCAL_GL_CLAMP_TO_EDGE && mWrapT == LOCAL_GL_CLAMP_TO_EDGE; + } + + PRBool DoesTexture2DMipmapHaveAllLevelsConsistentlyDefined(size_t face) const { + if (mHaveGeneratedMipmap) + return PR_TRUE; + + ImageInfo expected = ImageInfoAt(0, face); + + // 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) { + const ImageInfo& actual = ImageInfoAt(level, face); + if (actual != expected) + return PR_FALSE; + expected.mWidth = PR_MAX(1, expected.mWidth >> 1); + expected.mHeight = PR_MAX(1, expected.mHeight >> 1); + + // 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) + return PR_TRUE; + } + + // if we're here, we've exhausted all levels without finding a 1x1 image + return PR_FALSE; + } + +public: + + void SetDontKnowIfNeedFakeBlack() { + mFakeBlackStatus = DontKnowIfNeedFakeBlack; + mContext->SetDontKnowIfNeedFakeBlack(); + } + + void Bind(WebGLenum aTarget) { + // this function should only be called by bindTexture(). + // it assumes that the GL context is already current. + + PRBool firstTimeThisTextureIsBound = mTarget == 0; + + if (!firstTimeThisTextureIsBound && aTarget != mTarget) { + mContext->ErrorInvalidOperation("bindTexture: this texture has already been bound to a different target"); + // very important to return here before modifying texture state! This was the place when I lost a whole day figuring + // very strange 'invalid write' crashes. + return; + } + + mTarget = aTarget; + + mContext->gl->fBindTexture(mTarget, mName); + + if (firstTimeThisTextureIsBound) { + mFacesCount = (mTarget == LOCAL_GL_TEXTURE_2D) ? 1 : 6; + EnsureMaxLevelWithCustomImagesAtLeast(0); + SetDontKnowIfNeedFakeBlack(); + + // thanks to the WebKit people for finding this out: GL_TEXTURE_WRAP_R is not + // present in GLES 2, but is present in GL and it seems as if for cube maps + // we need to set it to GL_CLAMP_TO_EDGE to get the expected GLES behavior. + if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && !mContext->gl->IsGLES2()) + mContext->gl->fTexParameteri(mTarget, LOCAL_GL_TEXTURE_WRAP_R, LOCAL_GL_CLAMP_TO_EDGE); + } + } + + void SetImageInfo(WebGLenum aTarget, WebGLint aLevel, + WebGLsizei aWidth, WebGLsizei aHeight, + WebGLenum aFormat = 0, WebGLenum aType = 0) { + size_t face = 0; + if (aTarget == LOCAL_GL_TEXTURE_2D) { + if (mTarget != LOCAL_GL_TEXTURE_2D) return; + } else { + if (mTarget == LOCAL_GL_TEXTURE_2D) return; + face = aTarget - LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X; + } + + EnsureMaxLevelWithCustomImagesAtLeast(aLevel); + + ImageInfo& imageInfo = ImageInfoAt(aLevel, face); + imageInfo.mWidth = aWidth; + imageInfo.mHeight = aHeight; + if (aFormat) + imageInfo.mFormat = aFormat; + if (aType) + imageInfo.mType = aType; + imageInfo.mIsDefined = PR_TRUE; + + if (aLevel > 0) + SetCustomMipmap(); + + SetDontKnowIfNeedFakeBlack(); + } + + void SetMinFilter(WebGLenum aMinFilter) { + mMinFilter = aMinFilter; + SetDontKnowIfNeedFakeBlack(); + } + void SetMagFilter(WebGLenum aMagFilter) { + mMagFilter = aMagFilter; + SetDontKnowIfNeedFakeBlack(); + } + void SetWrapS(WebGLenum aWrapS) { + mWrapS = aWrapS; + SetDontKnowIfNeedFakeBlack(); + } + void SetWrapT(WebGLenum aWrapT) { + mWrapT = aWrapT; + SetDontKnowIfNeedFakeBlack(); + } + + void SetGeneratedMipmap() { + if (!mHaveGeneratedMipmap) { + mHaveGeneratedMipmap = PR_TRUE; + SetDontKnowIfNeedFakeBlack(); + } + } + + void 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. + + // 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 = ImageInfoAt(0, 0); + NS_ASSERTION(imageInfo.IsPowerOfTwo(), "this texture is NPOT, so how could GenerateMipmap() ever accept it?"); + + WebGLsizei size = PR_MAX(imageInfo.mWidth, imageInfo.mHeight); + + // so, the size is a power of two, let's find its log in base 2. + size_t maxLevel = 0; + for (WebGLsizei n = size; n > 1; n >>= 1) + ++maxLevel; + + EnsureMaxLevelWithCustomImagesAtLeast(maxLevel); + + for (size_t level = 1; level <= maxLevel; ++level) { + // again, since the sizes are powers of two, no need for any max(1,x) computation + imageInfo.mWidth >>= 1; + imageInfo.mHeight >>= 1; + for(size_t face = 0; face < mFacesCount; ++face) + ImageInfoAt(level, face) = imageInfo; + } + } + mHaveGeneratedMipmap = PR_FALSE; + } + + PRBool IsGenerateMipmapAllowed() const { + const ImageInfo &first = ImageInfoAt(0, 0); + if (!first.IsPowerOfTwo()) + return PR_FALSE; + for (size_t face = 0; face < mFacesCount; ++face) { + if (ImageInfoAt(0, face) != first) + return PR_FALSE; + } + return PR_TRUE; + } + + PRBool IsMipmapTexture2DComplete() const { + if (mTarget != LOCAL_GL_TEXTURE_2D) + return PR_FALSE; + if (!mImageInfos[0].IsPositive()) + return PR_FALSE; + if (mHaveGeneratedMipmap) + return PR_TRUE; + return DoesTexture2DMipmapHaveAllLevelsConsistentlyDefined(0); + } + + PRBool IsCubeComplete() const { + if (mTarget != LOCAL_GL_TEXTURE_CUBE_MAP) + return PR_FALSE; + const ImageInfo &first = ImageInfoAt(0, 0); + if (!first.IsPositive() || !first.IsSquare()) + return PR_FALSE; + for (size_t face = 0; face < mFacesCount; ++face) { + if (ImageInfoAt(0, face) != first) + return PR_FALSE; + } + return PR_TRUE; + } + + PRBool IsMipmapCubeComplete() const { + if (!IsCubeComplete()) // in particular, this checks that this is a cube map + return PR_FALSE; + for (size_t face = 0; face < mFacesCount; ++face) { + if (!DoesTexture2DMipmapHaveAllLevelsConsistentlyDefined(face)) + return PR_FALSE; + } + return PR_TRUE; + } + + PRBool NeedFakeBlack() { + // handle this case first, it's the generic case + if (mFakeBlackStatus == DoNotNeedFakeBlack) + return PR_FALSE; + + if (mFakeBlackStatus == DontKnowIfNeedFakeBlack) { + // 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. + + if (mTarget == LOCAL_GL_TEXTURE_2D) + { + if (DoesMinFilterRequireMipmap()) + { + if (!IsMipmapTexture2DComplete() || + !mImageInfos[0].IsPowerOfTwo()) + { + mFakeBlackStatus = DoNeedFakeBlack; + } + } + else // no mipmap required + { + if (!mImageInfos[0].IsPositive() || + (!AreBothWrapModesClampToEdge() && !mImageInfos[0].IsPowerOfTwo())) + { + mFakeBlackStatus = DoNeedFakeBlack; + } + } + } + else if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP) + { + PRBool areAllLevel0ImagesPOT = PR_TRUE; + for (size_t face = 0; face < mFacesCount; ++face) + areAllLevel0ImagesPOT &= ImageInfoAt(0, face).IsPowerOfTwo(); + + if (DoesMinFilterRequireMipmap()) + { + if (!IsMipmapCubeComplete() || + !areAllLevel0ImagesPOT) + { + mFakeBlackStatus = DoNeedFakeBlack; + } + } + else // no mipmap required + { + if (!IsCubeComplete() || + (!AreBothWrapModesClampToEdge() && !areAllLevel0ImagesPOT)) + { + mFakeBlackStatus = DoNeedFakeBlack; + } + } + } + + // we have exhausted all cases where we do need fakeblack, so if the status is still unknown, + // that means that we do NOT need it. + if (mFakeBlackStatus == DontKnowIfNeedFakeBlack) + mFakeBlackStatus = DoNotNeedFakeBlack; + } + + return mFakeBlackStatus == DoNeedFakeBlack; + } }; NS_DEFINE_STATIC_IID_ACCESSOR(WebGLTexture, WEBGLTEXTURE_PRIVATE_IID) diff --git a/content/canvas/src/WebGLContextGL.cpp b/content/canvas/src/WebGLContextGL.cpp index 58b439d134af..39843760e06e 100644 --- a/content/canvas/src/WebGLContextGL.cpp +++ b/content/canvas/src/WebGLContextGL.cpp @@ -62,15 +62,6 @@ using namespace mozilla; -template -bool is_power_of_two(T x) -{ - if (x <= 0) - return false; - else - return (x & (x-1)) == 0; -} - static PRBool BaseTypeAndSizeFromUniformType(WebGLenum uType, WebGLenum *baseType, WebGLint *unitSize); /* Helper macros for when we're just wrapping a gl method, so that @@ -272,7 +263,7 @@ WebGLContext::BindTexture(WebGLenum target, nsIWebGLTexture *tobj) { WebGLuint texturename; WebGLTexture *tex; - PRBool isNull; + PRBool isNull; // allow null object if (!GetConcreteObjectAndGLName("bindTexture", tobj, &tex, &texturename, &isNull)) return NS_OK; @@ -286,7 +277,10 @@ WebGLContext::BindTexture(WebGLenum target, nsIWebGLTexture *tobj) MakeContextCurrent(); - gl->fBindTexture(target, texturename); + if (tex) + tex->Bind(target); + else + gl->fBindTexture(target, 0 /* == texturename */); return NS_OK; } @@ -573,16 +567,21 @@ WebGLContext::CopyTexImage2D(WebGLenum target, return ErrorInvalidEnumInfo("CopyTexImage2D: internal format", internalformat); } - if (border != 0) { - return ErrorInvalidValue("CopyTexImage2D: border != 0"); - } + if (border != 0) + return ErrorInvalidValue("copyTexImage2D: border must be 0"); + + if (level < 0) + return ErrorInvalidValue("copyTexImage2D: level may not be negative"); if (!CanvasUtils::CheckSaneSubrectSize(x,y,width, height, mWidth, mHeight)) return ErrorInvalidOperation("CopyTexImage2D: copied rectangle out of bounds"); - if (!activeBoundTextureForTarget(target)) + WebGLTexture *tex = activeBoundTextureForTarget(target); + if (!tex) return ErrorInvalidOperation("copyTexImage2D: no texture bound to this target"); + tex->SetImageInfo(target, level, width, height); + MakeContextCurrent(); gl->fCopyTexImage2D(target, level, internalformat, x, y, width, height, border); @@ -856,6 +855,94 @@ WebGLContext::DisableVertexAttribArray(WebGLuint index) return NS_OK; } +PRBool +WebGLContext::NeedFakeBlack() +{ + // handle this case first, it's the generic case + if (mFakeBlackStatus == DoNotNeedFakeBlack) + return PR_FALSE; + + if (mFakeBlackStatus == DontKnowIfNeedFakeBlack) { + for (PRInt32 i = 0; i < mGLMaxTextureImageUnits; ++i) { + if ((mBound2DTextures[i] && mBound2DTextures[i]->NeedFakeBlack()) || + (mBoundCubeMapTextures[i] && mBoundCubeMapTextures[i]->NeedFakeBlack())) + { + mFakeBlackStatus = DoNeedFakeBlack; + break; + } + } + + // we have exhausted all cases where we do need fakeblack, so if the status is still unknown, + // that means that we do NOT need it. + if (mFakeBlackStatus == DontKnowIfNeedFakeBlack) + mFakeBlackStatus = DoNotNeedFakeBlack; + } + + return mFakeBlackStatus == DoNeedFakeBlack; +} + +void +WebGLContext::BindFakeBlackTextures() +{ + // this is the generic case: try to return early + if (!NeedFakeBlack()) + return; + + if (!mBlackTexturesAreInitialized) { + const PRUint8 black[] = {0, 0, 0, 255}; + + gl->fGenTextures(1, &mBlackTexture2D); + gl->fBindTexture(LOCAL_GL_TEXTURE_2D, mBlackTexture2D); + gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 1, 1, + 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, &black); + + gl->fGenTextures(1, &mBlackTextureCubeMap); + gl->fBindTexture(LOCAL_GL_TEXTURE_CUBE_MAP, mBlackTextureCubeMap); + for (WebGLuint i = 0; i < 6; ++i) { + gl->fTexImage2D(LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, LOCAL_GL_RGBA, 1, 1, + 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, &black); + } + + // return the texture bindings to the 0 texture to prevent the user from modifying our black textures + gl->fBindTexture(LOCAL_GL_TEXTURE_2D, 0); + gl->fBindTexture(LOCAL_GL_TEXTURE_CUBE_MAP, 0); + + mBlackTexturesAreInitialized = PR_TRUE; + } + + for (PRInt32 i = 0; i < mGLMaxTextureImageUnits; ++i) { + if (mBound2DTextures[i] && mBound2DTextures[i]->NeedFakeBlack()) { + gl->fActiveTexture(LOCAL_GL_TEXTURE0 + i); + gl->fBindTexture(LOCAL_GL_TEXTURE_2D, mBlackTexture2D); + } + if (mBoundCubeMapTextures[i] && mBoundCubeMapTextures[i]->NeedFakeBlack()) { + gl->fActiveTexture(LOCAL_GL_TEXTURE0 + i); + gl->fBindTexture(LOCAL_GL_TEXTURE_CUBE_MAP, mBlackTextureCubeMap); + } + } +} + +void +WebGLContext::UnbindFakeBlackTextures() +{ + // this is the generic case: try to return early + if (!NeedFakeBlack()) + return; + + for (PRInt32 i = 0; i < mGLMaxTextureImageUnits; ++i) { + if (mBound2DTextures[i] && mBound2DTextures[i]->NeedFakeBlack()) { + gl->fActiveTexture(LOCAL_GL_TEXTURE0 + i); + gl->fBindTexture(LOCAL_GL_TEXTURE_2D, mBound2DTextures[i]->GLName()); + } + if (mBoundCubeMapTextures[i] && mBoundCubeMapTextures[i]->NeedFakeBlack()) { + gl->fActiveTexture(LOCAL_GL_TEXTURE0 + i); + gl->fBindTexture(LOCAL_GL_TEXTURE_CUBE_MAP, mBoundCubeMapTextures[i]->GLName()); + } + } + + gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture); +} + NS_IMETHODIMP WebGLContext::DrawArrays(GLenum mode, WebGLint first, WebGLsizei count) { @@ -884,7 +971,9 @@ WebGLContext::DrawArrays(GLenum mode, WebGLint first, WebGLsizei count) MakeContextCurrent(); + BindFakeBlackTextures(); gl->fDrawArrays(mode, first, count); + UnbindFakeBlackTextures(); Invalidate(); @@ -955,7 +1044,9 @@ WebGLContext::DrawElements(WebGLenum mode, WebGLsizei count, WebGLenum type, Web MakeContextCurrent(); + BindFakeBlackTextures(); gl->fDrawElements(mode, count, type, (GLvoid*) (byteOffset)); + UnbindFakeBlackTextures(); Invalidate(); @@ -1144,11 +1235,15 @@ WebGLContext::GenerateMipmap(WebGLenum target) WebGLTexture *tex = activeBoundTextureForTarget(target); - if (!(is_power_of_two(tex->width()) || - is_power_of_two(tex->height()))) { - return ErrorInvalidOperation("generateMipmap: texture width and height must be powers of two"); + if (!tex) + return ErrorInvalidOperation("generateMipmap: no texture is bound to this target"); + + if (!tex->IsGenerateMipmapAllowed()) { + return ErrorInvalidOperation("generateMipmap: texture does not satisfy requirements for generateMipmap"); } + tex->SetGeneratedMipmap(); + MakeContextCurrent(); gl->fGenerateMipmap(target); return NS_OK; @@ -1791,6 +1886,10 @@ nsresult WebGLContext::TexParameter_base(WebGLenum target, WebGLenum pname, if (!ValidateTextureTargetEnum(target, "texParameter: target")) return NS_OK; + WebGLTexture *tex = activeBoundTextureForTarget(target); + if (!tex) + return ErrorInvalidOperation("texParameter: no texture is bound to this target"); + PRBool pnameAndParamAreIncompatible = PR_FALSE; switch (pname) { @@ -1802,6 +1901,7 @@ nsresult WebGLContext::TexParameter_base(WebGLenum target, WebGLenum pname, case LOCAL_GL_LINEAR_MIPMAP_NEAREST: case LOCAL_GL_NEAREST_MIPMAP_LINEAR: case LOCAL_GL_LINEAR_MIPMAP_LINEAR: + tex->SetMinFilter(intParam); break; default: pnameAndParamAreIncompatible = PR_TRUE; @@ -1811,17 +1911,29 @@ nsresult WebGLContext::TexParameter_base(WebGLenum target, WebGLenum pname, switch (intParam) { case LOCAL_GL_NEAREST: case LOCAL_GL_LINEAR: + tex->SetMagFilter(intParam); break; default: pnameAndParamAreIncompatible = PR_TRUE; } break; case LOCAL_GL_TEXTURE_WRAP_S: + switch (intParam) { + case LOCAL_GL_CLAMP_TO_EDGE: + case LOCAL_GL_MIRRORED_REPEAT: + case LOCAL_GL_REPEAT: + tex->SetWrapS(intParam); + break; + default: + pnameAndParamAreIncompatible = PR_TRUE; + } + break; case LOCAL_GL_TEXTURE_WRAP_T: switch (intParam) { case LOCAL_GL_CLAMP_TO_EDGE: case LOCAL_GL_MIRRORED_REPEAT: case LOCAL_GL_REPEAT: + tex->SetWrapT(intParam); break; default: pnameAndParamAreIncompatible = PR_TRUE; @@ -1840,9 +1952,6 @@ nsresult WebGLContext::TexParameter_base(WebGLenum target, WebGLenum pname, pname, floatParam); } - if (!activeBoundTextureForTarget(target)) - return ErrorInvalidOperation("texParameter: no texture is bound to this target"); - MakeContextCurrent(); if (intParamPtr) gl->fTexParameteri(target, pname, intParam); @@ -3078,7 +3187,8 @@ WebGLContext::TexImage2D_base(WebGLenum target, WebGLint level, WebGLenum intern return ErrorInvalidValue("texImage2D: width or height exceeds maximum texture size"); if (level >= 1) { - if (!(is_power_of_two(width) && is_power_of_two(height))) + if (!(is_pot_assuming_nonnegative(width) && + is_pot_assuming_nonnegative(height))) return ErrorInvalidValue("texImage2D: with level > 0, width and height must be powers of two"); } @@ -3105,6 +3215,8 @@ WebGLContext::TexImage2D_base(WebGLenum target, WebGLint level, WebGLenum intern if (!tex) return ErrorInvalidOperation("texImage2D: no texture is bound to this target"); + tex->SetImageInfo(target, level, width, height, format, type); + MakeContextCurrent(); if (byteLength) { @@ -3211,7 +3323,8 @@ WebGLContext::TexSubImage2D_base(WebGLenum target, WebGLint level, return ErrorInvalidValue("texSubImage2D: width or height exceeds maximum texture size"); if (level >= 1) { - if (!(is_power_of_two(width) && is_power_of_two(height))) + if (!(is_pot_assuming_nonnegative(width) && + is_pot_assuming_nonnegative(height))) return ErrorInvalidValue("texSubImage2D: with level > 0, width and height must be powers of two"); }