void WebGLTexture::SetCustomMipmap() { if (mHaveGeneratedMipmap) { if (!IsMipmapRangeValid()) return; // 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()); MOZ_ASSERT(mContext->IsWebGL2() || imageInfo.IsPowerOfTwo(), "This texture is NPOT, so how could GenerateMipmap() ever" " accept it?"); size_t maxRelativeLevel = MipmapLevelsForSize(imageInfo); size_t maxLevel = EffectiveBaseMipmapLevel() + maxRelativeLevel; EnsureMaxLevelWithCustomImagesAtLeast(maxLevel); 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; }
bool WebGLTexture::AreAllLevel0ImageInfosEqual() const { for (size_t face = 1; face < mFacesCount; ++face) { if (ImageInfoAtFace(face, 0) != ImageInfoAtFace(0, 0)) return false; } return true; }
void WebGLTexture::PopulateMipChain(uint32_t firstLevel, uint32_t lastLevel) { const ImageInfo& baseImageInfo = ImageInfoAtFace(0, firstLevel); MOZ_ASSERT(baseImageInfo.IsDefined()); uint32_t refWidth = baseImageInfo.mWidth; uint32_t refHeight = baseImageInfo.mHeight; uint32_t refDepth = baseImageInfo.mDepth; if (!refWidth || !refHeight || !refDepth) return; for (uint32_t level = firstLevel + 1; level <= lastLevel; level++) { bool isMinimal = (refWidth == 1 && refHeight == 1); if (mTarget == LOCAL_GL_TEXTURE_3D) { isMinimal &= (refDepth == 1); } // Higher levels are unaffected. if (isMinimal) break; refWidth = std::max(uint32_t(1), refWidth / 2); refHeight = std::max(uint32_t(1), refHeight / 2); if (mTarget == LOCAL_GL_TEXTURE_3D) { // But not TEXTURE_2D_ARRAY! refDepth = std::max(uint32_t(1), refDepth / 2); } const ImageInfo cur(baseImageInfo.mFormat, refWidth, refHeight, refDepth, baseImageInfo.IsDataInitialized()); SetImageInfosAtLevel(level, cur); } }
bool WebGLTexture::ResolveForDraw(const char* funcName, uint32_t texUnit, FakeBlackType* const out_fakeBlack) { if (!mIsResolved) { if (!GetFakeBlackType(funcName, texUnit, &mResolved_FakeBlack)) return false; // Check which swizzle we should use. Since the texture must be complete at this // point, just grab the format off any valid image. const GLint* newSwizzle = nullptr; if (mResolved_FakeBlack == FakeBlackType::None) { const auto& cur = ImageInfoAtFace(0, mBaseMipmapLevel); newSwizzle = cur.mFormat->textureSwizzleRGBA; } // Only set the swizzle if it changed since last time we did it. if (newSwizzle != mResolved_Swizzle) { mResolved_Swizzle = newSwizzle; // Set the new swizzle! mContext->gl->fActiveTexture(LOCAL_GL_TEXTURE0 + texUnit); SetSwizzle(mContext->gl, mTarget, mResolved_Swizzle); mContext->gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mContext->mActiveTexture); } mIsResolved = true; } *out_fakeBlack = mResolved_FakeBlack; return true; }
void WebGLTexture::SetImageInfosAtLevel(uint32_t level, const ImageInfo& newInfo) { for (uint8_t i = 0; i < mFaceCount; i++) { ImageInfoAtFace(i, level) = newInfo; } InvalidateResolveCache(); }
void WebGLTexture::SetImageInfosAtLevel(const char* funcName, uint32_t level, const ImageInfo& newInfo) { for (uint8_t i = 0; i < mFaceCount; i++) { ImageInfoAtFace(i, level).Set(funcName, newInfo); } InvalidateResolveCache(); }
bool WebGLTexture::IsMipAndCubeComplete(const uint32_t maxLevel, const bool ensureInit, bool* const out_initFailed) const { *out_initFailed = false; // Reference dimensions based on baseLevel. auto ref = BaseImageInfo(); MOZ_ASSERT(ref.mWidth && ref.mHeight && ref.mDepth); for (auto level = mBaseMipmapLevel; level <= maxLevel; ++level) { // GLES 3.0.4, p161 // "A cube map texture is mipmap complete if each of the six texture images, // considered individually, is mipmap complete." for (uint8_t face = 0; face < mFaceCount; face++) { auto& cur = ImageInfoAtFace(face, level); // "* The set of mipmap arrays `level_base` through `q` (where `q` // is defined the "Mipmapping" discussion of section 3.8.10) were // each specified with the same effective internal format." // "* The dimensions of the arrays follow the sequence described in // the "Mipmapping" discussion of section 3.8.10." if (cur.mWidth != ref.mWidth || cur.mHeight != ref.mHeight || cur.mDepth != ref.mDepth || cur.mFormat != ref.mFormat) { return false; } if (MOZ_UNLIKELY(ensureInit && !cur.mHasData)) { auto imageTarget = mTarget.get(); if (imageTarget == LOCAL_GL_TEXTURE_CUBE_MAP) { imageTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face; } if (!ZeroTextureData(mContext, mGLName, imageTarget, level, cur.mFormat, cur.mWidth, cur.mHeight, cur.mDepth)) { mContext->ErrorOutOfMemory("Failed to zero tex image data."); *out_initFailed = true; return false; } cur.mHasData = true; } } const auto next = ref.NextMip(mTarget.get()); if (!next) break; ref = next.ref(); } return true; }
size_t WebGLTexture::MemoryUsage() const { if (IsDeleted()) return 0; size_t result = 0; for(size_t face = 0; face < mFacesCount; face++) { for(size_t level = 0; level <= mMaxLevelWithCustomImages; level++) { result += ImageInfoAtFace(face, level).MemoryUsage(); } } return result; }
void WebGLTexture::PopulateMipChain(const uint32_t maxLevel) { // Used by GenerateMipmap and TexStorage. // Populates based on mBaseMipmapLevel. auto ref = BaseImageInfo(); MOZ_ASSERT(ref.mWidth && ref.mHeight && ref.mDepth); for (auto level = mBaseMipmapLevel; level <= maxLevel; ++level) { // GLES 3.0.4, p161 // "A cube map texture is mipmap complete if each of the six texture images, // considered individually, is mipmap complete." for (uint8_t face = 0; face < mFaceCount; face++) { auto& cur = ImageInfoAtFace(face, level); cur = ref; } const auto next = ref.NextMip(mTarget.get()); if (!next) break; ref = next.ref(); } InvalidateCaches(); }
bool WebGLTexture::IsCubeComplete() const { // GLES 3.0.4, p161 // "[...] a cube map texture is cube complete if the following conditions all hold // true: // * The `level_base` arrays of each of the six texture images making up the cube map // have identical, positive, and square dimensions. // * The `level_base` arrays were each specified with the same effective internal // format." // Note that "cube complete" does not imply "mipmap complete". const ImageInfo& reference = BaseImageInfo(); if (!reference.IsDefined()) return false; auto refWidth = reference.mWidth; auto refFormat = reference.mFormat; for (uint8_t face = 0; face < mFaceCount; face++) { const ImageInfo& cur = ImageInfoAtFace(face, mBaseMipmapLevel); if (!cur.IsDefined()) return false; MOZ_ASSERT(cur.mDepth == 1); if (cur.mFormat != refFormat || // Check effective formats. cur.mWidth != refWidth || // Check both width and height against refWidth to cur.mHeight != refWidth) // to enforce positive and square dimensions. { return false; } } return true; }
bool WebGLTexture::GetFakeBlackType(const char* funcName, uint32_t texUnit, FakeBlackType* const out_fakeBlack) { const char* incompleteReason; if (!IsComplete(texUnit, &incompleteReason)) { if (incompleteReason) { mContext->GenerateWarning("%s: Active texture %u for target 0x%04x is" " 'incomplete', and will be rendered as" " RGBA(0,0,0,1), as per the GLES 2.0.24 $3.8.2: %s", funcName, texUnit, mTarget.get(), incompleteReason); } *out_fakeBlack = FakeBlackType::RGBA0001; return true; } // We may still want FakeBlack as an optimization for uninitialized image data. bool hasUninitializedData = false; bool hasInitializedData = false; const auto maxLevel = MaxEffectiveMipmapLevel(texUnit); MOZ_ASSERT(mBaseMipmapLevel <= maxLevel); for (uint32_t level = mBaseMipmapLevel; level <= maxLevel; level++) { for (uint8_t face = 0; face < mFaceCount; face++) { const auto& cur = ImageInfoAtFace(face, level); if (cur.IsDataInitialized()) hasInitializedData = true; else hasUninitializedData = true; } } MOZ_ASSERT(hasUninitializedData || hasInitializedData); if (!hasUninitializedData) { *out_fakeBlack = FakeBlackType::None; return true; } if (!hasInitializedData) { const auto format = ImageInfoAtFace(0, mBaseMipmapLevel).mFormat->format; if (format->isColorFormat) { *out_fakeBlack = (format->hasAlpha ? FakeBlackType::RGBA0000 : FakeBlackType::RGBA0001); return true; } mContext->GenerateWarning("%s: Active texture %u for target 0x%04x is" " uninitialized, and will be (perhaps slowly) cleared" " by the implementation.", funcName, texUnit, mTarget.get()); } else { mContext->GenerateWarning("%s: Active texture %u for target 0x%04x contains" " TexImages with uninitialized data along with" " TexImages with initialized data, forcing the" " implementation to (slowly) initialize the" " uninitialized TexImages.", funcName, texUnit, mTarget.get()); } GLenum baseImageTarget = mTarget.get(); if (baseImageTarget == LOCAL_GL_TEXTURE_CUBE_MAP) baseImageTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X; for (uint32_t level = mBaseMipmapLevel; level <= maxLevel; level++) { for (uint8_t face = 0; face < mFaceCount; face++) { TexImageTarget imageTarget = baseImageTarget + face; if (!EnsureImageDataInitialized(funcName, imageTarget, level)) return false; // The world just exploded. } } *out_fakeBlack = FakeBlackType::None; return true; }
bool WebGLTexture::IsMipmapComplete(uint32_t texUnit) const { MOZ_ASSERT(DoesMinFilterRequireMipmap()); // GLES 3.0.4, p161 const uint32_t maxLevel = MaxEffectiveMipmapLevel(texUnit); // "* `level_base <= level_max`" if (mBaseMipmapLevel > maxLevel) return false; // Make a copy so we can modify it. const ImageInfo& baseImageInfo = BaseImageInfo(); if (!baseImageInfo.IsDefined()) return false; // Reference dimensions based on the current level. uint32_t refWidth = baseImageInfo.mWidth; uint32_t refHeight = baseImageInfo.mHeight; uint32_t refDepth = baseImageInfo.mDepth; MOZ_ASSERT(refWidth && refHeight && refDepth); for (uint32_t level = mBaseMipmapLevel; level <= maxLevel; level++) { // "A cube map texture is mipmap complete if each of the six texture images, // considered individually, is mipmap complete." for (uint8_t face = 0; face < mFaceCount; face++) { const ImageInfo& cur = ImageInfoAtFace(face, level); // "* The set of mipmap arrays `level_base` through `q` (where `q` is defined // the "Mipmapping" discussion of section 3.8.10) were each specified with // the same effective internal format." // "* The dimensions of the arrays follow the sequence described in the // "Mipmapping" discussion of section 3.8.10." if (cur.mWidth != refWidth || cur.mHeight != refHeight || cur.mDepth != refDepth || cur.mFormat != baseImageInfo.mFormat) { return false; } } // GLES 3.0.4, p158: // "[...] until the last array is reached with dimension 1 x 1 x 1." if (refWidth == 1 && refHeight == 1 && refDepth == 1) { break; } refWidth = std::max(uint32_t(1), refWidth / 2); refHeight = std::max(uint32_t(1), refHeight / 2); refDepth = std::max(uint32_t(1), refDepth / 2); } return true; }
bool WebGLTexture::IsMipmapComplete(const char* funcName, uint32_t texUnit, bool* const out_initFailed) { *out_initFailed = false; MOZ_ASSERT(DoesMinFilterRequireMipmap()); // GLES 3.0.4, p161 uint32_t maxLevel; if (!MaxEffectiveMipmapLevel(texUnit, &maxLevel)) return false; // "* `level_base <= level_max`" if (mBaseMipmapLevel > maxLevel) return false; // Make a copy so we can modify it. const ImageInfo& baseImageInfo = BaseImageInfo(); // Reference dimensions based on the current level. uint32_t refWidth = baseImageInfo.mWidth; uint32_t refHeight = baseImageInfo.mHeight; uint32_t refDepth = baseImageInfo.mDepth; MOZ_ASSERT(refWidth && refHeight && refDepth); for (uint32_t level = mBaseMipmapLevel; level <= maxLevel; level++) { if (!EnsureLevelInitialized(funcName, level)) { *out_initFailed = true; return false; } // "A cube map texture is mipmap complete if each of the six texture images, // considered individually, is mipmap complete." for (uint8_t face = 0; face < mFaceCount; face++) { const ImageInfo& cur = ImageInfoAtFace(face, level); // "* The set of mipmap arrays `level_base` through `q` (where `q` is defined // the "Mipmapping" discussion of section 3.8.10) were each specified with // the same effective internal format." // "* The dimensions of the arrays follow the sequence described in the // "Mipmapping" discussion of section 3.8.10." if (cur.mWidth != refWidth || cur.mHeight != refHeight || cur.mDepth != refDepth || cur.mFormat != baseImageInfo.mFormat) { return false; } } // GLES 3.0.4, p158: // "[...] until the last array is reached with dimension 1 x 1 x 1." if (mTarget == LOCAL_GL_TEXTURE_3D) { if (refWidth == 1 && refHeight == 1 && refDepth == 1) { break; } refDepth = std::max(uint32_t(1), refDepth / 2); } else { // TEXTURE_2D_ARRAY may have depth != 1, but that's normal. if (refWidth == 1 && refHeight == 1) { break; } } refWidth = std::max(uint32_t(1), refWidth / 2); refHeight = std::max(uint32_t(1), refHeight / 2); } 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, 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) { WebGLImageDataStatus status = ImageInfoAtFace(face, EffectiveBaseMipmapLevel()).mImageDataStatus; if (status == 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 preamble[] = "A texture is going to be rendered as if it were" " black, as per the OpenGL ES 2.0.24 spec section" " 3.8.2, because it"; if (mTarget == LOCAL_GL_TEXTURE_2D || mTarget == LOCAL_GL_TEXTURE_3D) { int dim = mTarget == LOCAL_GL_TEXTURE_2D ? 2 : 3; if (DoesMinFilterRequireMipmap()) { if (!IsMipmapComplete()) { mContext->GenerateWarning("%s is a %dD texture, with a" " minification filter requiring a" " mipmap, and is not mipmap complete" " (as defined in section 3.7.10).", preamble, dim); mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture; } else if (!mContext->IsWebGL2() && !ImageInfoBase().IsPowerOfTwo()) { mContext->GenerateWarning("%s is a %dD texture, with a" " minification filter requiring a" " mipmap, and either its width or" " height is not a power of two.", preamble, dim); mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture; } } else { // No mipmap required here. if (!ImageInfoBase().IsPositive()) { mContext->GenerateWarning("%s is a %dD texture and its width or" " height is equal to zero.", preamble, dim); mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture; } else if (!AreBothWrapModesClampToEdge() && !mContext->IsWebGL2() && !ImageInfoBase().IsPowerOfTwo()) { mContext->GenerateWarning("%s is a %dD texture, with a" " minification filter not requiring a" " mipmap, with its width or height" " not a power of two, and with a wrap" " mode different from CLAMP_TO_EDGE.", preamble, dim); mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture; } } } else { // Cube map bool legalImageSize = true; if (!mContext->IsWebGL2()) { for (size_t face = 0; face < mFacesCount; ++face) legalImageSize &= ImageInfoAtFace(face, 0).IsPowerOfTwo(); } if (DoesMinFilterRequireMipmap()) { if (!IsMipmapCubeComplete()) { mContext->GenerateWarning("%s is a cube map texture, with a" " minification filter requiring a" " mipmap, and is not mipmap cube" " complete (as defined in section" " 3.7.10).", preamble); mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture; } else if (!legalImageSize) { mContext->GenerateWarning("%s is a cube map texture, with a" " minification filter requiring a" " mipmap, and either the width or the" " height of some level 0 image is not" " a power of two.", preamble); mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture; } } else // no mipmap required { if (!IsCubeComplete()) { mContext->GenerateWarning("%s is a cube map texture, with a" " minification filter not requiring a" " mipmap, and is not cube complete" " (as defined in section 3.7.10).", preamble); mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture; } else if (!AreBothWrapModesClampToEdge() && !legalImageSize) { mContext->GenerateWarning("%s is a cube map texture, with a" " minification filter not requiring a" " mipmap, with some level 0 image" " having width or height not a power" " of two, and with a wrap mode" " different from CLAMP_TO_EDGE.", preamble); mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture; } } } TexType type = TypeFromInternalFormat(ImageInfoBase().mEffectiveInternalFormat); const char* badFormatText = nullptr; const char* extText = nullptr; if (type == LOCAL_GL_FLOAT && !Context()->IsExtensionEnabled(WebGLExtensionID::OES_texture_float_linear)) { badFormatText = "FLOAT"; extText = "OES_texture_float_linear"; } else if (type == LOCAL_GL_HALF_FLOAT && !Context()->IsExtensionEnabled(WebGLExtensionID::OES_texture_half_float_linear)) { badFormatText = "HALF_FLOAT"; extText = "OES_texture_half_float_linear"; } const char* badFilterText = nullptr; if (badFormatText) { if (mMinFilter == LOCAL_GL_LINEAR || mMinFilter == LOCAL_GL_LINEAR_MIPMAP_LINEAR || mMinFilter == LOCAL_GL_LINEAR_MIPMAP_NEAREST || mMinFilter == LOCAL_GL_NEAREST_MIPMAP_LINEAR) { badFilterText = "minification"; } else if (mMagFilter == LOCAL_GL_LINEAR) { badFilterText = "magnification"; } } if (badFilterText) { mContext->GenerateWarning("%s is a texture with a linear %s filter," " which is not compatible with format %s by" " default. Try enabling the %s extension, if" " supported.", preamble, badFilterText, badFormatText, extText); mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture; } // We have exhausted all cases of incomplete textures, where we would need opaque black. // We may still need transparent black in case of uninitialized image data. bool hasUninitializedImageData = false; for (size_t level = 0; level <= mMaxLevelWithCustomImages; ++level) { for (size_t face = 0; face < mFacesCount; ++face) { bool cur = (ImageInfoAtFace(face, level).mImageDataStatus == WebGLImageDataStatus::UninitializedImageData); hasUninitializedImageData |= cur; } } if (hasUninitializedImageData) { bool hasAnyInitializedImageData = false; for (size_t level = 0; level <= mMaxLevelWithCustomImages; ++level) { for (size_t face = 0; face < mFacesCount; ++face) { if (ImageInfoAtFace(face, level).mImageDataStatus == WebGLImageDataStatus::InitializedImageData) { hasAnyInitializedImageData = true; break; } } if (hasAnyInitializedImageData) { break; } } 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 = TexImageTargetForTargetAndFace(mTarget, face); const ImageInfo& imageInfo = ImageInfoAt(imageTarget, level); if (imageInfo.mImageDataStatus == WebGLImageDataStatus::UninitializedImageData) { EnsureInitializedImageData(imageTarget, level); } } } mFakeBlackStatus = WebGLTextureFakeBlackStatus::NotNeeded; } else { // The texture only contains uninitialized image data. In this case, // we can use a black texture for it. mFakeBlackStatus = WebGLTextureFakeBlackStatus::UninitializedImageData; } } // 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 == WebGLTextureFakeBlackStatus::Unknown) { mFakeBlackStatus = WebGLTextureFakeBlackStatus::NotNeeded; } MOZ_ASSERT(mFakeBlackStatus != WebGLTextureFakeBlackStatus::Unknown); return mFakeBlackStatus; }
Maybe<const WebGLTexture::CompletenessInfo> WebGLTexture::CalcCompletenessInfo( const bool ensureInit, const bool skipMips) const { Maybe<CompletenessInfo> ret = Some(CompletenessInfo()); // - if (mBaseMipmapLevel > kMaxLevelCount - 1) { ret->incompleteReason = "`level_base` too high."; return ret; } // Texture completeness is established at GLES 3.0.4, p160-161. // "[A] texture is complete unless any of the following conditions hold true:" // "* Any dimension of the `level_base` array is not positive." const auto& baseImageInfo = ImageInfoAtFace(0, mBaseMipmapLevel); if (!baseImageInfo.IsDefined()) { // 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). ret->incompleteReason = nullptr; return ret; } if (!baseImageInfo.mWidth || !baseImageInfo.mHeight || !baseImageInfo.mDepth) { ret->incompleteReason = "The dimensions of `level_base` are not all positive."; return ret; } // "* The texture is a cube map texture, and is not cube complete." bool initFailed = false; if (!IsMipAndCubeComplete(mBaseMipmapLevel, ensureInit, &initFailed)) { if (initFailed) return {}; // Can only fail if not cube-complete. ret->incompleteReason = "Cubemaps must be \"cube complete\"."; return ret; } ret->levels = 1; ret->usage = baseImageInfo.mFormat; RefreshSwizzle(); ret->powerOfTwo = mozilla::IsPowerOfTwo(baseImageInfo.mWidth) && mozilla::IsPowerOfTwo(baseImageInfo.mHeight); if (mTarget == LOCAL_GL_TEXTURE_3D) { ret->powerOfTwo &= mozilla::IsPowerOfTwo(baseImageInfo.mDepth); } // - if (!mContext->IsWebGL2() && !ret->powerOfTwo) { // WebGL 1 mipmaps require POT. ret->incompleteReason = "Mipmapping requires power-of-two sizes."; return ret; } // "* `level_base <= level_max`" const auto maxLevel = EffectiveMaxLevel(); if (mBaseMipmapLevel > maxLevel) { ret->incompleteReason = "`level_base > level_max`."; return ret; } if (skipMips) return ret; if (!IsMipAndCubeComplete(maxLevel, ensureInit, &initFailed)) { if (initFailed) return {}; ret->incompleteReason = "Bad mipmap dimension or format."; return ret; } ret->levels = maxLevel - mBaseMipmapLevel + 1; ret->mipmapComplete = true; // - return ret; }