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"
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"
28 MOZ_CONSTINIT
/*static*/ const ImageInfo
ImageInfo::kUndefined
;
30 size_t ImageInfo::MemoryUsage() const {
31 if (!IsDefined()) return 0;
33 size_t samples
= mSamples
;
38 const size_t bytesPerTexel
= mFormat
->format
->estimatedBytesPerPixel
;
39 return size_t(mWidth
) * size_t(mHeight
) * size_t(mDepth
) * samples
*
43 Maybe
<ImageInfo
> ImageInfo::NextMip(const GLenum target
) const {
44 MOZ_ASSERT(IsDefined());
48 if (target
== LOCAL_GL_TEXTURE_3D
) {
49 if (mWidth
<= 1 && mHeight
<= 1 && mDepth
<= 1) {
53 next
.mDepth
= std::max(uint32_t(1), next
.mDepth
/ 2);
55 // TEXTURE_2D_ARRAY may have depth != 1, but that's normal.
56 if (mWidth
<= 1 && mHeight
<= 1) {
60 if (next
.mUninitializedSlices
) {
61 next
.mUninitializedSlices
.emplace(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);
71 ////////////////////////////////////////
73 WebGLTexture::WebGLTexture(WebGLContext
* webgl
, GLuint tex
)
74 : WebGLContextBoundObject(webgl
),
76 mTarget(LOCAL_GL_NONE
),
79 mImmutableLevelCount(0),
81 mMaxMipmapLevel(1000) {}
83 WebGLTexture::~WebGLTexture() {
84 for (auto& cur
: mImageInfoArr
) {
85 cur
= webgl::ImageInfo();
89 if (!mContext
) return;
90 mContext
->gl
->fDeleteTextures(1, &mGLName
);
93 size_t WebGLTexture::MemoryUsage() const {
95 for (const auto& cur
: mImageInfoArr
) {
96 accum
+= cur
.MemoryUsage();
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
) {
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
);
120 const auto next
= ref
.NextMip(mTarget
.get());
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
) {
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
) {
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;
170 cur
.mUninitializedSlices
.reset();
174 const auto next
= ref
.NextMip(mTarget
.get());
182 Maybe
<const WebGLTexture::CompletenessInfo
> WebGLTexture::CalcCompletenessInfo(
183 const bool ensureInit
, const bool skipMips
) const {
184 Maybe
<CompletenessInfo
> ret
= Some(CompletenessInfo());
188 const auto level_base
= Es3_level_base();
189 if (level_base
> kMaxLevelCount
- 1) {
190 ret
->incompleteReason
= "`level_base` too high.";
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
203 ret
->incompleteReason
= nullptr;
207 if (!baseImageInfo
.mWidth
|| !baseImageInfo
.mHeight
||
208 !baseImageInfo
.mDepth
) {
209 ret
->incompleteReason
=
210 "The dimensions of `level_base` are not all positive.";
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\".";
224 ret
->usage
= baseImageInfo
.mFormat
;
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
);
235 if (!mContext
->IsWebGL2() && !ret
->powerOfTwo
) {
236 // WebGL 1 mipmaps require POT.
237 ret
->incompleteReason
= "Mipmapping requires power-of-two sizes.";
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`.";
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.";
258 ret
->levels
= AutoAssertCast(maxLevel_aka_q
- level_base
+ 1);
259 ret
->mipmapComplete
= true;
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
;
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
;
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
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
) {
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.
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\".";
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)
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[.]"
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."
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
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"
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."
388 ret
->incompleteReason
=
389 nullptr; // NB: incompleteReason is also null for undefined
390 ret
->levels
= completeness
->levels
; // textures.
391 if (!needsMips
&& ret
->levels
) {
394 ret
->usage
= completeness
->usage
;
398 const webgl::SampleableInfo
* WebGLTexture::GetSampleableInfo(
399 const WebGLSampler
* const sampler
) const {
400 auto itr
= mSamplingCache
.Find(sampler
);
402 const auto info
= CalcSampleableInfo(sampler
);
403 if (!info
) return nullptr;
405 auto entry
= mSamplingCache
.MakeEntry(sampler
, info
.value());
406 entry
->AddInvalidator(*this);
408 entry
->AddInvalidator(*sampler
);
410 itr
= mSamplingCache
.Insert(std::move(entry
));
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
);
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
};
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
,
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
)) {
474 imageInfo
.mUninitializedSlices
.reset();
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
;
495 attachPoint
= LOCAL_GL_DEPTH_STENCIL_ATTACHMENT
;
496 clearBits
|= LOCAL_GL_STENCIL_BUFFER_BIT
;
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
,
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
);
521 gl
->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER
, attachPoint
,
522 imageTarget
.get(), tex
, level
);
528 for (const auto z
: IntegerRange(info
.mDepth
)) {
529 if ((*info
.mUninitializedSlices
)[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
);
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:
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
;
575 scopeBindTarget
= target
.get();
578 const gl::ScopedBindTexture
scopeBindTexture(gl
, tex
, scopeBindTarget
);
579 const auto& compression
= usage
->format
->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); });
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
,
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);
652 MakeScopeExit([&]() { gl
->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT
, 4); });
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());
665 void WebGLTexture::ClampLevelBaseAndMax() {
666 if (!mImmutable
) return;
669 // "For immutable-format textures, `level_base` is clamped to the range
670 // `[0, levels-1]`, `level_max` is then clamped to the range `
671 // `[level_base, levels-1]`, where `levels` is the parameter passed to
672 // TexStorage* for the texture object."
673 MOZ_ASSERT(mImmutableLevelCount
> 0);
674 const auto oldBase
= mBaseMipmapLevel
;
675 const auto oldMax
= mMaxMipmapLevel
;
677 std::clamp(mBaseMipmapLevel
, 0u, mImmutableLevelCount
- 1u);
679 std::clamp(mMaxMipmapLevel
, mBaseMipmapLevel
, mImmutableLevelCount
- 1u);
680 if (oldBase
!= mBaseMipmapLevel
&&
681 mBaseMipmapLevelState
!= MIPMAP_LEVEL_DEFAULT
) {
682 mBaseMipmapLevelState
= MIPMAP_LEVEL_DIRTY
;
684 if (oldMax
!= mMaxMipmapLevel
&&
685 mMaxMipmapLevelState
!= MIPMAP_LEVEL_DEFAULT
) {
686 mMaxMipmapLevelState
= MIPMAP_LEVEL_DIRTY
;
689 // Note: This means that immutable textures are *always* texture-complete!
692 //////////////////////////////////////////////////////////////////////////////////////////
695 bool WebGLTexture::BindTexture(TexTarget texTarget
) {
696 const bool isFirstBinding
= !mTarget
;
697 if (!isFirstBinding
&& mTarget
!= texTarget
) {
698 mContext
->ErrorInvalidOperation(
699 "bindTexture: This texture has already been bound"
700 " to a different target.");
706 mContext
->gl
->fBindTexture(mTarget
.get(), mGLName
);
708 if (isFirstBinding
) {
709 mFaceCount
= IsCubeMap() ? 6 : 1;
711 gl::GLContext
* gl
= mContext
->gl
;
713 // Thanks to the WebKit people for finding this out: GL_TEXTURE_WRAP_R
714 // is not present in GLES 2, but is present in GL and it seems as if for
715 // cube maps we need to set it to GL_CLAMP_TO_EDGE to get the expected
717 // If we are WebGL 2 though, we'll want to leave it as REPEAT.
718 const bool hasWrapR
= gl
->IsSupported(gl::GLFeature::texture_3D
);
719 if (IsCubeMap() && hasWrapR
&& !mContext
->IsWebGL2()) {
720 gl
->fTexParameteri(texTarget
.get(), LOCAL_GL_TEXTURE_WRAP_R
,
721 LOCAL_GL_CLAMP_TO_EDGE
);
728 static constexpr GLint
ClampMipmapLevelForDriver(uint32_t level
) {
729 return static_cast<GLint
>(
730 std::clamp(level
, 0u, (uint32_t)WebGLTexture::kMaxLevelCount
));
733 void WebGLTexture::GenerateMipmap() {
735 // "Mipmap generation replaces texel array levels level base + 1 through q
736 // with arrrays derived from the level base array, regardless of their
737 // previous contents. All other mipmap arrays, including the level base
738 // array, are left unchanged by this computation."
739 // But only check and init the base level.
740 const bool ensureInit
= true;
741 const bool skipMips
= true;
742 const auto completeness
= CalcCompletenessInfo(ensureInit
, skipMips
);
743 if (!completeness
|| !completeness
->levels
) {
744 mContext
->ErrorInvalidOperation(
745 "The texture's base level must be complete.");
748 const auto& usage
= completeness
->usage
;
749 const auto& format
= usage
->format
;
750 if (!mContext
->IsWebGL2()) {
751 if (!completeness
->powerOfTwo
) {
752 mContext
->ErrorInvalidOperation(
753 "The base level of the texture does not"
754 " have power-of-two dimensions.");
757 if (format
->isSRGB
) {
758 mContext
->ErrorInvalidOperation(
759 "EXT_sRGB forbids GenerateMipmap with"
765 if (format
->compression
) {
766 mContext
->ErrorInvalidOperation(
767 "Texture data at base level is compressed.");
772 mContext
->ErrorInvalidOperation("Depth textures are not supported.");
776 // OpenGL ES 3.0.4 p160:
777 // If the level base array was not specified with an unsized internal format
778 // from table 3.3 or a sized internal format that is both color-renderable and
779 // texture-filterable according to table 3.13, an INVALID_OPERATION error
781 bool canGenerateMipmap
= (usage
->IsRenderable() && usage
->isFilterable
);
782 switch (usage
->format
->effectiveFormat
) {
783 case webgl::EffectiveFormat::Luminance8
:
784 case webgl::EffectiveFormat::Alpha8
:
785 case webgl::EffectiveFormat::Luminance8Alpha8
:
786 // Non-color-renderable formats from Table 3.3.
787 canGenerateMipmap
= true;
793 if (!canGenerateMipmap
) {
794 mContext
->ErrorInvalidOperation(
795 "Texture at base level is not unsized"
796 " internal format or is not"
797 " color-renderable or texture-filterable.");
801 if (usage
->IsRenderable() && !usage
->IsExplicitlyRenderable()) {
802 mContext
->WarnIfImplicit(usage
->GetExtensionID());
805 // Done with validation. Do the operation.
807 gl::GLContext
* gl
= mContext
->gl
;
809 if (gl
->WorkAroundDriverBugs()) {
810 // If we first set GL_TEXTURE_BASE_LEVEL to a number such as 20, then set
811 // MGL_TEXTURE_MAX_LEVEL to a smaller number like 8, our copy of the
812 // base level will be lowered, but we havn't yet updated the driver, we
813 // should do so now, before calling glGenerateMipmap().
814 if (mBaseMipmapLevelState
== MIPMAP_LEVEL_DIRTY
) {
815 gl
->fTexParameteri(mTarget
.get(), LOCAL_GL_TEXTURE_BASE_LEVEL
,
816 ClampMipmapLevelForDriver(mBaseMipmapLevel
));
817 mBaseMipmapLevelState
= MIPMAP_LEVEL_CLEAN
;
819 if (mMaxMipmapLevelState
== MIPMAP_LEVEL_DIRTY
) {
820 gl
->fTexParameteri(mTarget
.get(), LOCAL_GL_TEXTURE_MAX_LEVEL
,
821 ClampMipmapLevelForDriver(mMaxMipmapLevel
));
822 mMaxMipmapLevelState
= MIPMAP_LEVEL_CLEAN
;
825 // bug 696495 - to work around failures in the texture-mips.html test on
826 // various drivers, we set the minification filter before calling
827 // glGenerateMipmap. This should not carry a significant performance
828 // overhead so we do it unconditionally.
830 // note that the choice of GL_NEAREST_MIPMAP_NEAREST really matters. See
831 // Chromium bug 101105.
832 gl
->fTexParameteri(mTarget
.get(), LOCAL_GL_TEXTURE_MIN_FILTER
,
833 LOCAL_GL_NEAREST_MIPMAP_NEAREST
);
834 gl
->fGenerateMipmap(mTarget
.get());
835 gl
->fTexParameteri(mTarget
.get(), LOCAL_GL_TEXTURE_MIN_FILTER
,
836 mSamplingState
.minFilter
.get());
838 gl
->fGenerateMipmap(mTarget
.get());
841 // Record the results.
843 const auto maxLevel
= Es3_q();
844 PopulateMipChain(maxLevel
);
847 Maybe
<double> WebGLTexture::GetTexParameter(GLenum pname
) const {
852 case LOCAL_GL_TEXTURE_BASE_LEVEL
:
853 return Some(mBaseMipmapLevel
);
855 case LOCAL_GL_TEXTURE_MAX_LEVEL
:
856 return Some(mMaxMipmapLevel
);
858 case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT
:
859 return Some(mImmutable
);
861 case LOCAL_GL_TEXTURE_IMMUTABLE_LEVELS
:
862 return Some(uint32_t(mImmutableLevelCount
));
864 case LOCAL_GL_TEXTURE_MIN_FILTER
:
865 case LOCAL_GL_TEXTURE_MAG_FILTER
:
866 case LOCAL_GL_TEXTURE_WRAP_S
:
867 case LOCAL_GL_TEXTURE_WRAP_T
:
868 case LOCAL_GL_TEXTURE_WRAP_R
:
869 case LOCAL_GL_TEXTURE_COMPARE_MODE
:
870 case LOCAL_GL_TEXTURE_COMPARE_FUNC
: {
872 const gl::ScopedBindTexture
autoTex(mContext
->gl
, mGLName
, mTarget
.get());
873 mContext
->gl
->fGetTexParameteriv(mTarget
.get(), pname
, &i
);
877 case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT
:
878 case LOCAL_GL_TEXTURE_MAX_LOD
:
879 case LOCAL_GL_TEXTURE_MIN_LOD
: {
881 const gl::ScopedBindTexture
autoTex(mContext
->gl
, mGLName
, mTarget
.get());
882 mContext
->gl
->fGetTexParameterfv(mTarget
.get(), pname
, &f
);
887 MOZ_CRASH("GFX: Unhandled pname.");
891 // Here we have to support all pnames with both int and float params.
892 // See this discussion:
893 // https://www.khronos.org/webgl/public-mailing-list/archives/1008/msg00014.html
894 void WebGLTexture::TexParameter(TexTarget texTarget
, GLenum pname
,
895 const FloatOrInt
& param
) {
896 bool isPNameValid
= false;
899 case LOCAL_GL_TEXTURE_WRAP_S
:
900 case LOCAL_GL_TEXTURE_WRAP_T
:
901 case LOCAL_GL_TEXTURE_MIN_FILTER
:
902 case LOCAL_GL_TEXTURE_MAG_FILTER
:
906 // GLES 3.0.4 p149-150:
907 case LOCAL_GL_TEXTURE_BASE_LEVEL
:
908 case LOCAL_GL_TEXTURE_COMPARE_MODE
:
909 case LOCAL_GL_TEXTURE_COMPARE_FUNC
:
910 case LOCAL_GL_TEXTURE_MAX_LEVEL
:
911 case LOCAL_GL_TEXTURE_MAX_LOD
:
912 case LOCAL_GL_TEXTURE_MIN_LOD
:
913 case LOCAL_GL_TEXTURE_WRAP_R
:
914 if (mContext
->IsWebGL2()) isPNameValid
= true;
917 case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT
:
918 if (mContext
->IsExtensionEnabled(
919 WebGLExtensionID::EXT_texture_filter_anisotropic
))
925 mContext
->ErrorInvalidEnumInfo("texParameter: pname", pname
);
930 // Validate params and invalidate if needed.
932 bool paramBadEnum
= false;
933 bool paramBadValue
= false;
936 case LOCAL_GL_TEXTURE_BASE_LEVEL
:
937 case LOCAL_GL_TEXTURE_MAX_LEVEL
:
938 paramBadValue
= (param
.i
< 0);
941 case LOCAL_GL_TEXTURE_COMPARE_MODE
:
942 paramBadValue
= (param
.i
!= LOCAL_GL_NONE
&&
943 param
.i
!= LOCAL_GL_COMPARE_REF_TO_TEXTURE
);
946 case LOCAL_GL_TEXTURE_COMPARE_FUNC
:
948 case LOCAL_GL_LEQUAL
:
949 case LOCAL_GL_GEQUAL
:
951 case LOCAL_GL_GREATER
:
953 case LOCAL_GL_NOTEQUAL
:
954 case LOCAL_GL_ALWAYS
:
959 paramBadValue
= true;
964 case LOCAL_GL_TEXTURE_MIN_FILTER
:
966 case LOCAL_GL_NEAREST
:
967 case LOCAL_GL_LINEAR
:
968 case LOCAL_GL_NEAREST_MIPMAP_NEAREST
:
969 case LOCAL_GL_LINEAR_MIPMAP_NEAREST
:
970 case LOCAL_GL_NEAREST_MIPMAP_LINEAR
:
971 case LOCAL_GL_LINEAR_MIPMAP_LINEAR
:
980 case LOCAL_GL_TEXTURE_MAG_FILTER
:
982 case LOCAL_GL_NEAREST
:
983 case LOCAL_GL_LINEAR
:
992 case LOCAL_GL_TEXTURE_WRAP_S
:
993 case LOCAL_GL_TEXTURE_WRAP_T
:
994 case LOCAL_GL_TEXTURE_WRAP_R
:
996 case LOCAL_GL_CLAMP_TO_EDGE
:
997 case LOCAL_GL_MIRRORED_REPEAT
:
998 case LOCAL_GL_REPEAT
:
1002 paramBadEnum
= true;
1007 case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT
:
1008 if (param
.f
< 1.0f
) paramBadValue
= true;
1014 if (!param
.isFloat
) {
1015 mContext
->ErrorInvalidEnum(
1016 "pname 0x%04x: Invalid param"
1020 mContext
->ErrorInvalidEnum("pname 0x%04x: Invalid param %g.", pname
,
1026 if (paramBadValue
) {
1027 if (!param
.isFloat
) {
1028 mContext
->ErrorInvalidValue(
1029 "pname 0x%04x: Invalid param %i"
1031 pname
, param
.i
, param
.i
);
1033 mContext
->ErrorInvalidValue("pname 0x%04x: Invalid param %g.", pname
,
1040 // Store any needed values
1042 FloatOrInt clamped
= param
;
1043 bool invalidate
= true;
1045 case LOCAL_GL_TEXTURE_BASE_LEVEL
: {
1046 mBaseMipmapLevel
= clamped
.i
;
1047 mBaseMipmapLevelState
= MIPMAP_LEVEL_CLEAN
;
1048 ClampLevelBaseAndMax();
1049 clamped
= FloatOrInt(ClampMipmapLevelForDriver(mBaseMipmapLevel
));
1053 case LOCAL_GL_TEXTURE_MAX_LEVEL
: {
1054 mMaxMipmapLevel
= clamped
.i
;
1055 mMaxMipmapLevelState
= MIPMAP_LEVEL_CLEAN
;
1056 ClampLevelBaseAndMax();
1057 clamped
= FloatOrInt(ClampMipmapLevelForDriver(mMaxMipmapLevel
));
1061 case LOCAL_GL_TEXTURE_MIN_FILTER
:
1062 mSamplingState
.minFilter
= clamped
.i
;
1065 case LOCAL_GL_TEXTURE_MAG_FILTER
:
1066 mSamplingState
.magFilter
= clamped
.i
;
1069 case LOCAL_GL_TEXTURE_WRAP_S
:
1070 mSamplingState
.wrapS
= clamped
.i
;
1073 case LOCAL_GL_TEXTURE_WRAP_T
:
1074 mSamplingState
.wrapT
= clamped
.i
;
1077 case LOCAL_GL_TEXTURE_COMPARE_MODE
:
1078 mSamplingState
.compareMode
= clamped
.i
;
1082 invalidate
= false; // Texture completeness will not change.
1092 if (!clamped
.isFloat
) {
1093 mContext
->gl
->fTexParameteri(texTarget
.get(), pname
, clamped
.i
);
1095 mContext
->gl
->fTexParameterf(texTarget
.get(), pname
, clamped
.f
);
1099 void WebGLTexture::Truncate() {
1100 for (auto& cur
: mImageInfoArr
) {
1106 } // namespace mozilla