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