summaryrefslogtreecommitdiffstats
path: root/dom/canvas/WebGLTexture.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/canvas/WebGLTexture.cpp
parentInitial commit. (diff)
downloadfirefox-esr-upstream/115.8.0esr.tar.xz
firefox-esr-upstream/115.8.0esr.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas/WebGLTexture.cpp')
-rw-r--r--dom/canvas/WebGLTexture.cpp1112
1 files changed, 1112 insertions, 0 deletions
diff --git a/dom/canvas/WebGLTexture.cpp b/dom/canvas/WebGLTexture.cpp
new file mode 100644
index 0000000000..7b50e85ed3
--- /dev/null
+++ b/dom/canvas/WebGLTexture.cpp
@@ -0,0 +1,1112 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebGLTexture.h"
+
+#include <algorithm>
+#include "GLContext.h"
+#include "mozilla/Casting.h"
+#include "mozilla/dom/WebGLRenderingContextBinding.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Scoped.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "ScopedGLHelpers.h"
+#include "WebGLContext.h"
+#include "WebGLContextUtils.h"
+#include "WebGLFormats.h"
+#include "WebGLFramebuffer.h"
+#include "WebGLSampler.h"
+#include "WebGLTexelConversions.h"
+
+namespace mozilla {
+namespace webgl {
+
+/*static*/ const ImageInfo ImageInfo::kUndefined;
+
+size_t ImageInfo::MemoryUsage() const {
+ if (!IsDefined()) return 0;
+
+ size_t samples = mSamples;
+ if (!samples) {
+ samples = 1;
+ }
+
+ const size_t bytesPerTexel = mFormat->format->estimatedBytesPerPixel;
+ return size_t(mWidth) * size_t(mHeight) * size_t(mDepth) * samples *
+ bytesPerTexel;
+}
+
+Maybe<ImageInfo> ImageInfo::NextMip(const GLenum target) const {
+ MOZ_ASSERT(IsDefined());
+
+ auto next = *this;
+
+ if (target == LOCAL_GL_TEXTURE_3D) {
+ if (mWidth <= 1 && mHeight <= 1 && mDepth <= 1) {
+ return {};
+ }
+
+ next.mDepth = std::max(uint32_t(1), next.mDepth / 2);
+ } else {
+ // TEXTURE_2D_ARRAY may have depth != 1, but that's normal.
+ if (mWidth <= 1 && mHeight <= 1) {
+ return {};
+ }
+ }
+ if (next.mUninitializedSlices) {
+ next.mUninitializedSlices = Some(std::vector<bool>(next.mDepth, true));
+ }
+
+ next.mWidth = std::max(uint32_t(1), next.mWidth / 2);
+ next.mHeight = std::max(uint32_t(1), next.mHeight / 2);
+ return Some(next);
+}
+
+} // namespace webgl
+
+////////////////////////////////////////
+
+WebGLTexture::WebGLTexture(WebGLContext* webgl, GLuint tex)
+ : WebGLContextBoundObject(webgl),
+ mGLName(tex),
+ mTarget(LOCAL_GL_NONE),
+ mFaceCount(0),
+ mImmutable(false),
+ mImmutableLevelCount(0),
+ mBaseMipmapLevel(0),
+ mMaxMipmapLevel(1000) {}
+
+WebGLTexture::~WebGLTexture() {
+ for (auto& cur : mImageInfoArr) {
+ cur = webgl::ImageInfo();
+ }
+ InvalidateCaches();
+
+ if (!mContext) return;
+ mContext->gl->fDeleteTextures(1, &mGLName);
+}
+
+size_t WebGLTexture::MemoryUsage() const {
+ size_t accum = 0;
+ for (const auto& cur : mImageInfoArr) {
+ accum += cur.MemoryUsage();
+ }
+ return accum;
+}
+
+// ---------------------------
+
+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();
+}
+
+static bool ZeroTextureData(const WebGLContext* webgl, GLuint tex,
+ TexImageTarget target, uint32_t level,
+ const webgl::ImageInfo& info);
+
+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.mUninitializedSlices)) {
+ 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)) {
+ mContext->ErrorOutOfMemory("Failed to zero tex image data.");
+ *out_initFailed = true;
+ return false;
+ }
+ cur.mUninitializedSlices = Nothing();
+ }
+ }
+
+ const auto next = ref.NextMip(mTarget.get());
+ if (!next) break;
+ ref = next.ref();
+ }
+
+ return true;
+}
+
+Maybe<const WebGLTexture::CompletenessInfo> WebGLTexture::CalcCompletenessInfo(
+ const bool ensureInit, const bool skipMips) const {
+ Maybe<CompletenessInfo> ret = Some(CompletenessInfo());
+
+ // -
+
+ const auto level_base = Es3_level_base();
+ if (level_base > 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, level_base);
+ 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(level_base, 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 level_max = Es3_level_max();
+ const auto maxLevel_aka_q = Es3_q();
+ if (level_base > level_max) { // `level_max` not `q`!
+ ret->incompleteReason = "`level_base > level_max`.";
+ return ret;
+ }
+
+ if (skipMips) return ret;
+
+ if (!IsMipAndCubeComplete(maxLevel_aka_q, ensureInit, &initFailed)) {
+ if (initFailed) return {};
+
+ ret->incompleteReason = "Bad mipmap dimension or format.";
+ return ret;
+ }
+ ret->levels = AutoAssertCast(maxLevel_aka_q - level_base + 1);
+ ret->mipmapComplete = true;
+
+ // -
+
+ return ret;
+}
+
+Maybe<const webgl::SampleableInfo> WebGLTexture::CalcSampleableInfo(
+ const WebGLSampler* const sampler) const {
+ Maybe<webgl::SampleableInfo> ret = Some(webgl::SampleableInfo());
+
+ const bool ensureInit = true;
+ const auto completeness = CalcCompletenessInfo(ensureInit);
+ if (!completeness) return {};
+
+ ret->incompleteReason = completeness->incompleteReason;
+
+ if (!completeness->levels) return ret;
+
+ const auto* sampling = &mSamplingState;
+ if (sampler) {
+ sampling = &sampler->State();
+ }
+ const auto isDepthTex = bool(completeness->usage->format->d);
+ ret->isDepthTexCompare = isDepthTex & bool(sampling->compareMode.get());
+ // Because if it's not a depth texture, we always ignore compareMode.
+
+ const auto& minFilter = sampling->minFilter;
+ const auto& magFilter = sampling->magFilter;
+
+ // -
+
+ const bool needsMips = (minFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST ||
+ minFilter == LOCAL_GL_NEAREST_MIPMAP_LINEAR ||
+ minFilter == LOCAL_GL_LINEAR_MIPMAP_NEAREST ||
+ minFilter == LOCAL_GL_LINEAR_MIPMAP_LINEAR);
+ if (needsMips & !completeness->mipmapComplete) return ret;
+
+ 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) {
+ bool isFilterable = completeness->usage->isFilterable;
+
+ // "* 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)."
+ // In short, depth formats are not filterable, but shadow-samplers are.
+ if (ret->isDepthTexCompare) {
+ isFilterable = true;
+
+ if (mContext->mWarnOnce_DepthTexCompareFilterable) {
+ mContext->mWarnOnce_DepthTexCompareFilterable = false;
+ mContext->GenerateWarning(
+ "Depth texture comparison requests (e.g. `LINEAR`) Filtering, but"
+ " behavior is implementation-defined, and so on some systems will"
+ " sometimes behave as `NEAREST`. (warns once)");
+ }
+ }
+
+ // "* 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 (!isFilterable) {
+ ret->incompleteReason =
+ "Minification or magnification filtering is not"
+ " NEAREST or NEAREST_MIPMAP_NEAREST, and the"
+ " texture's format is not \"texture-filterable\".";
+ return ret;
+ }
+ }
+
+ // 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() && !completeness->powerOfTwo) {
+ // 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."
+ // "either the texture wrap mode is not CLAMP_TO_EDGE"
+ if (sampling->wrapS != LOCAL_GL_CLAMP_TO_EDGE ||
+ sampling->wrapT != LOCAL_GL_CLAMP_TO_EDGE) {
+ ret->incompleteReason =
+ "Non-power-of-two textures must have a wrap mode of"
+ " CLAMP_TO_EDGE.";
+ return ret;
+ }
+
+ // "* 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)
+ }
+
+ // Mark complete.
+ ret->incompleteReason =
+ nullptr; // NB: incompleteReason is also null for undefined
+ ret->levels = completeness->levels; // textures.
+ if (!needsMips && ret->levels) {
+ ret->levels = 1;
+ }
+ ret->usage = completeness->usage;
+ return ret;
+}
+
+const webgl::SampleableInfo* WebGLTexture::GetSampleableInfo(
+ const WebGLSampler* const sampler) const {
+ auto itr = mSamplingCache.Find(sampler);
+ if (!itr) {
+ const auto info = CalcSampleableInfo(sampler);
+ if (!info) return nullptr;
+
+ auto entry = mSamplingCache.MakeEntry(sampler, info.value());
+ entry->AddInvalidator(*this);
+ if (sampler) {
+ entry->AddInvalidator(*sampler);
+ }
+ itr = mSamplingCache.Insert(std::move(entry));
+ }
+ return itr;
+}
+
+// ---------------------------
+
+uint32_t WebGLTexture::Es3_q() 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);
+}
+
+// -
+
+static void SetSwizzle(gl::GLContext* gl, TexTarget target,
+ const GLint* swizzle) {
+ static const GLint kNoSwizzle[4] = {LOCAL_GL_RED, LOCAL_GL_GREEN,
+ LOCAL_GL_BLUE, LOCAL_GL_ALPHA};
+ if (!swizzle) {
+ swizzle = kNoSwizzle;
+ } else if (!gl->IsSupported(gl::GLFeature::texture_swizzle)) {
+ MOZ_CRASH("GFX: Needs swizzle feature to swizzle!");
+ }
+
+ gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_R, swizzle[0]);
+ gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_G, swizzle[1]);
+ gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_B, swizzle[2]);
+ gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_A, swizzle[3]);
+}
+
+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::EnsureImageDataInitialized(const TexImageTarget target,
+ const uint32_t level) {
+ auto& imageInfo = ImageInfoAt(target, level);
+ if (!imageInfo.IsDefined()) return true;
+
+ if (!imageInfo.mUninitializedSlices) return true;
+
+ if (!ZeroTextureData(mContext, mGLName, target, level, imageInfo)) {
+ return false;
+ }
+ imageInfo.mUninitializedSlices = Nothing();
+ return true;
+}
+
+static bool ClearDepthTexture(const WebGLContext& webgl, const GLuint tex,
+ const TexImageTarget imageTarget,
+ const uint32_t level,
+ const webgl::ImageInfo& info) {
+ const auto& gl = webgl.gl;
+ const auto& usage = info.mFormat;
+ const auto& format = usage->format;
+
+ // Depth resources actually clear to 1.0f, not 0.0f!
+ // They are also always renderable.
+ MOZ_ASSERT(usage->IsRenderable());
+ MOZ_ASSERT(info.mUninitializedSlices);
+
+ GLenum attachPoint = LOCAL_GL_DEPTH_ATTACHMENT;
+ GLbitfield clearBits = LOCAL_GL_DEPTH_BUFFER_BIT;
+
+ if (format->s) {
+ attachPoint = LOCAL_GL_DEPTH_STENCIL_ATTACHMENT;
+ clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
+ }
+
+ // -
+
+ gl::ScopedFramebuffer scopedFB(gl);
+ const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB());
+ const webgl::ScopedPrepForResourceClear scopedPrep(webgl);
+
+ const auto fnAttach = [&](const uint32_t z) {
+ switch (imageTarget.get()) {
+ case LOCAL_GL_TEXTURE_3D:
+ case LOCAL_GL_TEXTURE_2D_ARRAY:
+ gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, attachPoint, tex,
+ level, z);
+ break;
+ default:
+ if (attachPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+ gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
+ LOCAL_GL_DEPTH_ATTACHMENT,
+ imageTarget.get(), tex, level);
+ gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
+ LOCAL_GL_STENCIL_ATTACHMENT,
+ imageTarget.get(), tex, level);
+ } else {
+ gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint,
+ imageTarget.get(), tex, level);
+ }
+ break;
+ }
+ };
+
+ for (const auto z : IntegerRange(info.mDepth)) {
+ if ((*info.mUninitializedSlices)[z]) {
+ fnAttach(z);
+ gl->fClear(clearBits);
+ }
+ }
+ const auto& status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
+ const bool isComplete = (status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
+ MOZ_ASSERT(isComplete);
+ return isComplete;
+}
+
+static bool ZeroTextureData(const WebGLContext* webgl, GLuint tex,
+ TexImageTarget target, uint32_t level,
+ const webgl::ImageInfo& info) {
+ // This has one usecase:
+ // Lazy zeroing of uninitialized textures:
+ // a. Before draw.
+ // b. Before partial upload. (TexStorage + TexSubImage)
+
+ // We have no sympathy for this case.
+
+ // "Doctor, it hurts when I do this!" "Well don't do that!"
+ MOZ_ASSERT(info.mUninitializedSlices);
+
+ const auto targetStr = EnumString(target.get());
+ webgl->GenerateWarning(
+ "Tex image %s level %u is incurring lazy initialization.",
+ targetStr.c_str(), level);
+
+ gl::GLContext* gl = webgl->GL();
+ const auto& width = info.mWidth;
+ const auto& height = info.mHeight;
+ const auto& depth = info.mDepth;
+ const auto& usage = info.mFormat;
+
+ GLenum scopeBindTarget;
+ switch (target.get()) {
+ case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
+ case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
+ case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
+ case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
+ case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
+ case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
+ scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP;
+ break;
+ default:
+ scopeBindTarget = target.get();
+ break;
+ }
+ const gl::ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget);
+ const auto& compression = usage->format->compression;
+ if (compression) {
+ auto sizedFormat = usage->format->sizedFormat;
+ MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set");
+
+ const auto fnSizeInBlocks = [](CheckedUint32 pixels,
+ uint8_t pixelsPerBlock) {
+ return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock;
+ };
+
+ const auto widthBlocks = fnSizeInBlocks(width, compression->blockWidth);
+ const auto heightBlocks = fnSizeInBlocks(height, compression->blockHeight);
+
+ CheckedUint32 checkedByteCount = compression->bytesPerBlock;
+ checkedByteCount *= widthBlocks;
+ checkedByteCount *= heightBlocks;
+
+ if (!checkedByteCount.isValid()) return false;
+
+ const size_t sliceByteCount = checkedByteCount.value();
+
+ const auto zeros = UniqueBuffer::Take(calloc(1u, sliceByteCount));
+ if (!zeros) return false;
+
+ // Don't bother with striding it well.
+ // TODO: We shouldn't need to do this for CompressedTexSubImage.
+ webgl::PixelPackingState{}.AssertCurrentUnpack(*gl, webgl->IsWebGL2());
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
+ const auto revert = MakeScopeExit(
+ [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
+
+ GLenum error = 0;
+ for (const auto z : IntegerRange(depth)) {
+ if ((*info.mUninitializedSlices)[z]) {
+ error = DoCompressedTexSubImage(gl, target.get(), level, 0, 0, z, width,
+ height, 1, sizedFormat, sliceByteCount,
+ zeros.get());
+ if (error) break;
+ }
+ }
+ return !error;
+ }
+
+ const auto driverUnpackInfo = usage->idealUnpack;
+ MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set.");
+
+ if (usage->format->d) {
+ // ANGLE_depth_texture does not allow uploads, so we have to clear.
+ // (Restriction because of D3D9)
+ // Also, depth resources are cleared to 1.0f and are always renderable, so
+ // just use FB clears.
+ return ClearDepthTexture(*webgl, tex, target, level, info);
+ }
+
+ const webgl::PackingInfo packing = driverUnpackInfo->ToPacking();
+
+ const auto bytesPerPixel = webgl::BytesPerPixel(packing);
+
+ CheckedUint32 checkedByteCount = bytesPerPixel;
+ checkedByteCount *= width;
+ checkedByteCount *= height;
+
+ if (!checkedByteCount.isValid()) return false;
+
+ const size_t sliceByteCount = checkedByteCount.value();
+
+ const auto zeros = UniqueBuffer::Take(calloc(1u, sliceByteCount));
+ if (!zeros) return false;
+
+ // Don't bother with striding it well.
+ webgl::PixelPackingState{}.AssertCurrentUnpack(*gl, webgl->IsWebGL2());
+ gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
+ const auto revert =
+ MakeScopeExit([&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
+
+ GLenum error = 0;
+ for (const auto z : IntegerRange(depth)) {
+ if ((*info.mUninitializedSlices)[z]) {
+ error = DoTexSubImage(gl, target, level, 0, 0, z, width, height, 1,
+ packing, zeros.get());
+ if (error) break;
+ }
+ }
+ return !error;
+}
+
+template <typename T, typename R>
+static constexpr R Clamp(const T val, const R min, const R max) {
+ if (val < min) return min;
+ if (val > max) return max;
+ return static_cast<R>(val);
+}
+
+void WebGLTexture::ClampLevelBaseAndMax() {
+ if (!mImmutable) return;
+
+ // GLES 3.0.4, p158:
+ // "For immutable-format textures, `level_base` is clamped to the range
+ // `[0, levels-1]`, `level_max` is then clamped to the range `
+ // `[level_base, levels-1]`, where `levels` is the parameter passed to
+ // TexStorage* for the texture object."
+ MOZ_ASSERT(mImmutableLevelCount > 0);
+ const auto oldBase = mBaseMipmapLevel;
+ const auto oldMax = mMaxMipmapLevel;
+ mBaseMipmapLevel = Clamp(mBaseMipmapLevel, 0u, mImmutableLevelCount - 1u);
+ mMaxMipmapLevel =
+ Clamp(mMaxMipmapLevel, mBaseMipmapLevel, mImmutableLevelCount - 1u);
+ if (oldBase != mBaseMipmapLevel &&
+ mBaseMipmapLevelState != MIPMAP_LEVEL_DEFAULT) {
+ mBaseMipmapLevelState = MIPMAP_LEVEL_DIRTY;
+ }
+ if (oldMax != mMaxMipmapLevel &&
+ mMaxMipmapLevelState != MIPMAP_LEVEL_DEFAULT) {
+ mMaxMipmapLevelState = MIPMAP_LEVEL_DIRTY;
+ }
+
+ // Note: This means that immutable textures are *always* texture-complete!
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+// GL calls
+
+bool WebGLTexture::BindTexture(TexTarget texTarget) {
+ const bool isFirstBinding = !mTarget;
+ if (!isFirstBinding && mTarget != texTarget) {
+ mContext->ErrorInvalidOperation(
+ "bindTexture: This texture has already been bound"
+ " to a different target.");
+ return false;
+ }
+
+ mTarget = texTarget;
+
+ mContext->gl->fBindTexture(mTarget.get(), mGLName);
+
+ if (isFirstBinding) {
+ mFaceCount = IsCubeMap() ? 6 : 1;
+
+ gl::GLContext* gl = mContext->gl;
+
+ // 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 we are WebGL 2 though, we'll want to leave it as REPEAT.
+ const bool hasWrapR = gl->IsSupported(gl::GLFeature::texture_3D);
+ if (IsCubeMap() && hasWrapR && !mContext->IsWebGL2()) {
+ gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_WRAP_R,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ }
+ }
+
+ return true;
+}
+
+static constexpr GLint ClampMipmapLevelForDriver(uint32_t level) {
+ return Clamp(level, uint8_t{0}, WebGLTexture::kMaxLevelCount);
+}
+
+void WebGLTexture::GenerateMipmap() {
+ // GLES 3.0.4 p160:
+ // "Mipmap generation replaces texel array levels level base + 1 through q
+ // with arrrays 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."
+ // But only check and init the base level.
+ const bool ensureInit = true;
+ const bool skipMips = true;
+ const auto completeness = CalcCompletenessInfo(ensureInit, skipMips);
+ if (!completeness || !completeness->levels) {
+ mContext->ErrorInvalidOperation(
+ "The texture's base level must be complete.");
+ return;
+ }
+ const auto& usage = completeness->usage;
+ const auto& format = usage->format;
+ if (!mContext->IsWebGL2()) {
+ if (!completeness->powerOfTwo) {
+ mContext->ErrorInvalidOperation(
+ "The base level of the texture does not"
+ " have power-of-two dimensions.");
+ return;
+ }
+ if (format->isSRGB) {
+ mContext->ErrorInvalidOperation(
+ "EXT_sRGB forbids GenerateMipmap with"
+ " sRGB.");
+ return;
+ }
+ }
+
+ if (format->compression) {
+ mContext->ErrorInvalidOperation(
+ "Texture data at base level is compressed.");
+ return;
+ }
+
+ if (format->d) {
+ mContext->ErrorInvalidOperation("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.
+ 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(
+ "Texture at base level is not unsized"
+ " internal format or is not"
+ " color-renderable or texture-filterable.");
+ return;
+ }
+
+ if (usage->IsRenderable() && !usage->IsExplicitlyRenderable()) {
+ mContext->WarnIfImplicit(usage->GetExtensionID());
+ }
+
+ // Done with validation. Do the operation.
+
+ gl::GLContext* gl = mContext->gl;
+
+ if (gl->WorkAroundDriverBugs()) {
+ // If we first set GL_TEXTURE_BASE_LEVEL to a number such as 20, then set
+ // MGL_TEXTURE_MAX_LEVEL to a smaller number like 8, our copy of the
+ // base level will be lowered, but we havn't yet updated the driver, we
+ // should do so now, before calling glGenerateMipmap().
+ if (mBaseMipmapLevelState == MIPMAP_LEVEL_DIRTY) {
+ gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_BASE_LEVEL,
+ ClampMipmapLevelForDriver(mBaseMipmapLevel));
+ mBaseMipmapLevelState = MIPMAP_LEVEL_CLEAN;
+ }
+ if (mMaxMipmapLevelState == MIPMAP_LEVEL_DIRTY) {
+ gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MAX_LEVEL,
+ ClampMipmapLevelForDriver(mMaxMipmapLevel));
+ mMaxMipmapLevelState = MIPMAP_LEVEL_CLEAN;
+ }
+
+ // 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(mTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
+ LOCAL_GL_NEAREST_MIPMAP_NEAREST);
+ gl->fGenerateMipmap(mTarget.get());
+ gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
+ mSamplingState.minFilter.get());
+ } else {
+ gl->fGenerateMipmap(mTarget.get());
+ }
+
+ // Record the results.
+
+ const auto maxLevel = Es3_q();
+ PopulateMipChain(maxLevel);
+}
+
+Maybe<double> WebGLTexture::GetTexParameter(GLenum pname) const {
+ GLint i = 0;
+ GLfloat f = 0.0f;
+
+ switch (pname) {
+ case LOCAL_GL_TEXTURE_BASE_LEVEL:
+ return Some(mBaseMipmapLevel);
+
+ case LOCAL_GL_TEXTURE_MAX_LEVEL:
+ return Some(mMaxMipmapLevel);
+
+ case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
+ return Some(mImmutable);
+
+ case LOCAL_GL_TEXTURE_IMMUTABLE_LEVELS:
+ return Some(uint32_t(mImmutableLevelCount));
+
+ case LOCAL_GL_TEXTURE_MIN_FILTER:
+ case LOCAL_GL_TEXTURE_MAG_FILTER:
+ case LOCAL_GL_TEXTURE_WRAP_S:
+ case LOCAL_GL_TEXTURE_WRAP_T:
+ case LOCAL_GL_TEXTURE_WRAP_R:
+ case LOCAL_GL_TEXTURE_COMPARE_MODE:
+ case LOCAL_GL_TEXTURE_COMPARE_FUNC: {
+ MOZ_ASSERT(mTarget);
+ const gl::ScopedBindTexture autoTex(mContext->gl, mGLName, mTarget.get());
+ mContext->gl->fGetTexParameteriv(mTarget.get(), pname, &i);
+ return Some(i);
+ }
+
+ case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
+ case LOCAL_GL_TEXTURE_MAX_LOD:
+ case LOCAL_GL_TEXTURE_MIN_LOD: {
+ MOZ_ASSERT(mTarget);
+ const gl::ScopedBindTexture autoTex(mContext->gl, mGLName, mTarget.get());
+ mContext->gl->fGetTexParameterfv(mTarget.get(), pname, &f);
+ return Some(f);
+ }
+
+ default:
+ MOZ_CRASH("GFX: Unhandled pname.");
+ }
+}
+
+// Here we have to support all pnames with both int and float params.
+// See this discussion:
+// https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html
+void WebGLTexture::TexParameter(TexTarget texTarget, GLenum pname,
+ const FloatOrInt& param) {
+ bool isPNameValid = false;
+ switch (pname) {
+ // GLES 2.0.25 p76:
+ case LOCAL_GL_TEXTURE_WRAP_S:
+ case LOCAL_GL_TEXTURE_WRAP_T:
+ case LOCAL_GL_TEXTURE_MIN_FILTER:
+ case LOCAL_GL_TEXTURE_MAG_FILTER:
+ isPNameValid = true;
+ break;
+
+ // GLES 3.0.4 p149-150:
+ case LOCAL_GL_TEXTURE_BASE_LEVEL:
+ case LOCAL_GL_TEXTURE_COMPARE_MODE:
+ case LOCAL_GL_TEXTURE_COMPARE_FUNC:
+ case LOCAL_GL_TEXTURE_MAX_LEVEL:
+ case LOCAL_GL_TEXTURE_MAX_LOD:
+ case LOCAL_GL_TEXTURE_MIN_LOD:
+ case LOCAL_GL_TEXTURE_WRAP_R:
+ if (mContext->IsWebGL2()) isPNameValid = true;
+ break;
+
+ case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
+ if (mContext->IsExtensionEnabled(
+ WebGLExtensionID::EXT_texture_filter_anisotropic))
+ isPNameValid = true;
+ break;
+ }
+
+ if (!isPNameValid) {
+ mContext->ErrorInvalidEnumInfo("texParameter: pname", pname);
+ return;
+ }
+
+ ////////////////
+ // Validate params and invalidate if needed.
+
+ bool paramBadEnum = false;
+ bool paramBadValue = false;
+
+ switch (pname) {
+ case LOCAL_GL_TEXTURE_BASE_LEVEL:
+ case LOCAL_GL_TEXTURE_MAX_LEVEL:
+ paramBadValue = (param.i < 0);
+ break;
+
+ case LOCAL_GL_TEXTURE_COMPARE_MODE:
+ paramBadValue = (param.i != LOCAL_GL_NONE &&
+ param.i != LOCAL_GL_COMPARE_REF_TO_TEXTURE);
+ break;
+
+ case LOCAL_GL_TEXTURE_COMPARE_FUNC:
+ switch (param.i) {
+ case LOCAL_GL_LEQUAL:
+ case LOCAL_GL_GEQUAL:
+ case LOCAL_GL_LESS:
+ case LOCAL_GL_GREATER:
+ case LOCAL_GL_EQUAL:
+ case LOCAL_GL_NOTEQUAL:
+ case LOCAL_GL_ALWAYS:
+ case LOCAL_GL_NEVER:
+ break;
+
+ default:
+ paramBadValue = true;
+ break;
+ }
+ break;
+
+ case LOCAL_GL_TEXTURE_MIN_FILTER:
+ switch (param.i) {
+ 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:
+ break;
+
+ default:
+ paramBadEnum = true;
+ break;
+ }
+ break;
+
+ case LOCAL_GL_TEXTURE_MAG_FILTER:
+ switch (param.i) {
+ case LOCAL_GL_NEAREST:
+ case LOCAL_GL_LINEAR:
+ break;
+
+ default:
+ paramBadEnum = true;
+ break;
+ }
+ break;
+
+ case LOCAL_GL_TEXTURE_WRAP_S:
+ case LOCAL_GL_TEXTURE_WRAP_T:
+ case LOCAL_GL_TEXTURE_WRAP_R:
+ switch (param.i) {
+ case LOCAL_GL_CLAMP_TO_EDGE:
+ case LOCAL_GL_MIRRORED_REPEAT:
+ case LOCAL_GL_REPEAT:
+ break;
+
+ default:
+ paramBadEnum = true;
+ break;
+ }
+ break;
+
+ case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
+ if (param.f < 1.0f) paramBadValue = true;
+
+ break;
+ }
+
+ if (paramBadEnum) {
+ if (!param.isFloat) {
+ mContext->ErrorInvalidEnum(
+ "pname 0x%04x: Invalid param"
+ " 0x%04x.",
+ pname, param.i);
+ } else {
+ mContext->ErrorInvalidEnum("pname 0x%04x: Invalid param %g.", pname,
+ param.f);
+ }
+ return;
+ }
+
+ if (paramBadValue) {
+ if (!param.isFloat) {
+ mContext->ErrorInvalidValue(
+ "pname 0x%04x: Invalid param %i"
+ " (0x%x).",
+ pname, param.i, param.i);
+ } else {
+ mContext->ErrorInvalidValue("pname 0x%04x: Invalid param %g.", pname,
+ param.f);
+ }
+ return;
+ }
+
+ ////////////////
+ // Store any needed values
+
+ FloatOrInt clamped = param;
+ bool invalidate = true;
+ switch (pname) {
+ case LOCAL_GL_TEXTURE_BASE_LEVEL: {
+ mBaseMipmapLevel = clamped.i;
+ mBaseMipmapLevelState = MIPMAP_LEVEL_CLEAN;
+ ClampLevelBaseAndMax();
+ clamped = FloatOrInt(ClampMipmapLevelForDriver(mBaseMipmapLevel));
+ break;
+ }
+
+ case LOCAL_GL_TEXTURE_MAX_LEVEL: {
+ mMaxMipmapLevel = clamped.i;
+ mMaxMipmapLevelState = MIPMAP_LEVEL_CLEAN;
+ ClampLevelBaseAndMax();
+ clamped = FloatOrInt(ClampMipmapLevelForDriver(mMaxMipmapLevel));
+ break;
+ }
+
+ case LOCAL_GL_TEXTURE_MIN_FILTER:
+ mSamplingState.minFilter = clamped.i;
+ break;
+
+ case LOCAL_GL_TEXTURE_MAG_FILTER:
+ mSamplingState.magFilter = clamped.i;
+ break;
+
+ case LOCAL_GL_TEXTURE_WRAP_S:
+ mSamplingState.wrapS = clamped.i;
+ break;
+
+ case LOCAL_GL_TEXTURE_WRAP_T:
+ mSamplingState.wrapT = clamped.i;
+ break;
+
+ case LOCAL_GL_TEXTURE_COMPARE_MODE:
+ mSamplingState.compareMode = clamped.i;
+ break;
+
+ default:
+ invalidate = false; // Texture completeness will not change.
+ break;
+ }
+
+ if (invalidate) {
+ InvalidateCaches();
+ }
+
+ ////////////////
+
+ if (!clamped.isFloat) {
+ mContext->gl->fTexParameteri(texTarget.get(), pname, clamped.i);
+ } else {
+ mContext->gl->fTexParameterf(texTarget.get(), pname, clamped.f);
+ }
+}
+
+void WebGLTexture::Truncate() {
+ for (auto& cur : mImageInfoArr) {
+ cur = {};
+ }
+ InvalidateCaches();
+}
+
+} // namespace mozilla