Bug 1700051: part 26) Correct typo in comment of `mozInlineSpellWordUtil::BuildSoftTe...
[gecko.git] / dom / canvas / WebGLTexture.cpp
blob0d955d25d7e7590e7c94637eca5d2d1e6b352ffe
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/Scoped.h"
16 #include "mozilla/ScopeExit.h"
17 #include "mozilla/Unused.h"
18 #include "ScopedGLHelpers.h"
19 #include "WebGLContext.h"
20 #include "WebGLContextUtils.h"
21 #include "WebGLFormats.h"
22 #include "WebGLFramebuffer.h"
23 #include "WebGLSampler.h"
24 #include "WebGLTexelConversions.h"
26 namespace mozilla {
27 namespace webgl {
29 /*static*/ const ImageInfo ImageInfo::kUndefined;
31 size_t ImageInfo::MemoryUsage() const {
32 if (!IsDefined()) return 0;
34 size_t samples = mSamples;
35 if (!samples) {
36 samples = 1;
39 const size_t bytesPerTexel = mFormat->format->estimatedBytesPerPixel;
40 return size_t(mWidth) * size_t(mHeight) * size_t(mDepth) * samples *
41 bytesPerTexel;
44 Maybe<ImageInfo> ImageInfo::NextMip(const GLenum target) const {
45 MOZ_ASSERT(IsDefined());
47 auto next = *this;
49 if (target == LOCAL_GL_TEXTURE_3D) {
50 if (mWidth <= 1 && mHeight <= 1 && mDepth <= 1) {
51 return {};
54 next.mDepth = std::max(uint32_t(1), next.mDepth / 2);
55 } else {
56 // TEXTURE_2D_ARRAY may have depth != 1, but that's normal.
57 if (mWidth <= 1 && mHeight <= 1) {
58 return {};
61 if (next.mUninitializedSlices) {
62 next.mUninitializedSlices = Some(std::vector<bool>(next.mDepth, true));
65 next.mWidth = std::max(uint32_t(1), next.mWidth / 2);
66 next.mHeight = std::max(uint32_t(1), next.mHeight / 2);
67 return Some(next);
70 } // namespace webgl
72 ////////////////////////////////////////
74 WebGLTexture::WebGLTexture(WebGLContext* webgl, GLuint tex)
75 : WebGLContextBoundObject(webgl),
76 mGLName(tex),
77 mTarget(LOCAL_GL_NONE),
78 mFaceCount(0),
79 mImmutable(false),
80 mImmutableLevelCount(0),
81 mBaseMipmapLevel(0),
82 mMaxMipmapLevel(1000) {}
84 WebGLTexture::~WebGLTexture() {
85 for (auto& cur : mImageInfoArr) {
86 cur = webgl::ImageInfo();
88 InvalidateCaches();
90 if (!mContext) return;
91 mContext->gl->fDeleteTextures(1, &mGLName);
94 size_t WebGLTexture::MemoryUsage() const {
95 size_t accum = 0;
96 for (const auto& cur : mImageInfoArr) {
97 accum += cur.MemoryUsage();
99 return accum;
102 // ---------------------------
104 void WebGLTexture::PopulateMipChain(const uint32_t maxLevel) {
105 // Used by GenerateMipmap and TexStorage.
106 // Populates based on mBaseMipmapLevel.
108 auto ref = BaseImageInfo();
109 MOZ_ASSERT(ref.mWidth && ref.mHeight && ref.mDepth);
111 for (auto level = mBaseMipmapLevel; level <= maxLevel; ++level) {
112 // GLES 3.0.4, p161
113 // "A cube map texture is mipmap complete if each of the six texture images,
114 // considered individually, is mipmap complete."
116 for (uint8_t face = 0; face < mFaceCount; face++) {
117 auto& cur = ImageInfoAtFace(face, level);
118 cur = ref;
121 const auto next = ref.NextMip(mTarget.get());
122 if (!next) break;
123 ref = next.ref();
125 InvalidateCaches();
128 static bool ZeroTextureData(const WebGLContext* webgl, GLuint tex,
129 TexImageTarget target, uint32_t level,
130 const webgl::ImageInfo& info);
132 bool WebGLTexture::IsMipAndCubeComplete(const uint32_t maxLevel,
133 const bool ensureInit,
134 bool* const out_initFailed) const {
135 *out_initFailed = false;
137 // Reference dimensions based on baseLevel.
138 auto ref = BaseImageInfo();
139 MOZ_ASSERT(ref.mWidth && ref.mHeight && ref.mDepth);
141 for (auto level = mBaseMipmapLevel; level <= maxLevel; ++level) {
142 // GLES 3.0.4, p161
143 // "A cube map texture is mipmap complete if each of the six texture images,
144 // considered individually, is mipmap complete."
146 for (uint8_t face = 0; face < mFaceCount; face++) {
147 auto& cur = ImageInfoAtFace(face, level);
149 // "* The set of mipmap arrays `level_base` through `q` (where `q`
150 // is defined the "Mipmapping" discussion of section 3.8.10) were
151 // each specified with the same effective internal format."
153 // "* The dimensions of the arrays follow the sequence described in
154 // the "Mipmapping" discussion of section 3.8.10."
156 if (cur.mWidth != ref.mWidth || cur.mHeight != ref.mHeight ||
157 cur.mDepth != ref.mDepth || cur.mFormat != ref.mFormat) {
158 return false;
161 if (MOZ_UNLIKELY(ensureInit && cur.mUninitializedSlices)) {
162 auto imageTarget = mTarget.get();
163 if (imageTarget == LOCAL_GL_TEXTURE_CUBE_MAP) {
164 imageTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
166 if (!ZeroTextureData(mContext, mGLName, imageTarget, level, cur)) {
167 mContext->ErrorOutOfMemory("Failed to zero tex image data.");
168 *out_initFailed = true;
169 return false;
171 cur.mUninitializedSlices = Nothing();
175 const auto next = ref.NextMip(mTarget.get());
176 if (!next) break;
177 ref = next.ref();
180 return true;
183 Maybe<const WebGLTexture::CompletenessInfo> WebGLTexture::CalcCompletenessInfo(
184 const bool ensureInit, const bool skipMips) const {
185 Maybe<CompletenessInfo> ret = Some(CompletenessInfo());
187 // -
189 const auto level_base = Es3_level_base();
190 if (level_base > kMaxLevelCount - 1) {
191 ret->incompleteReason = "`level_base` too high.";
192 return ret;
195 // Texture completeness is established at GLES 3.0.4, p160-161.
196 // "[A] texture is complete unless any of the following conditions hold true:"
198 // "* Any dimension of the `level_base` array is not positive."
199 const auto& baseImageInfo = ImageInfoAtFace(0, level_base);
200 if (!baseImageInfo.IsDefined()) {
201 // In case of undefined texture image, we don't print any message because
202 // this is a very common and often legitimate case (asynchronous texture
203 // loading).
204 ret->incompleteReason = nullptr;
205 return ret;
208 if (!baseImageInfo.mWidth || !baseImageInfo.mHeight ||
209 !baseImageInfo.mDepth) {
210 ret->incompleteReason =
211 "The dimensions of `level_base` are not all positive.";
212 return ret;
215 // "* The texture is a cube map texture, and is not cube complete."
216 bool initFailed = false;
217 if (!IsMipAndCubeComplete(level_base, ensureInit, &initFailed)) {
218 if (initFailed) return {};
220 // Can only fail if not cube-complete.
221 ret->incompleteReason = "Cubemaps must be \"cube complete\".";
222 return ret;
224 ret->levels = 1;
225 ret->usage = baseImageInfo.mFormat;
226 RefreshSwizzle();
228 ret->powerOfTwo = mozilla::IsPowerOfTwo(baseImageInfo.mWidth) &&
229 mozilla::IsPowerOfTwo(baseImageInfo.mHeight);
230 if (mTarget == LOCAL_GL_TEXTURE_3D) {
231 ret->powerOfTwo &= mozilla::IsPowerOfTwo(baseImageInfo.mDepth);
234 // -
236 if (!mContext->IsWebGL2() && !ret->powerOfTwo) {
237 // WebGL 1 mipmaps require POT.
238 ret->incompleteReason = "Mipmapping requires power-of-two sizes.";
239 return ret;
242 // "* `level_base <= level_max`"
244 const auto level_max = Es3_level_max();
245 const auto maxLevel_aka_q = Es3_q();
246 if (level_base > level_max) { // `level_max` not `q`!
247 ret->incompleteReason = "`level_base > level_max`.";
248 return ret;
251 if (skipMips) return ret;
253 if (!IsMipAndCubeComplete(maxLevel_aka_q, ensureInit, &initFailed)) {
254 if (initFailed) return {};
256 ret->incompleteReason = "Bad mipmap dimension or format.";
257 return ret;
259 ret->levels = AutoAssertCast(maxLevel_aka_q - level_base + 1);
260 ret->mipmapComplete = true;
262 // -
264 return ret;
267 Maybe<const webgl::SampleableInfo> WebGLTexture::CalcSampleableInfo(
268 const WebGLSampler* const sampler) const {
269 Maybe<webgl::SampleableInfo> ret = Some(webgl::SampleableInfo());
271 const bool ensureInit = true;
272 const auto completeness = CalcCompletenessInfo(ensureInit);
273 if (!completeness) return {};
275 ret->incompleteReason = completeness->incompleteReason;
277 if (!completeness->levels) return ret;
279 const auto* sampling = &mSamplingState;
280 if (sampler) {
281 sampling = &sampler->State();
283 const auto isDepthTex = bool(completeness->usage->format->d);
284 ret->isDepthTexCompare = isDepthTex & bool(sampling->compareMode.get());
285 // Because if it's not a depth texture, we always ignore compareMode.
287 const auto& minFilter = sampling->minFilter;
288 const auto& magFilter = sampling->magFilter;
290 // -
292 const bool needsMips = (minFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST ||
293 minFilter == LOCAL_GL_NEAREST_MIPMAP_LINEAR ||
294 minFilter == LOCAL_GL_LINEAR_MIPMAP_NEAREST ||
295 minFilter == LOCAL_GL_LINEAR_MIPMAP_LINEAR);
296 if (needsMips & !completeness->mipmapComplete) return ret;
298 const bool isMinFilteringNearest =
299 (minFilter == LOCAL_GL_NEAREST ||
300 minFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST);
301 const bool isMagFilteringNearest = (magFilter == LOCAL_GL_NEAREST);
302 const bool isFilteringNearestOnly =
303 (isMinFilteringNearest && isMagFilteringNearest);
304 if (!isFilteringNearestOnly) {
305 bool isFilterable = completeness->usage->isFilterable;
307 // "* The effective internal format specified for the texture arrays is a
308 // sized internal depth or depth and stencil format, the value of
309 // TEXTURE_COMPARE_MODE is NONE[1], and either the magnification filter
310 // is not NEAREST, or the minification filter is neither NEAREST nor
311 // NEAREST_MIPMAP_NEAREST."
312 // [1]: This sounds suspect, but is explicitly noted in the change log for
313 // GLES 3.0.1:
314 // "* Clarify that a texture is incomplete if it has a depth component,
315 // no shadow comparison, and linear filtering (also Bug 9481)."
316 // In short, depth formats are not filterable, but shadow-samplers are.
317 if (ret->isDepthTexCompare) {
318 isFilterable = true;
321 // "* The effective internal format specified for the texture arrays is a
322 // sized internal color format that is not texture-filterable, and either
323 // the magnification filter is not NEAREST or the minification filter is
324 // neither NEAREST nor NEAREST_MIPMAP_NEAREST."
325 // Since all (GLES3) unsized color formats are filterable just like their
326 // sized equivalents, we don't have to care whether its sized or not.
327 if (!isFilterable) {
328 ret->incompleteReason =
329 "Minification or magnification filtering is not"
330 " NEAREST or NEAREST_MIPMAP_NEAREST, and the"
331 " texture's format is not \"texture-filterable\".";
332 return ret;
336 // Texture completeness is effectively (though not explicitly) amended for
337 // GLES2 by the "Texture Access" section under $3.8 "Fragment Shaders". This
338 // also applies to vertex shaders, as noted on GLES 2.0.25, p41.
339 if (!mContext->IsWebGL2() && !completeness->powerOfTwo) {
340 // GLES 2.0.25, p87-88:
341 // "Calling a sampler from a fragment shader will return (R,G,B,A)=(0,0,0,1)
342 // if
343 // any of the following conditions are true:"
345 // "* A two-dimensional sampler is called, the minification filter is one
346 // that requires a mipmap[...], and the sampler's associated texture
347 // object is not complete[.]"
348 // (already covered)
350 // "* A two-dimensional sampler is called, the minification filter is
351 // not one that requires a mipmap (either NEAREST nor[sic] LINEAR), and
352 // either dimension of the level zero array of the associated texture
353 // object is not positive."
354 // (already covered)
356 // "* A two-dimensional sampler is called, the corresponding texture
357 // image is a non-power-of-two image[...], and either the texture wrap
358 // mode is not CLAMP_TO_EDGE, or the minification filter is neither
359 // NEAREST nor LINEAR."
361 // "* A cube map sampler is called, any of the corresponding texture
362 // images are non-power-of-two images, and either the texture wrap mode
363 // is not CLAMP_TO_EDGE, or the minification filter is neither NEAREST
364 // nor LINEAR."
365 // "either the texture wrap mode is not CLAMP_TO_EDGE"
366 if (sampling->wrapS != LOCAL_GL_CLAMP_TO_EDGE ||
367 sampling->wrapT != LOCAL_GL_CLAMP_TO_EDGE) {
368 ret->incompleteReason =
369 "Non-power-of-two textures must have a wrap mode of"
370 " CLAMP_TO_EDGE.";
371 return ret;
374 // "* A cube map sampler is called, and either the corresponding cube
375 // map texture image is not cube complete, or TEXTURE_MIN_FILTER is one
376 // that requires a mipmap and the texture is not mipmap cube complete."
377 // (already covered)
380 // Mark complete.
381 ret->incompleteReason =
382 nullptr; // NB: incompleteReason is also null for undefined
383 ret->levels = completeness->levels; // textures.
384 if (!needsMips && ret->levels) {
385 ret->levels = 1;
387 ret->usage = completeness->usage;
388 return ret;
391 const webgl::SampleableInfo* WebGLTexture::GetSampleableInfo(
392 const WebGLSampler* const sampler) const {
393 auto itr = mSamplingCache.Find(sampler);
394 if (!itr) {
395 const auto info = CalcSampleableInfo(sampler);
396 if (!info) return nullptr;
398 auto entry = mSamplingCache.MakeEntry(sampler, info.value());
399 entry->AddInvalidator(*this);
400 if (sampler) {
401 entry->AddInvalidator(*sampler);
403 itr = mSamplingCache.Insert(std::move(entry));
405 return itr;
408 // ---------------------------
410 uint32_t WebGLTexture::Es3_q() const {
411 const auto& imageInfo = BaseImageInfo();
412 if (!imageInfo.IsDefined()) return mBaseMipmapLevel;
414 uint32_t largestDim = std::max(imageInfo.mWidth, imageInfo.mHeight);
415 if (mTarget == LOCAL_GL_TEXTURE_3D) {
416 largestDim = std::max(largestDim, imageInfo.mDepth);
418 if (!largestDim) return mBaseMipmapLevel;
420 // GLES 3.0.4, 3.8 - Mipmapping: `floor(log2(largest_of_dims)) + 1`
421 const auto numLevels = FloorLog2Size(largestDim) + 1;
423 const auto maxLevelBySize = mBaseMipmapLevel + numLevels - 1;
424 return std::min<uint32_t>(maxLevelBySize, mMaxMipmapLevel);
427 // -
429 static void SetSwizzle(gl::GLContext* gl, TexTarget target,
430 const GLint* swizzle) {
431 static const GLint kNoSwizzle[4] = {LOCAL_GL_RED, LOCAL_GL_GREEN,
432 LOCAL_GL_BLUE, LOCAL_GL_ALPHA};
433 if (!swizzle) {
434 swizzle = kNoSwizzle;
435 } else if (!gl->IsSupported(gl::GLFeature::texture_swizzle)) {
436 MOZ_CRASH("GFX: Needs swizzle feature to swizzle!");
439 gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_R, swizzle[0]);
440 gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_G, swizzle[1]);
441 gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_B, swizzle[2]);
442 gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_A, swizzle[3]);
445 void WebGLTexture::RefreshSwizzle() const {
446 const auto& imageInfo = BaseImageInfo();
447 const auto& swizzle = imageInfo.mFormat->textureSwizzleRGBA;
449 if (swizzle != mCurSwizzle) {
450 const gl::ScopedBindTexture scopeBindTexture(mContext->gl, mGLName,
451 mTarget.get());
452 SetSwizzle(mContext->gl, mTarget, swizzle);
453 mCurSwizzle = swizzle;
457 bool WebGLTexture::EnsureImageDataInitialized(const TexImageTarget target,
458 const uint32_t level) {
459 auto& imageInfo = ImageInfoAt(target, level);
460 if (!imageInfo.IsDefined()) return true;
462 if (!imageInfo.mUninitializedSlices) return true;
464 if (!ZeroTextureData(mContext, mGLName, target, level, imageInfo)) {
465 return false;
467 imageInfo.mUninitializedSlices = Nothing();
468 return true;
471 static bool ClearDepthTexture(const WebGLContext& webgl, const GLuint tex,
472 const TexImageTarget imageTarget,
473 const uint32_t level,
474 const webgl::ImageInfo& info) {
475 const auto& gl = webgl.gl;
476 const auto& usage = info.mFormat;
477 const auto& format = usage->format;
479 // Depth resources actually clear to 1.0f, not 0.0f!
480 // They are also always renderable.
481 MOZ_ASSERT(usage->IsRenderable());
482 MOZ_ASSERT(info.mUninitializedSlices);
484 GLenum attachPoint = LOCAL_GL_DEPTH_ATTACHMENT;
485 GLbitfield clearBits = LOCAL_GL_DEPTH_BUFFER_BIT;
487 if (format->s) {
488 attachPoint = LOCAL_GL_DEPTH_STENCIL_ATTACHMENT;
489 clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
492 // -
494 gl::ScopedFramebuffer scopedFB(gl);
495 const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB());
496 const webgl::ScopedPrepForResourceClear scopedPrep(webgl);
498 const auto fnAttach = [&](const uint32_t z) {
499 switch (imageTarget.get()) {
500 case LOCAL_GL_TEXTURE_3D:
501 case LOCAL_GL_TEXTURE_2D_ARRAY:
502 gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, attachPoint, tex,
503 level, z);
504 break;
505 default:
506 if (attachPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
507 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
508 LOCAL_GL_DEPTH_ATTACHMENT,
509 imageTarget.get(), tex, level);
510 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
511 LOCAL_GL_STENCIL_ATTACHMENT,
512 imageTarget.get(), tex, level);
513 } else {
514 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint,
515 imageTarget.get(), tex, level);
517 break;
521 for (const auto z : IntegerRange(info.mDepth)) {
522 if ((*info.mUninitializedSlices)[z]) {
523 fnAttach(z);
524 gl->fClear(clearBits);
527 const auto& status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
528 const bool isComplete = (status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
529 MOZ_ASSERT(isComplete);
530 return isComplete;
533 static bool ZeroTextureData(const WebGLContext* webgl, GLuint tex,
534 TexImageTarget target, uint32_t level,
535 const webgl::ImageInfo& info) {
536 // This has one usecase:
537 // Lazy zeroing of uninitialized textures:
538 // a. Before draw.
539 // b. Before partial upload. (TexStorage + TexSubImage)
541 // We have no sympathy for this case.
543 // "Doctor, it hurts when I do this!" "Well don't do that!"
544 MOZ_ASSERT(info.mUninitializedSlices);
546 const auto targetStr = EnumString(target.get());
547 webgl->GenerateWarning(
548 "Tex image %s level %u is incurring lazy initialization.",
549 targetStr.c_str(), level);
551 gl::GLContext* gl = webgl->GL();
552 const auto& width = info.mWidth;
553 const auto& height = info.mHeight;
554 const auto& depth = info.mDepth;
555 const auto& usage = info.mFormat;
557 GLenum scopeBindTarget;
558 switch (target.get()) {
559 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
560 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
561 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
562 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
563 case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
564 case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
565 scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP;
566 break;
567 default:
568 scopeBindTarget = target.get();
569 break;
571 const gl::ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget);
572 const auto& compression = usage->format->compression;
573 if (compression) {
574 auto sizedFormat = usage->format->sizedFormat;
575 MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set");
577 const auto fnSizeInBlocks = [](CheckedUint32 pixels,
578 uint8_t pixelsPerBlock) {
579 return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock;
582 const auto widthBlocks = fnSizeInBlocks(width, compression->blockWidth);
583 const auto heightBlocks = fnSizeInBlocks(height, compression->blockHeight);
585 CheckedUint32 checkedByteCount = compression->bytesPerBlock;
586 checkedByteCount *= widthBlocks;
587 checkedByteCount *= heightBlocks;
589 if (!checkedByteCount.isValid()) return false;
591 const size_t sliceByteCount = checkedByteCount.value();
593 UniqueBuffer zeros = calloc(1u, sliceByteCount);
594 if (!zeros) return false;
596 // Don't bother with striding it well.
597 // TODO: We shouldn't need to do this for CompressedTexSubImage.
598 WebGLPixelStore::AssertDefault(*gl, webgl->IsWebGL2());
599 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
600 const auto revert = MakeScopeExit(
601 [&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
603 GLenum error = 0;
604 for (const auto z : IntegerRange(depth)) {
605 if ((*info.mUninitializedSlices)[z]) {
606 error = DoCompressedTexSubImage(gl, target.get(), level, 0, 0, z, width,
607 height, 1, sizedFormat, sliceByteCount,
608 zeros.get());
609 if (error) break;
612 return !error;
615 const auto driverUnpackInfo = usage->idealUnpack;
616 MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set.");
618 if (usage->format->d) {
619 // ANGLE_depth_texture does not allow uploads, so we have to clear.
620 // (Restriction because of D3D9)
621 // Also, depth resources are cleared to 1.0f and are always renderable, so
622 // just use FB clears.
623 return ClearDepthTexture(*webgl, tex, target, level, info);
626 const webgl::PackingInfo packing = driverUnpackInfo->ToPacking();
628 const auto bytesPerPixel = webgl::BytesPerPixel(packing);
630 CheckedUint32 checkedByteCount = bytesPerPixel;
631 checkedByteCount *= width;
632 checkedByteCount *= height;
634 if (!checkedByteCount.isValid()) return false;
636 const size_t sliceByteCount = checkedByteCount.value();
638 UniqueBuffer zeros = calloc(1u, sliceByteCount);
639 if (!zeros) return false;
641 // Don't bother with striding it well.
642 WebGLPixelStore::AssertDefault(*gl, webgl->IsWebGL2());
643 gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
644 const auto revert =
645 MakeScopeExit([&]() { gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); });
647 GLenum error = 0;
648 for (const auto z : IntegerRange(depth)) {
649 if ((*info.mUninitializedSlices)[z]) {
650 error = DoTexSubImage(gl, target, level, 0, 0, z, width, height, 1,
651 packing, zeros.get());
652 if (error) break;
655 return !error;
658 template <typename T, typename R>
659 static R Clamp(const T val, const R min, const R max) {
660 if (val < min) return min;
661 if (val > max) return max;
662 return static_cast<R>(val);
665 template <typename T, typename A, typename B>
666 static void ClampSelf(T* const out, const A min, const B max) {
667 if (*out < min) {
668 *out = T{min};
669 } else if (*out > max) {
670 *out = T{max};
674 void WebGLTexture::ClampLevelBaseAndMax() {
675 if (!mImmutable) return;
677 // GLES 3.0.4, p158:
678 // "For immutable-format textures, `level_base` is clamped to the range
679 // `[0, levels-1]`, `level_max` is then clamped to the range `
680 // `[level_base, levels-1]`, where `levels` is the parameter passed to
681 // TexStorage* for the texture object."
682 ClampSelf(&mBaseMipmapLevel, 0u, mImmutableLevelCount - 1u);
683 ClampSelf(&mMaxMipmapLevel, mBaseMipmapLevel, mImmutableLevelCount - 1u);
685 // Note: This means that immutable textures are *always* texture-complete!
688 //////////////////////////////////////////////////////////////////////////////////////////
689 // GL calls
691 bool WebGLTexture::BindTexture(TexTarget texTarget) {
692 const bool isFirstBinding = !mTarget;
693 if (!isFirstBinding && mTarget != texTarget) {
694 mContext->ErrorInvalidOperation(
695 "bindTexture: This texture has already been bound"
696 " to a different target.");
697 return false;
700 mTarget = texTarget;
702 mContext->gl->fBindTexture(mTarget.get(), mGLName);
704 if (isFirstBinding) {
705 mFaceCount = IsCubeMap() ? 6 : 1;
707 gl::GLContext* gl = mContext->gl;
709 // Thanks to the WebKit people for finding this out: GL_TEXTURE_WRAP_R
710 // is not present in GLES 2, but is present in GL and it seems as if for
711 // cube maps we need to set it to GL_CLAMP_TO_EDGE to get the expected
712 // GLES behavior.
713 // If we are WebGL 2 though, we'll want to leave it as REPEAT.
714 const bool hasWrapR = gl->IsSupported(gl::GLFeature::texture_3D);
715 if (IsCubeMap() && hasWrapR && !mContext->IsWebGL2()) {
716 gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_WRAP_R,
717 LOCAL_GL_CLAMP_TO_EDGE);
721 return true;
724 void WebGLTexture::GenerateMipmap() {
725 // GLES 3.0.4 p160:
726 // "Mipmap generation replaces texel array levels level base + 1 through q
727 // with arrrays derived from the level base array, regardless of their
728 // previous contents. All other mipmap arrays, including the level base
729 // array, are left unchanged by this computation."
730 // But only check and init the base level.
731 const bool ensureInit = true;
732 const bool skipMips = true;
733 const auto completeness = CalcCompletenessInfo(ensureInit, skipMips);
734 if (!completeness || !completeness->levels) {
735 mContext->ErrorInvalidOperation(
736 "The texture's base level must be complete.");
737 return;
739 const auto& usage = completeness->usage;
740 const auto& format = usage->format;
741 if (!mContext->IsWebGL2()) {
742 if (!completeness->powerOfTwo) {
743 mContext->ErrorInvalidOperation(
744 "The base level of the texture does not"
745 " have power-of-two dimensions.");
746 return;
748 if (format->isSRGB) {
749 mContext->ErrorInvalidOperation(
750 "EXT_sRGB forbids GenerateMipmap with"
751 " sRGB.");
752 return;
756 if (format->compression) {
757 mContext->ErrorInvalidOperation(
758 "Texture data at base level is compressed.");
759 return;
762 if (format->d) {
763 mContext->ErrorInvalidOperation("Depth textures are not supported.");
764 return;
767 // OpenGL ES 3.0.4 p160:
768 // If the level base array was not specified with an unsized internal format
769 // from table 3.3 or a sized internal format that is both color-renderable and
770 // texture-filterable according to table 3.13, an INVALID_OPERATION error
771 // is generated.
772 bool canGenerateMipmap = (usage->IsRenderable() && usage->isFilterable);
773 switch (usage->format->effectiveFormat) {
774 case webgl::EffectiveFormat::Luminance8:
775 case webgl::EffectiveFormat::Alpha8:
776 case webgl::EffectiveFormat::Luminance8Alpha8:
777 // Non-color-renderable formats from Table 3.3.
778 canGenerateMipmap = true;
779 break;
780 default:
781 break;
784 if (!canGenerateMipmap) {
785 mContext->ErrorInvalidOperation(
786 "Texture at base level is not unsized"
787 " internal format or is not"
788 " color-renderable or texture-filterable.");
789 return;
792 if (usage->IsRenderable() && !usage->IsExplicitlyRenderable()) {
793 mContext->WarnIfImplicit(usage->GetExtensionID());
796 // Done with validation. Do the operation.
798 gl::GLContext* gl = mContext->gl;
800 if (gl->WorkAroundDriverBugs()) {
801 // bug 696495 - to work around failures in the texture-mips.html test on
802 // various drivers, we set the minification filter before calling
803 // glGenerateMipmap. This should not carry a significant performance
804 // overhead so we do it unconditionally.
806 // note that the choice of GL_NEAREST_MIPMAP_NEAREST really matters. See
807 // Chromium bug 101105.
808 gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
809 LOCAL_GL_NEAREST_MIPMAP_NEAREST);
810 gl->fGenerateMipmap(mTarget.get());
811 gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
812 mSamplingState.minFilter.get());
813 } else {
814 gl->fGenerateMipmap(mTarget.get());
817 // Record the results.
819 const auto maxLevel = Es3_q();
820 PopulateMipChain(maxLevel);
823 Maybe<double> WebGLTexture::GetTexParameter(GLenum pname) const {
824 GLint i = 0;
825 GLfloat f = 0.0f;
827 switch (pname) {
828 case LOCAL_GL_TEXTURE_BASE_LEVEL:
829 return Some(mBaseMipmapLevel);
831 case LOCAL_GL_TEXTURE_MAX_LEVEL:
832 return Some(mMaxMipmapLevel);
834 case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
835 return Some(mImmutable);
837 case LOCAL_GL_TEXTURE_IMMUTABLE_LEVELS:
838 return Some(uint32_t(mImmutableLevelCount));
840 case LOCAL_GL_TEXTURE_MIN_FILTER:
841 case LOCAL_GL_TEXTURE_MAG_FILTER:
842 case LOCAL_GL_TEXTURE_WRAP_S:
843 case LOCAL_GL_TEXTURE_WRAP_T:
844 case LOCAL_GL_TEXTURE_WRAP_R:
845 case LOCAL_GL_TEXTURE_COMPARE_MODE:
846 case LOCAL_GL_TEXTURE_COMPARE_FUNC: {
847 MOZ_ASSERT(mTarget);
848 const gl::ScopedBindTexture autoTex(mContext->gl, mGLName, mTarget.get());
849 mContext->gl->fGetTexParameteriv(mTarget.get(), pname, &i);
850 return Some(i);
853 case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
854 case LOCAL_GL_TEXTURE_MAX_LOD:
855 case LOCAL_GL_TEXTURE_MIN_LOD: {
856 MOZ_ASSERT(mTarget);
857 const gl::ScopedBindTexture autoTex(mContext->gl, mGLName, mTarget.get());
858 mContext->gl->fGetTexParameterfv(mTarget.get(), pname, &f);
859 return Some(f);
862 default:
863 MOZ_CRASH("GFX: Unhandled pname.");
867 // Here we have to support all pnames with both int and float params.
868 // See this discussion:
869 // https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html
870 void WebGLTexture::TexParameter(TexTarget texTarget, GLenum pname,
871 const FloatOrInt& param) {
872 bool isPNameValid = false;
873 switch (pname) {
874 // GLES 2.0.25 p76:
875 case LOCAL_GL_TEXTURE_WRAP_S:
876 case LOCAL_GL_TEXTURE_WRAP_T:
877 case LOCAL_GL_TEXTURE_MIN_FILTER:
878 case LOCAL_GL_TEXTURE_MAG_FILTER:
879 isPNameValid = true;
880 break;
882 // GLES 3.0.4 p149-150:
883 case LOCAL_GL_TEXTURE_BASE_LEVEL:
884 case LOCAL_GL_TEXTURE_COMPARE_MODE:
885 case LOCAL_GL_TEXTURE_COMPARE_FUNC:
886 case LOCAL_GL_TEXTURE_MAX_LEVEL:
887 case LOCAL_GL_TEXTURE_MAX_LOD:
888 case LOCAL_GL_TEXTURE_MIN_LOD:
889 case LOCAL_GL_TEXTURE_WRAP_R:
890 if (mContext->IsWebGL2()) isPNameValid = true;
891 break;
893 case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
894 if (mContext->IsExtensionEnabled(
895 WebGLExtensionID::EXT_texture_filter_anisotropic))
896 isPNameValid = true;
897 break;
900 if (!isPNameValid) {
901 mContext->ErrorInvalidEnumInfo("texParameter: pname", pname);
902 return;
905 ////////////////
906 // Validate params and invalidate if needed.
908 bool paramBadEnum = false;
909 bool paramBadValue = false;
911 switch (pname) {
912 case LOCAL_GL_TEXTURE_BASE_LEVEL:
913 case LOCAL_GL_TEXTURE_MAX_LEVEL:
914 paramBadValue = (param.i < 0);
915 break;
917 case LOCAL_GL_TEXTURE_COMPARE_MODE:
918 paramBadValue = (param.i != LOCAL_GL_NONE &&
919 param.i != LOCAL_GL_COMPARE_REF_TO_TEXTURE);
920 break;
922 case LOCAL_GL_TEXTURE_COMPARE_FUNC:
923 switch (param.i) {
924 case LOCAL_GL_LEQUAL:
925 case LOCAL_GL_GEQUAL:
926 case LOCAL_GL_LESS:
927 case LOCAL_GL_GREATER:
928 case LOCAL_GL_EQUAL:
929 case LOCAL_GL_NOTEQUAL:
930 case LOCAL_GL_ALWAYS:
931 case LOCAL_GL_NEVER:
932 break;
934 default:
935 paramBadValue = true;
936 break;
938 break;
940 case LOCAL_GL_TEXTURE_MIN_FILTER:
941 switch (param.i) {
942 case LOCAL_GL_NEAREST:
943 case LOCAL_GL_LINEAR:
944 case LOCAL_GL_NEAREST_MIPMAP_NEAREST:
945 case LOCAL_GL_LINEAR_MIPMAP_NEAREST:
946 case LOCAL_GL_NEAREST_MIPMAP_LINEAR:
947 case LOCAL_GL_LINEAR_MIPMAP_LINEAR:
948 break;
950 default:
951 paramBadEnum = true;
952 break;
954 break;
956 case LOCAL_GL_TEXTURE_MAG_FILTER:
957 switch (param.i) {
958 case LOCAL_GL_NEAREST:
959 case LOCAL_GL_LINEAR:
960 break;
962 default:
963 paramBadEnum = true;
964 break;
966 break;
968 case LOCAL_GL_TEXTURE_WRAP_S:
969 case LOCAL_GL_TEXTURE_WRAP_T:
970 case LOCAL_GL_TEXTURE_WRAP_R:
971 switch (param.i) {
972 case LOCAL_GL_CLAMP_TO_EDGE:
973 case LOCAL_GL_MIRRORED_REPEAT:
974 case LOCAL_GL_REPEAT:
975 break;
977 default:
978 paramBadEnum = true;
979 break;
981 break;
983 case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
984 if (param.f < 1.0f) paramBadValue = true;
986 break;
989 if (paramBadEnum) {
990 if (!param.isFloat) {
991 mContext->ErrorInvalidEnum(
992 "pname 0x%04x: Invalid param"
993 " 0x%04x.",
994 pname, param.i);
995 } else {
996 mContext->ErrorInvalidEnum("pname 0x%04x: Invalid param %g.", pname,
997 param.f);
999 return;
1002 if (paramBadValue) {
1003 if (!param.isFloat) {
1004 mContext->ErrorInvalidValue(
1005 "pname 0x%04x: Invalid param %i"
1006 " (0x%x).",
1007 pname, param.i, param.i);
1008 } else {
1009 mContext->ErrorInvalidValue("pname 0x%04x: Invalid param %g.", pname,
1010 param.f);
1012 return;
1015 ////////////////
1016 // Store any needed values
1018 FloatOrInt clamped = param;
1019 bool invalidate = true;
1020 switch (pname) {
1021 case LOCAL_GL_TEXTURE_BASE_LEVEL: {
1022 mBaseMipmapLevel = clamped.i;
1023 ClampLevelBaseAndMax();
1024 const auto forDriver =
1025 Clamp(mBaseMipmapLevel, uint8_t{0}, kMaxLevelCount);
1026 clamped = FloatOrInt(forDriver);
1027 break;
1030 case LOCAL_GL_TEXTURE_MAX_LEVEL: {
1031 mMaxMipmapLevel = clamped.i;
1032 ClampLevelBaseAndMax();
1033 const auto forDriver = Clamp(mMaxMipmapLevel, uint8_t{0}, kMaxLevelCount);
1034 clamped = FloatOrInt(forDriver);
1035 break;
1038 case LOCAL_GL_TEXTURE_MIN_FILTER:
1039 mSamplingState.minFilter = clamped.i;
1040 break;
1042 case LOCAL_GL_TEXTURE_MAG_FILTER:
1043 mSamplingState.magFilter = clamped.i;
1044 break;
1046 case LOCAL_GL_TEXTURE_WRAP_S:
1047 mSamplingState.wrapS = clamped.i;
1048 break;
1050 case LOCAL_GL_TEXTURE_WRAP_T:
1051 mSamplingState.wrapT = clamped.i;
1052 break;
1054 case LOCAL_GL_TEXTURE_COMPARE_MODE:
1055 mSamplingState.compareMode = clamped.i;
1056 break;
1058 default:
1059 invalidate = false; // Texture completeness will not change.
1060 break;
1063 if (invalidate) {
1064 InvalidateCaches();
1067 ////////////////
1069 if (!clamped.isFloat)
1070 mContext->gl->fTexParameteri(texTarget.get(), pname, clamped.i);
1071 else
1072 mContext->gl->fTexParameterf(texTarget.get(), pname, clamped.f);
1075 void WebGLTexture::Truncate() {
1076 for (auto& cur : mImageInfoArr) {
1077 cur = {};
1079 InvalidateCaches();
1082 } // namespace mozilla