Bug 1874684 - Part 4: Prefer const references instead of copying Instant values....
[gecko.git] / dom / canvas / WebGLTexture.cpp
blobdb8906f7f5082131955ba57a209a8c9c67296f99
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "WebGLTexture.h"
8 #include <algorithm>
9 #include "GLContext.h"
10 #include "mozilla/Casting.h"
11 #include "mozilla/dom/WebGLRenderingContextBinding.h"
12 #include "mozilla/gfx/Logging.h"
13 #include "mozilla/IntegerRange.h"
14 #include "mozilla/MathAlgorithms.h"
15 #include "mozilla/ScopeExit.h"
16 #include "mozilla/Unused.h"
17 #include "ScopedGLHelpers.h"
18 #include "WebGLContext.h"
19 #include "WebGLContextUtils.h"
20 #include "WebGLFormats.h"
21 #include "WebGLFramebuffer.h"
22 #include "WebGLSampler.h"
23 #include "WebGLTexelConversions.h"
25 namespace mozilla {
26 namespace webgl {
28 /*static*/ const ImageInfo ImageInfo::kUndefined;
30 size_t ImageInfo::MemoryUsage() const {
31 if (!IsDefined()) return 0;
33 size_t samples = mSamples;
34 if (!samples) {
35 samples = 1;
38 const size_t bytesPerTexel = mFormat->format->estimatedBytesPerPixel;
39 return size_t(mWidth) * size_t(mHeight) * size_t(mDepth) * samples *
40 bytesPerTexel;
43 Maybe<ImageInfo> ImageInfo::NextMip(const GLenum target) const {
44 MOZ_ASSERT(IsDefined());
46 auto next = *this;
48 if (target == LOCAL_GL_TEXTURE_3D) {
49 if (mWidth <= 1 && mHeight <= 1 && mDepth <= 1) {
50 return {};
53 next.mDepth = std::max(uint32_t(1), next.mDepth / 2);
54 } else {
55 // TEXTURE_2D_ARRAY may have depth != 1, but that's normal.
56 if (mWidth <= 1 && mHeight <= 1) {
57 return {};
60 if (next.mUninitializedSlices) {
61 next.mUninitializedSlices = Some(std::vector<bool>(next.mDepth, true));
64 next.mWidth = std::max(uint32_t(1), next.mWidth / 2);
65 next.mHeight = std::max(uint32_t(1), next.mHeight / 2);
66 return Some(next);
69 } // namespace webgl
71 ////////////////////////////////////////
73 WebGLTexture::WebGLTexture(WebGLContext* webgl, GLuint tex)
74 : WebGLContextBoundObject(webgl),
75 mGLName(tex),
76 mTarget(LOCAL_GL_NONE),
77 mFaceCount(0),
78 mImmutable(false),
79 mImmutableLevelCount(0),
80 mBaseMipmapLevel(0),
81 mMaxMipmapLevel(1000) {}
83 WebGLTexture::~WebGLTexture() {
84 for (auto& cur : mImageInfoArr) {
85 cur = webgl::ImageInfo();
87 InvalidateCaches();
89 if (!mContext) return;
90 mContext->gl->fDeleteTextures(1, &mGLName);
93 size_t WebGLTexture::MemoryUsage() const {
94 size_t accum = 0;
95 for (const auto& cur : mImageInfoArr) {
96 accum += cur.MemoryUsage();
98 return accum;
101 // ---------------------------
103 void WebGLTexture::PopulateMipChain(const uint32_t maxLevel) {
104 // Used by GenerateMipmap and TexStorage.
105 // Populates based on mBaseMipmapLevel.
107 auto ref = BaseImageInfo();
108 MOZ_ASSERT(ref.mWidth && ref.mHeight && ref.mDepth);
110 for (auto level = mBaseMipmapLevel; level <= maxLevel; ++level) {
111 // GLES 3.0.4, p161
112 // "A cube map texture is mipmap complete if each of the six texture images,
113 // considered individually, is mipmap complete."
115 for (uint8_t face = 0; face < mFaceCount; face++) {
116 auto& cur = ImageInfoAtFace(face, level);
117 cur = ref;
120 const auto next = ref.NextMip(mTarget.get());
121 if (!next) break;
122 ref = next.ref();
124 InvalidateCaches();
127 static bool ZeroTextureData(const WebGLContext* webgl, GLuint tex,
128 TexImageTarget target, uint32_t level,
129 const webgl::ImageInfo& info);
131 bool WebGLTexture::IsMipAndCubeComplete(const uint32_t maxLevel,
132 const bool ensureInit,
133 bool* const out_initFailed) const {
134 *out_initFailed = false;
136 // Reference dimensions based on baseLevel.
137 auto ref = BaseImageInfo();
138 MOZ_ASSERT(ref.mWidth && ref.mHeight && ref.mDepth);
140 for (auto level = mBaseMipmapLevel; level <= maxLevel; ++level) {
141 // GLES 3.0.4, p161
142 // "A cube map texture is mipmap complete if each of the six texture images,
143 // considered individually, is mipmap complete."
145 for (uint8_t face = 0; face < mFaceCount; face++) {
146 auto& cur = ImageInfoAtFace(face, level);
148 // "* The set of mipmap arrays `level_base` through `q` (where `q`
149 // is defined the "Mipmapping" discussion of section 3.8.10) were
150 // each specified with the same effective internal format."
152 // "* The dimensions of the arrays follow the sequence described in
153 // the "Mipmapping" discussion of section 3.8.10."
155 if (cur.mWidth != ref.mWidth || cur.mHeight != ref.mHeight ||
156 cur.mDepth != ref.mDepth || cur.mFormat != ref.mFormat) {
157 return false;
160 if (MOZ_UNLIKELY(ensureInit && cur.mUninitializedSlices)) {
161 auto imageTarget = mTarget.get();
162 if (imageTarget == LOCAL_GL_TEXTURE_CUBE_MAP) {
163 imageTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
165 if (!ZeroTextureData(mContext, mGLName, imageTarget, level, cur)) {
166 mContext->ErrorOutOfMemory("Failed to zero tex image data.");
167 *out_initFailed = true;
168 return false;
170 cur.mUninitializedSlices = Nothing();
174 const auto next = ref.NextMip(mTarget.get());
175 if (!next) break;
176 ref = next.ref();
179 return true;
182 Maybe<const WebGLTexture::CompletenessInfo> WebGLTexture::CalcCompletenessInfo(
183 const bool ensureInit, const bool skipMips) const {
184 Maybe<CompletenessInfo> ret = Some(CompletenessInfo());
186 // -
188 const auto level_base = Es3_level_base();
189 if (level_base > kMaxLevelCount - 1) {
190 ret->incompleteReason = "`level_base` too high.";
191 return ret;
194 // Texture completeness is established at GLES 3.0.4, p160-161.
195 // "[A] texture is complete unless any of the following conditions hold true:"
197 // "* Any dimension of the `level_base` array is not positive."
198 const auto& baseImageInfo = ImageInfoAtFace(0, level_base);
199 if (!baseImageInfo.IsDefined()) {
200 // In case of undefined texture image, we don't print any message because
201 // this is a very common and often legitimate case (asynchronous texture
202 // loading).
203 ret->incompleteReason = nullptr;
204 return ret;
207 if (!baseImageInfo.mWidth || !baseImageInfo.mHeight ||
208 !baseImageInfo.mDepth) {
209 ret->incompleteReason =
210 "The dimensions of `level_base` are not all positive.";
211 return ret;
214 // "* The texture is a cube map texture, and is not cube complete."
215 bool initFailed = false;
216 if (!IsMipAndCubeComplete(level_base, ensureInit, &initFailed)) {
217 if (initFailed) return {};
219 // Can only fail if not cube-complete.
220 ret->incompleteReason = "Cubemaps must be \"cube complete\".";
221 return ret;
223 ret->levels = 1;
224 ret->usage = baseImageInfo.mFormat;
225 RefreshSwizzle();
227 ret->powerOfTwo = mozilla::IsPowerOfTwo(baseImageInfo.mWidth) &&
228 mozilla::IsPowerOfTwo(baseImageInfo.mHeight);
229 if (mTarget == LOCAL_GL_TEXTURE_3D) {
230 ret->powerOfTwo &= mozilla::IsPowerOfTwo(baseImageInfo.mDepth);
233 // -
235 if (!mContext->IsWebGL2() && !ret->powerOfTwo) {
236 // WebGL 1 mipmaps require POT.
237 ret->incompleteReason = "Mipmapping requires power-of-two sizes.";
238 return ret;
241 // "* `level_base <= level_max`"
243 const auto level_max = Es3_level_max();
244 const auto maxLevel_aka_q = Es3_q();
245 if (level_base > level_max) { // `level_max` not `q`!
246 ret->incompleteReason = "`level_base > level_max`.";
247 return ret;
250 if (skipMips) return ret;
252 if (!IsMipAndCubeComplete(maxLevel_aka_q, ensureInit, &initFailed)) {
253 if (initFailed) return {};
255 ret->incompleteReason = "Bad mipmap dimension or format.";
256 return ret;
258 ret->levels = AutoAssertCast(maxLevel_aka_q - level_base + 1);
259 ret->mipmapComplete = true;
261 // -
263 return ret;
266 Maybe<const webgl::SampleableInfo> WebGLTexture::CalcSampleableInfo(
267 const WebGLSampler* const sampler) const {
268 Maybe<webgl::SampleableInfo> ret = Some(webgl::SampleableInfo());
270 const bool ensureInit = true;
271 const auto completeness = CalcCompletenessInfo(ensureInit);
272 if (!completeness) return {};
274 ret->incompleteReason = completeness->incompleteReason;
276 if (!completeness->levels) return ret;
278 const auto* sampling = &mSamplingState;
279 if (sampler) {
280 sampling = &sampler->State();
282 const auto isDepthTex = bool(completeness->usage->format->d);
283 ret->isDepthTexCompare = isDepthTex & bool(sampling->compareMode.get());
284 // Because if it's not a depth texture, we always ignore compareMode.
286 const auto& minFilter = sampling->minFilter;
287 const auto& magFilter = sampling->magFilter;
289 // -
291 const bool needsMips = (minFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST ||
292 minFilter == LOCAL_GL_NEAREST_MIPMAP_LINEAR ||
293 minFilter == LOCAL_GL_LINEAR_MIPMAP_NEAREST ||
294 minFilter == LOCAL_GL_LINEAR_MIPMAP_LINEAR);
295 if (needsMips & !completeness->mipmapComplete) return ret;
297 const bool isMinFilteringNearest =
298 (minFilter == LOCAL_GL_NEAREST ||
299 minFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST);
300 const bool isMagFilteringNearest = (magFilter == LOCAL_GL_NEAREST);
301 const bool isFilteringNearestOnly =
302 (isMinFilteringNearest && isMagFilteringNearest);
303 if (!isFilteringNearestOnly) {
304 bool isFilterable = completeness->usage->isFilterable;
306 // "* The effective internal format specified for the texture arrays is a
307 // sized internal depth or depth and stencil format, the value of
308 // TEXTURE_COMPARE_MODE is NONE[1], and either the magnification filter
309 // is not NEAREST, or the minification filter is neither NEAREST nor
310 // NEAREST_MIPMAP_NEAREST."
311 // [1]: This sounds suspect, but is explicitly noted in the change log for
312 // GLES 3.0.1:
313 // "* Clarify that a texture is incomplete if it has a depth component,
314 // no shadow comparison, and linear filtering (also Bug 9481)."
315 // In short, depth formats are not filterable, but shadow-samplers are.
316 if (ret->isDepthTexCompare) {
317 isFilterable = true;
319 if (mContext->mWarnOnce_DepthTexCompareFilterable) {
320 mContext->mWarnOnce_DepthTexCompareFilterable = false;
321 mContext->GenerateWarning(
322 "Depth texture comparison requests (e.g. `LINEAR`) Filtering, but"
323 " behavior is implementation-defined, and so on some systems will"
324 " sometimes behave as `NEAREST`. (warns once)");
328 // "* The effective internal format specified for the texture arrays is a
329 // sized internal color format that is not texture-filterable, and either
330 // the magnification filter is not NEAREST or the minification filter is
331 // neither NEAREST nor NEAREST_MIPMAP_NEAREST."
332 // Since all (GLES3) unsized color formats are filterable just like their
333 // sized equivalents, we don't have to care whether its sized or not.
334 if (!isFilterable) {
335 ret->incompleteReason =
336 "Minification or magnification filtering is not"
337 " NEAREST or NEAREST_MIPMAP_NEAREST, and the"
338 " texture's format is not \"texture-filterable\".";
339 return ret;
343 // Texture completeness is effectively (though not explicitly) amended for
344 // GLES2 by the "Texture Access" section under $3.8 "Fragment Shaders". This
345 // also applies to vertex shaders, as noted on GLES 2.0.25, p41.
346 if (!mContext->IsWebGL2() && !completeness->powerOfTwo) {
347 // GLES 2.0.25, p87-88:
348 // "Calling a sampler from a fragment shader will return (R,G,B,A)=(0,0,0,1)
349 // if
350 // any of the following conditions are true:"
352 // "* A two-dimensional sampler is called, the minification filter is one
353 // that requires a mipmap[...], and the sampler's associated texture
354 // object is not complete[.]"
355 // (already covered)
357 // "* A two-dimensional sampler is called, the minification filter is
358 // not one that requires a mipmap (either NEAREST nor[sic] LINEAR), and
359 // either dimension of the level zero array of the associated texture
360 // object is not positive."
361 // (already covered)
363 // "* A two-dimensional sampler is called, the corresponding texture
364 // image is a non-power-of-two image[...], and either the texture wrap
365 // mode is not CLAMP_TO_EDGE, or the minification filter is neither
366 // NEAREST nor LINEAR."
368 // "* A cube map sampler is called, any of the corresponding texture
369 // images are non-power-of-two images, and either the texture wrap mode
370 // is not CLAMP_TO_EDGE, or the minification filter is neither NEAREST
371 // nor LINEAR."
372 // "either the texture wrap mode is not CLAMP_TO_EDGE"
373 if (sampling->wrapS != LOCAL_GL_CLAMP_TO_EDGE ||
374 sampling->wrapT != LOCAL_GL_CLAMP_TO_EDGE) {
375 ret->incompleteReason =
376 "Non-power-of-two textures must have a wrap mode of"
377 " CLAMP_TO_EDGE.";
378 return ret;
381 // "* A cube map sampler is called, and either the corresponding cube
382 // map texture image is not cube complete, or TEXTURE_MIN_FILTER is one
383 // that requires a mipmap and the texture is not mipmap cube complete."
384 // (already covered)
387 // Mark complete.
388 ret->incompleteReason =
389 nullptr; // NB: incompleteReason is also null for undefined
390 ret->levels = completeness->levels; // textures.
391 if (!needsMips && ret->levels) {
392 ret->levels = 1;
394 ret->usage = completeness->usage;
395 return ret;
398 const webgl::SampleableInfo* WebGLTexture::GetSampleableInfo(
399 const WebGLSampler* const sampler) const {
400 auto itr = mSamplingCache.Find(sampler);
401 if (!itr) {
402 const auto info = CalcSampleableInfo(sampler);
403 if (!info) return nullptr;
405 auto entry = mSamplingCache.MakeEntry(sampler, info.value());
406 entry->AddInvalidator(*this);
407 if (sampler) {
408 entry->AddInvalidator(*sampler);
410 itr = mSamplingCache.Insert(std::move(entry));
412 return itr;
415 // ---------------------------
417 uint32_t WebGLTexture::Es3_q() const {
418 const auto& imageInfo = BaseImageInfo();
419 if (!imageInfo.IsDefined()) return mBaseMipmapLevel;
421 uint32_t largestDim = std::max(imageInfo.mWidth, imageInfo.mHeight);
422 if (mTarget == LOCAL_GL_TEXTURE_3D) {
423 largestDim = std::max(largestDim, imageInfo.mDepth);
425 if (!largestDim) return mBaseMipmapLevel;
427 // GLES 3.0.4, 3.8 - Mipmapping: `floor(log2(largest_of_dims)) + 1`
428 const auto numLevels = FloorLog2Size(largestDim) + 1;
430 const auto maxLevelBySize = mBaseMipmapLevel + numLevels - 1;
431 return std::min<uint32_t>(maxLevelBySize, mMaxMipmapLevel);
434 // -
436 static void SetSwizzle(gl::GLContext* gl, TexTarget target,
437 const GLint* swizzle) {
438 static const GLint kNoSwizzle[4] = {LOCAL_GL_RED, LOCAL_GL_GREEN,
439 LOCAL_GL_BLUE, LOCAL_GL_ALPHA};
440 if (!swizzle) {
441 swizzle = kNoSwizzle;
442 } else if (!gl->IsSupported(gl::GLFeature::texture_swizzle)) {
443 MOZ_CRASH("GFX: Needs swizzle feature to swizzle!");
446 gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_R, swizzle[0]);
447 gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_G, swizzle[1]);
448 gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_B, swizzle[2]);
449 gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_A, swizzle[3]);
452 void WebGLTexture::RefreshSwizzle() const {
453 const auto& imageInfo = BaseImageInfo();
454 const auto& swizzle = imageInfo.mFormat->textureSwizzleRGBA;
456 if (swizzle != mCurSwizzle) {
457 const gl::ScopedBindTexture scopeBindTexture(mContext->gl, mGLName,
458 mTarget.get());
459 SetSwizzle(mContext->gl, mTarget, swizzle);
460 mCurSwizzle = swizzle;
464 bool WebGLTexture::EnsureImageDataInitialized(const TexImageTarget target,
465 const uint32_t level) {
466 auto& imageInfo = ImageInfoAt(target, level);
467 if (!imageInfo.IsDefined()) return true;
469 if (!imageInfo.mUninitializedSlices) return true;
471 if (!ZeroTextureData(mContext, mGLName, target, level, imageInfo)) {
472 return false;
474 imageInfo.mUninitializedSlices = Nothing();
475 return true;
478 static bool ClearDepthTexture(const WebGLContext& webgl, const GLuint tex,
479 const TexImageTarget imageTarget,
480 const uint32_t level,
481 const webgl::ImageInfo& info) {
482 const auto& gl = webgl.gl;
483 const auto& usage = info.mFormat;
484 const auto& format = usage->format;
486 // Depth resources actually clear to 1.0f, not 0.0f!
487 // They are also always renderable.
488 MOZ_ASSERT(usage->IsRenderable());
489 MOZ_ASSERT(info.mUninitializedSlices);
491 GLenum attachPoint = LOCAL_GL_DEPTH_ATTACHMENT;
492 GLbitfield clearBits = LOCAL_GL_DEPTH_BUFFER_BIT;
494 if (format->s) {
495 attachPoint = LOCAL_GL_DEPTH_STENCIL_ATTACHMENT;
496 clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
499 // -
501 gl::ScopedFramebuffer scopedFB(gl);
502 const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB());
503 const webgl::ScopedPrepForResourceClear scopedPrep(webgl);
505 const auto fnAttach = [&](const uint32_t z) {
506 switch (imageTarget.get()) {
507 case LOCAL_GL_TEXTURE_3D:
508 case LOCAL_GL_TEXTURE_2D_ARRAY:
509 gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, attachPoint, tex,
510 level, z);
511 break;
512 default:
513 if (attachPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
514 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
515 LOCAL_GL_DEPTH_ATTACHMENT,
516 imageTarget.get(), tex, level);
517 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
518 LOCAL_GL_STENCIL_ATTACHMENT,
519 imageTarget.get(), tex, level);
520 } else {
521 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint,
522 imageTarget.get(), tex, level);
524 break;
528 for (const auto z : IntegerRange(info.mDepth)) {
529 if ((*info.mUninitializedSlices)[z]) {
530 fnAttach(z);
531 gl->fClear(clearBits);
534 const auto& status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
535 const bool isComplete = (status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
536 MOZ_ASSERT(isComplete);
537 return isComplete;
540 static bool ZeroTextureData(const WebGLContext* webgl, GLuint tex,
541 TexImageTarget target, uint32_t level,
542 const webgl::ImageInfo& info) {
543 // This has one usecase:
544 // Lazy zeroing of uninitialized textures:
545 // a. Before draw.
546 // b. Before partial upload. (TexStorage + TexSubImage)
548 // We have no sympathy for this case.
550 // "Doctor, it hurts when I do this!" "Well don't do that!"
551 MOZ_ASSERT(info.mUninitializedSlices);
553 const auto targetStr = EnumString(target.get());
554 webgl->GenerateWarning(
555 "Tex image %s level %u is incurring lazy initialization.",
556 targetStr.c_str(), level);
558 gl::GLContext* gl = webgl->GL();
559 const auto& width = info.mWidth;
560 const auto& height = info.mHeight;
561 const auto& depth = info.mDepth;
562 const auto& usage = info.mFormat;
564 GLenum scopeBindTarget;
565 switch (target.get()) {
566 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
567 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
568 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
569 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
570 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
571 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
572 scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP;
573 break;
574 default:
575 scopeBindTarget = target.get();
576 break;
578 const gl::ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget);
579 const auto& compression = usage->format->compression;
580 if (compression) {
581 auto sizedFormat = usage->format->sizedFormat;
582 MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set");
584 const auto fnSizeInBlocks = [](CheckedUint32 pixels,
585 uint8_t pixelsPerBlock) {
586 return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock;
589 const auto widthBlocks = fnSizeInBlocks(width, compression->blockWidth);
590 const auto heightBlocks = fnSizeInBlocks(height, compression->blockHeight);
592 CheckedUint32 checkedByteCount = compression->bytesPerBlock;
593 checkedByteCount *= widthBlocks;
594 checkedByteCount *= heightBlocks;
596 if (!checkedByteCount.isValid()) return false;
598 const size_t sliceByteCount = checkedByteCount.value();
600 const auto zeros = UniqueBuffer::Take(calloc(1u, sliceByteCount));
601 if (!zeros) return false;
603 // Don't bother with striding it well.
604 // TODO: We shouldn't need to do this for CompressedTexSubImage.
605 webgl::PixelPackingState{}.AssertCurrentUnpack(*gl, webgl->IsWebGL2());
606 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
607 const auto revert = MakeScopeExit(
608 [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
610 GLenum error = 0;
611 for (const auto z : IntegerRange(depth)) {
612 if ((*info.mUninitializedSlices)[z]) {
613 error = DoCompressedTexSubImage(gl, target.get(), level, 0, 0, z, width,
614 height, 1, sizedFormat, sliceByteCount,
615 zeros.get());
616 if (error) break;
619 return !error;
622 const auto driverUnpackInfo = usage->idealUnpack;
623 MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set.");
625 if (usage->format->d) {
626 // ANGLE_depth_texture does not allow uploads, so we have to clear.
627 // (Restriction because of D3D9)
628 // Also, depth resources are cleared to 1.0f and are always renderable, so
629 // just use FB clears.
630 return ClearDepthTexture(*webgl, tex, target, level, info);
633 const webgl::PackingInfo packing = driverUnpackInfo->ToPacking();
635 const auto bytesPerPixel = webgl::BytesPerPixel(packing);
637 CheckedUint32 checkedByteCount = bytesPerPixel;
638 checkedByteCount *= width;
639 checkedByteCount *= height;
641 if (!checkedByteCount.isValid()) return false;
643 const size_t sliceByteCount = checkedByteCount.value();
645 const auto zeros = UniqueBuffer::Take(calloc(1u, sliceByteCount));
646 if (!zeros) return false;
648 // Don't bother with striding it well.
649 webgl::PixelPackingState{}.AssertCurrentUnpack(*gl, webgl->IsWebGL2());
650 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
651 const auto revert =
652 MakeScopeExit([&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
654 GLenum error = 0;
655 for (const auto z : IntegerRange(depth)) {
656 if ((*info.mUninitializedSlices)[z]) {
657 error = DoTexSubImage(gl, target, level, 0, 0, z, width, height, 1,
658 packing, zeros.get());
659 if (error) break;
662 return !error;
665 template <typename T, typename R>
666 static constexpr R Clamp(const T val, const R min, const R max) {
667 if (val < min) return min;
668 if (val > max) return max;
669 return static_cast<R>(val);
672 void WebGLTexture::ClampLevelBaseAndMax() {
673 if (!mImmutable) return;
675 // GLES 3.0.4, p158:
676 // "For immutable-format textures, `level_base` is clamped to the range
677 // `[0, levels-1]`, `level_max` is then clamped to the range `
678 // `[level_base, levels-1]`, where `levels` is the parameter passed to
679 // TexStorage* for the texture object."
680 MOZ_ASSERT(mImmutableLevelCount > 0);
681 const auto oldBase = mBaseMipmapLevel;
682 const auto oldMax = mMaxMipmapLevel;
683 mBaseMipmapLevel = Clamp(mBaseMipmapLevel, 0u, mImmutableLevelCount - 1u);
684 mMaxMipmapLevel =
685 Clamp(mMaxMipmapLevel, mBaseMipmapLevel, mImmutableLevelCount - 1u);
686 if (oldBase != mBaseMipmapLevel &&
687 mBaseMipmapLevelState != MIPMAP_LEVEL_DEFAULT) {
688 mBaseMipmapLevelState = MIPMAP_LEVEL_DIRTY;
690 if (oldMax != mMaxMipmapLevel &&
691 mMaxMipmapLevelState != MIPMAP_LEVEL_DEFAULT) {
692 mMaxMipmapLevelState = MIPMAP_LEVEL_DIRTY;
695 // Note: This means that immutable textures are *always* texture-complete!
698 //////////////////////////////////////////////////////////////////////////////////////////
699 // GL calls
701 bool WebGLTexture::BindTexture(TexTarget texTarget) {
702 const bool isFirstBinding = !mTarget;
703 if (!isFirstBinding && mTarget != texTarget) {
704 mContext->ErrorInvalidOperation(
705 "bindTexture: This texture has already been bound"
706 " to a different target.");
707 return false;
710 mTarget = texTarget;
712 mContext->gl->fBindTexture(mTarget.get(), mGLName);
714 if (isFirstBinding) {
715 mFaceCount = IsCubeMap() ? 6 : 1;
717 gl::GLContext* gl = mContext->gl;
719 // Thanks to the WebKit people for finding this out: GL_TEXTURE_WRAP_R
720 // is not present in GLES 2, but is present in GL and it seems as if for
721 // cube maps we need to set it to GL_CLAMP_TO_EDGE to get the expected
722 // GLES behavior.
723 // If we are WebGL 2 though, we'll want to leave it as REPEAT.
724 const bool hasWrapR = gl->IsSupported(gl::GLFeature::texture_3D);
725 if (IsCubeMap() && hasWrapR && !mContext->IsWebGL2()) {
726 gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_WRAP_R,
727 LOCAL_GL_CLAMP_TO_EDGE);
731 return true;
734 static constexpr GLint ClampMipmapLevelForDriver(uint32_t level) {
735 return Clamp(level, uint8_t{0}, WebGLTexture::kMaxLevelCount);
738 void WebGLTexture::GenerateMipmap() {
739 // GLES 3.0.4 p160:
740 // "Mipmap generation replaces texel array levels level base + 1 through q
741 // with arrrays derived from the level base array, regardless of their
742 // previous contents. All other mipmap arrays, including the level base
743 // array, are left unchanged by this computation."
744 // But only check and init the base level.
745 const bool ensureInit = true;
746 const bool skipMips = true;
747 const auto completeness = CalcCompletenessInfo(ensureInit, skipMips);
748 if (!completeness || !completeness->levels) {
749 mContext->ErrorInvalidOperation(
750 "The texture's base level must be complete.");
751 return;
753 const auto& usage = completeness->usage;
754 const auto& format = usage->format;
755 if (!mContext->IsWebGL2()) {
756 if (!completeness->powerOfTwo) {
757 mContext->ErrorInvalidOperation(
758 "The base level of the texture does not"
759 " have power-of-two dimensions.");
760 return;
762 if (format->isSRGB) {
763 mContext->ErrorInvalidOperation(
764 "EXT_sRGB forbids GenerateMipmap with"
765 " sRGB.");
766 return;
770 if (format->compression) {
771 mContext->ErrorInvalidOperation(
772 "Texture data at base level is compressed.");
773 return;
776 if (format->d) {
777 mContext->ErrorInvalidOperation("Depth textures are not supported.");
778 return;
781 // OpenGL ES 3.0.4 p160:
782 // If the level base array was not specified with an unsized internal format
783 // from table 3.3 or a sized internal format that is both color-renderable and
784 // texture-filterable according to table 3.13, an INVALID_OPERATION error
785 // is generated.
786 bool canGenerateMipmap = (usage->IsRenderable() && usage->isFilterable);
787 switch (usage->format->effectiveFormat) {
788 case webgl::EffectiveFormat::Luminance8:
789 case webgl::EffectiveFormat::Alpha8:
790 case webgl::EffectiveFormat::Luminance8Alpha8:
791 // Non-color-renderable formats from Table 3.3.
792 canGenerateMipmap = true;
793 break;
794 default:
795 break;
798 if (!canGenerateMipmap) {
799 mContext->ErrorInvalidOperation(
800 "Texture at base level is not unsized"
801 " internal format or is not"
802 " color-renderable or texture-filterable.");
803 return;
806 if (usage->IsRenderable() && !usage->IsExplicitlyRenderable()) {
807 mContext->WarnIfImplicit(usage->GetExtensionID());
810 // Done with validation. Do the operation.
812 gl::GLContext* gl = mContext->gl;
814 if (gl->WorkAroundDriverBugs()) {
815 // If we first set GL_TEXTURE_BASE_LEVEL to a number such as 20, then set
816 // MGL_TEXTURE_MAX_LEVEL to a smaller number like 8, our copy of the
817 // base level will be lowered, but we havn't yet updated the driver, we
818 // should do so now, before calling glGenerateMipmap().
819 if (mBaseMipmapLevelState == MIPMAP_LEVEL_DIRTY) {
820 gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_BASE_LEVEL,
821 ClampMipmapLevelForDriver(mBaseMipmapLevel));
822 mBaseMipmapLevelState = MIPMAP_LEVEL_CLEAN;
824 if (mMaxMipmapLevelState == MIPMAP_LEVEL_DIRTY) {
825 gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MAX_LEVEL,
826 ClampMipmapLevelForDriver(mMaxMipmapLevel));
827 mMaxMipmapLevelState = MIPMAP_LEVEL_CLEAN;
830 // bug 696495 - to work around failures in the texture-mips.html test on
831 // various drivers, we set the minification filter before calling
832 // glGenerateMipmap. This should not carry a significant performance
833 // overhead so we do it unconditionally.
835 // note that the choice of GL_NEAREST_MIPMAP_NEAREST really matters. See
836 // Chromium bug 101105.
837 gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
838 LOCAL_GL_NEAREST_MIPMAP_NEAREST);
839 gl->fGenerateMipmap(mTarget.get());
840 gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
841 mSamplingState.minFilter.get());
842 } else {
843 gl->fGenerateMipmap(mTarget.get());
846 // Record the results.
848 const auto maxLevel = Es3_q();
849 PopulateMipChain(maxLevel);
852 Maybe<double> WebGLTexture::GetTexParameter(GLenum pname) const {
853 GLint i = 0;
854 GLfloat f = 0.0f;
856 switch (pname) {
857 case LOCAL_GL_TEXTURE_BASE_LEVEL:
858 return Some(mBaseMipmapLevel);
860 case LOCAL_GL_TEXTURE_MAX_LEVEL:
861 return Some(mMaxMipmapLevel);
863 case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
864 return Some(mImmutable);
866 case LOCAL_GL_TEXTURE_IMMUTABLE_LEVELS:
867 return Some(uint32_t(mImmutableLevelCount));
869 case LOCAL_GL_TEXTURE_MIN_FILTER:
870 case LOCAL_GL_TEXTURE_MAG_FILTER:
871 case LOCAL_GL_TEXTURE_WRAP_S:
872 case LOCAL_GL_TEXTURE_WRAP_T:
873 case LOCAL_GL_TEXTURE_WRAP_R:
874 case LOCAL_GL_TEXTURE_COMPARE_MODE:
875 case LOCAL_GL_TEXTURE_COMPARE_FUNC: {
876 MOZ_ASSERT(mTarget);
877 const gl::ScopedBindTexture autoTex(mContext->gl, mGLName, mTarget.get());
878 mContext->gl->fGetTexParameteriv(mTarget.get(), pname, &i);
879 return Some(i);
882 case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
883 case LOCAL_GL_TEXTURE_MAX_LOD:
884 case LOCAL_GL_TEXTURE_MIN_LOD: {
885 MOZ_ASSERT(mTarget);
886 const gl::ScopedBindTexture autoTex(mContext->gl, mGLName, mTarget.get());
887 mContext->gl->fGetTexParameterfv(mTarget.get(), pname, &f);
888 return Some(f);
891 default:
892 MOZ_CRASH("GFX: Unhandled pname.");
896 // Here we have to support all pnames with both int and float params.
897 // See this discussion:
898 // https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html
899 void WebGLTexture::TexParameter(TexTarget texTarget, GLenum pname,
900 const FloatOrInt& param) {
901 bool isPNameValid = false;
902 switch (pname) {
903 // GLES 2.0.25 p76:
904 case LOCAL_GL_TEXTURE_WRAP_S:
905 case LOCAL_GL_TEXTURE_WRAP_T:
906 case LOCAL_GL_TEXTURE_MIN_FILTER:
907 case LOCAL_GL_TEXTURE_MAG_FILTER:
908 isPNameValid = true;
909 break;
911 // GLES 3.0.4 p149-150:
912 case LOCAL_GL_TEXTURE_BASE_LEVEL:
913 case LOCAL_GL_TEXTURE_COMPARE_MODE:
914 case LOCAL_GL_TEXTURE_COMPARE_FUNC:
915 case LOCAL_GL_TEXTURE_MAX_LEVEL:
916 case LOCAL_GL_TEXTURE_MAX_LOD:
917 case LOCAL_GL_TEXTURE_MIN_LOD:
918 case LOCAL_GL_TEXTURE_WRAP_R:
919 if (mContext->IsWebGL2()) isPNameValid = true;
920 break;
922 case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
923 if (mContext->IsExtensionEnabled(
924 WebGLExtensionID::EXT_texture_filter_anisotropic))
925 isPNameValid = true;
926 break;
929 if (!isPNameValid) {
930 mContext->ErrorInvalidEnumInfo("texParameter: pname", pname);
931 return;
934 ////////////////
935 // Validate params and invalidate if needed.
937 bool paramBadEnum = false;
938 bool paramBadValue = false;
940 switch (pname) {
941 case LOCAL_GL_TEXTURE_BASE_LEVEL:
942 case LOCAL_GL_TEXTURE_MAX_LEVEL:
943 paramBadValue = (param.i < 0);
944 break;
946 case LOCAL_GL_TEXTURE_COMPARE_MODE:
947 paramBadValue = (param.i != LOCAL_GL_NONE &&
948 param.i != LOCAL_GL_COMPARE_REF_TO_TEXTURE);
949 break;
951 case LOCAL_GL_TEXTURE_COMPARE_FUNC:
952 switch (param.i) {
953 case LOCAL_GL_LEQUAL:
954 case LOCAL_GL_GEQUAL:
955 case LOCAL_GL_LESS:
956 case LOCAL_GL_GREATER:
957 case LOCAL_GL_EQUAL:
958 case LOCAL_GL_NOTEQUAL:
959 case LOCAL_GL_ALWAYS:
960 case LOCAL_GL_NEVER:
961 break;
963 default:
964 paramBadValue = true;
965 break;
967 break;
969 case LOCAL_GL_TEXTURE_MIN_FILTER:
970 switch (param.i) {
971 case LOCAL_GL_NEAREST:
972 case LOCAL_GL_LINEAR:
973 case LOCAL_GL_NEAREST_MIPMAP_NEAREST:
974 case LOCAL_GL_LINEAR_MIPMAP_NEAREST:
975 case LOCAL_GL_NEAREST_MIPMAP_LINEAR:
976 case LOCAL_GL_LINEAR_MIPMAP_LINEAR:
977 break;
979 default:
980 paramBadEnum = true;
981 break;
983 break;
985 case LOCAL_GL_TEXTURE_MAG_FILTER:
986 switch (param.i) {
987 case LOCAL_GL_NEAREST:
988 case LOCAL_GL_LINEAR:
989 break;
991 default:
992 paramBadEnum = true;
993 break;
995 break;
997 case LOCAL_GL_TEXTURE_WRAP_S:
998 case LOCAL_GL_TEXTURE_WRAP_T:
999 case LOCAL_GL_TEXTURE_WRAP_R:
1000 switch (param.i) {
1001 case LOCAL_GL_CLAMP_TO_EDGE:
1002 case LOCAL_GL_MIRRORED_REPEAT:
1003 case LOCAL_GL_REPEAT:
1004 break;
1006 default:
1007 paramBadEnum = true;
1008 break;
1010 break;
1012 case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
1013 if (param.f < 1.0f) paramBadValue = true;
1015 break;
1018 if (paramBadEnum) {
1019 if (!param.isFloat) {
1020 mContext->ErrorInvalidEnum(
1021 "pname 0x%04x: Invalid param"
1022 " 0x%04x.",
1023 pname, param.i);
1024 } else {
1025 mContext->ErrorInvalidEnum("pname 0x%04x: Invalid param %g.", pname,
1026 param.f);
1028 return;
1031 if (paramBadValue) {
1032 if (!param.isFloat) {
1033 mContext->ErrorInvalidValue(
1034 "pname 0x%04x: Invalid param %i"
1035 " (0x%x).",
1036 pname, param.i, param.i);
1037 } else {
1038 mContext->ErrorInvalidValue("pname 0x%04x: Invalid param %g.", pname,
1039 param.f);
1041 return;
1044 ////////////////
1045 // Store any needed values
1047 FloatOrInt clamped = param;
1048 bool invalidate = true;
1049 switch (pname) {
1050 case LOCAL_GL_TEXTURE_BASE_LEVEL: {
1051 mBaseMipmapLevel = clamped.i;
1052 mBaseMipmapLevelState = MIPMAP_LEVEL_CLEAN;
1053 ClampLevelBaseAndMax();
1054 clamped = FloatOrInt(ClampMipmapLevelForDriver(mBaseMipmapLevel));
1055 break;
1058 case LOCAL_GL_TEXTURE_MAX_LEVEL: {
1059 mMaxMipmapLevel = clamped.i;
1060 mMaxMipmapLevelState = MIPMAP_LEVEL_CLEAN;
1061 ClampLevelBaseAndMax();
1062 clamped = FloatOrInt(ClampMipmapLevelForDriver(mMaxMipmapLevel));
1063 break;
1066 case LOCAL_GL_TEXTURE_MIN_FILTER:
1067 mSamplingState.minFilter = clamped.i;
1068 break;
1070 case LOCAL_GL_TEXTURE_MAG_FILTER:
1071 mSamplingState.magFilter = clamped.i;
1072 break;
1074 case LOCAL_GL_TEXTURE_WRAP_S:
1075 mSamplingState.wrapS = clamped.i;
1076 break;
1078 case LOCAL_GL_TEXTURE_WRAP_T:
1079 mSamplingState.wrapT = clamped.i;
1080 break;
1082 case LOCAL_GL_TEXTURE_COMPARE_MODE:
1083 mSamplingState.compareMode = clamped.i;
1084 break;
1086 default:
1087 invalidate = false; // Texture completeness will not change.
1088 break;
1091 if (invalidate) {
1092 InvalidateCaches();
1095 ////////////////
1097 if (!clamped.isFloat) {
1098 mContext->gl->fTexParameteri(texTarget.get(), pname, clamped.i);
1099 } else {
1100 mContext->gl->fTexParameterf(texTarget.get(), pname, clamped.f);
1104 void WebGLTexture::Truncate() {
1105 for (auto& cur : mImageInfoArr) {
1106 cur = {};
1108 InvalidateCaches();
1111 } // namespace mozilla