void WebGLTexture::RefreshSwizzle() const { const auto& imageInfo = BaseImageInfo(); const auto& swizzle = imageInfo.mFormat->textureSwizzleRGBA; if (swizzle != mCurSwizzle) { const gl::ScopedBindTexture scopeBindTexture(mContext->gl, mGLName, mTarget.get()); SetSwizzle(mContext->gl, mTarget, swizzle); mCurSwizzle = swizzle; } }
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; }
uint32_t WebGLTexture::EffectiveMaxLevel() const { const auto& imageInfo = BaseImageInfo(); if (!imageInfo.IsDefined()) return mBaseMipmapLevel; uint32_t largestDim = std::max(imageInfo.mWidth, imageInfo.mHeight); if (mTarget == LOCAL_GL_TEXTURE_3D) { largestDim = std::max(largestDim, imageInfo.mDepth); } if (!largestDim) return mBaseMipmapLevel; // GLES 3.0.4, 3.8 - Mipmapping: `floor(log2(largest_of_dims)) + 1` const auto numLevels = FloorLog2Size(largestDim) + 1; const auto maxLevelBySize = mBaseMipmapLevel + numLevels - 1; return std::min<uint32_t>(maxLevelBySize, mMaxMipmapLevel); }
uint32_t WebGLTexture::MaxEffectiveMipmapLevel(uint32_t texUnit) const { WebGLSampler* sampler = mContext->mBoundSamplers[texUnit]; TexMinFilter minFilter = sampler ? sampler->mMinFilter : mMinFilter; if (minFilter == LOCAL_GL_NEAREST || minFilter == LOCAL_GL_LINEAR) { // No mips used. return mBaseMipmapLevel; } const auto& imageInfo = BaseImageInfo(); MOZ_ASSERT(imageInfo.IsDefined()); uint32_t maxLevelBySize = mBaseMipmapLevel + imageInfo.MaxMipmapLevels() - 1; return std::min<uint32_t>(maxLevelBySize, mMaxMipmapLevel); }
bool WebGLTexture::MaxEffectiveMipmapLevel(uint32_t texUnit, uint32_t* const out) const { WebGLSampler* sampler = mContext->mBoundSamplers[texUnit]; TexMinFilter minFilter = sampler ? sampler->mMinFilter : mMinFilter; if (minFilter == LOCAL_GL_NEAREST || minFilter == LOCAL_GL_LINEAR) { // No extra mips used. *out = mBaseMipmapLevel; return true; } const auto& imageInfo = BaseImageInfo(); if (!imageInfo.IsDefined()) return false; uint32_t maxLevelBySize = mBaseMipmapLevel + imageInfo.PossibleMipmapLevels() - 1; *out = std::min<uint32_t>(maxLevelBySize, mMaxMipmapLevel); return true; }
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; }
void WebGLTexture::GenerateMipmap(TexTarget texTarget) { // GLES 3.0.4 p160: // "Mipmap generation replaces texel array levels level base + 1 through q with arrays // derived from the level base array, regardless of their previous contents. All // other mipmap arrays, including the level base array, are left unchanged by this // computation." const ImageInfo& baseImageInfo = BaseImageInfo(); if (!baseImageInfo.IsDefined()) { mContext->ErrorInvalidOperation("generateMipmap: The base level of the texture is" " not defined."); return; } if (IsCubeMap() && !IsCubeComplete()) { mContext->ErrorInvalidOperation("generateMipmap: Cube maps must be \"cube" " complete\"."); return; } if (!mContext->IsWebGL2() && !baseImageInfo.IsPowerOfTwo()) { mContext->ErrorInvalidOperation("generateMipmap: The base level of the texture" " does not have power-of-two dimensions."); return; } auto format = baseImageInfo.mFormat->format; if (format->compression) { mContext->ErrorInvalidOperation("generateMipmap: Texture data at base level is" " compressed."); return; } if (format->hasDepth) { mContext->ErrorInvalidOperation("generateMipmap: Depth textures are not" " supported."); return; } // OpenGL ES 3.0.4 p160: // If the level base array was not specified with an unsized internal format from // table 3.3 or a sized internal format that is both color-renderable and // texture-filterable according to table 3.13, an INVALID_OPERATION error // is generated. const auto usage = baseImageInfo.mFormat; bool canGenerateMipmap = (usage->isRenderable && usage->isFilterable); switch (usage->format->effectiveFormat) { case webgl::EffectiveFormat::Luminance8: case webgl::EffectiveFormat::Alpha8: case webgl::EffectiveFormat::Luminance8Alpha8: // Non-color-renderable formats from Table 3.3. canGenerateMipmap = true; break; default: break; } if (!canGenerateMipmap) { mContext->ErrorInvalidOperation("generateMipmap: Texture at base level is not unsized" " internal format or is not" " color-renderable or texture-filterable."); return; } // Done with validation. Do the operation. mContext->MakeContextCurrent(); gl::GLContext* gl = mContext->gl; if (gl->WorkAroundDriverBugs()) { // bug 696495 - to work around failures in the texture-mips.html test on various drivers, we // set the minification filter before calling glGenerateMipmap. This should not carry a significant performance // overhead so we do it unconditionally. // // note that the choice of GL_NEAREST_MIPMAP_NEAREST really matters. See Chromium bug 101105. gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST_MIPMAP_NEAREST); gl->fGenerateMipmap(texTarget.get()); gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER, mMinFilter.get()); } else { gl->fGenerateMipmap(texTarget.get()); } // Record the results. // Note that we don't use MaxEffectiveMipmapLevel() here, since that returns // mBaseMipmapLevel if the min filter doesn't require mipmaps. const uint32_t lastLevel = mBaseMipmapLevel + baseImageInfo.MaxMipmapLevels() - 1; PopulateMipChain(mBaseMipmapLevel, lastLevel); }
bool WebGLTexture::IsComplete(uint32_t texUnit, const char** const out_reason) const { // 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 ImageInfo& baseImageInfo = BaseImageInfo(); 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). *out_reason = nullptr; return false; } if (!baseImageInfo.mWidth || !baseImageInfo.mHeight || !baseImageInfo.mDepth) { *out_reason = "The dimensions of `level_base` are not all positive."; return false; } // "* The texture is a cube map texture, and is not cube complete." if (IsCubeMap() && !IsCubeComplete()) { *out_reason = "Cubemaps must be \"cube complete\"."; return false; } WebGLSampler* sampler = mContext->mBoundSamplers[texUnit]; TexMinFilter minFilter = sampler ? sampler->mMinFilter : mMinFilter; TexMagFilter magFilter = sampler ? sampler->mMagFilter : mMagFilter; // "* The minification filter requires a mipmap (is neither NEAREST nor LINEAR) and // the texture is not mipmap complete." const bool requiresMipmap = (minFilter != LOCAL_GL_NEAREST && minFilter != LOCAL_GL_LINEAR); if (requiresMipmap && !IsMipmapComplete(texUnit)) { *out_reason = "Because the minification filter requires mipmapping, the texture" " must be \"mipmap complete\"."; return false; } const bool isMinFilteringNearest = (minFilter == LOCAL_GL_NEAREST || minFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST); const bool isMagFilteringNearest = (magFilter == LOCAL_GL_NEAREST); const bool isFilteringNearestOnly = (isMinFilteringNearest && isMagFilteringNearest); if (!isFilteringNearestOnly) { auto formatUsage = baseImageInfo.mFormat; auto format = formatUsage->format; // "* The effective internal format specified for the texture arrays is a sized // internal color format that is not texture-filterable, and either the // magnification filter is not NEAREST or the minification filter is neither // NEAREST nor NEAREST_MIPMAP_NEAREST." // Since all (GLES3) unsized color formats are filterable just like their sized // equivalents, we don't have to care whether its sized or not. if (format->isColorFormat && !formatUsage->isFilterable) { *out_reason = "Because minification or magnification filtering is not NEAREST" " or NEAREST_MIPMAP_NEAREST, and the texture's format is a" " color format, its format must be \"texture-filterable\"."; return false; } // "* The effective internal format specified for the texture arrays is a sized // internal depth or depth and stencil format, the value of // TEXTURE_COMPARE_MODE is NONE[1], and either the magnification filter is not // NEAREST, or the minification filter is neither NEAREST nor // NEAREST_MIPMAP_NEAREST." // [1]: This sounds suspect, but is explicitly noted in the change log for GLES // 3.0.1: // "* Clarify that a texture is incomplete if it has a depth component, no // shadow comparison, and linear filtering (also Bug 9481)." // As of OES_packed_depth_stencil rev #3, the sample code explicitly samples from // a DEPTH_STENCIL_OES texture with a min-filter of LINEAR. Therefore we relax // this restriction if WEBGL_depth_texture is enabled. if (!mContext->IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture)) { if (format->hasDepth && mTexCompareMode != LOCAL_GL_NONE) { *out_reason = "A depth or depth-stencil format with TEXTURE_COMPARE_MODE" " of NONE must have minification or magnification filtering" " of NEAREST or NEAREST_MIPMAP_NEAREST."; return false; } } } // Texture completeness is effectively (though not explicitly) amended for GLES2 by // the "Texture Access" section under $3.8 "Fragment Shaders". This also applies to // vertex shaders, as noted on GLES 2.0.25, p41. if (!mContext->IsWebGL2()) { // GLES 2.0.25, p87-88: // "Calling a sampler from a fragment shader will return (R,G,B,A)=(0,0,0,1) if // any of the following conditions are true:" // "* A two-dimensional sampler is called, the minification filter is one that // requires a mipmap[...], and the sampler's associated texture object is not // complete[.]" // (already covered) // "* A two-dimensional sampler is called, the minification filter is not one that // requires a mipmap (either NEAREST nor[sic] LINEAR), and either dimension of // the level zero array of the associated texture object is not positive." // (already covered) // "* A two-dimensional sampler is called, the corresponding texture image is a // non-power-of-two image[...], and either the texture wrap mode is not // CLAMP_TO_EDGE, or the minification filter is neither NEAREST nor LINEAR." // "* A cube map sampler is called, any of the corresponding texture images are // non-power-of-two images, and either the texture wrap mode is not // CLAMP_TO_EDGE, or the minification filter is neither NEAREST nor LINEAR." if (!baseImageInfo.IsPowerOfTwo()) { TexWrap wrapS = sampler ? sampler->mWrapS : mWrapS; TexWrap wrapT = sampler ? sampler->mWrapT : mWrapT; // "either the texture wrap mode is not CLAMP_TO_EDGE" if (wrapS != LOCAL_GL_CLAMP_TO_EDGE || wrapT != LOCAL_GL_CLAMP_TO_EDGE) { *out_reason = "Non-power-of-two textures must have a wrap mode of" " CLAMP_TO_EDGE."; return false; } // "or the minification filter is neither NEAREST nor LINEAR" if (requiresMipmap) { *out_reason = "Mipmapping requires power-of-two textures."; return false; } } // "* A cube map sampler is called, and either the corresponding cube map texture // image is not cube complete, or TEXTURE_MIN_FILTER is one that requires a // mipmap and the texture is not mipmap cube complete." // (already covered) } 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; }