WebGLFramebuffer.cpp (55068B)
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 "WebGLFramebuffer.h" 7 8 // You know it's going to be fun when these two show up: 9 #include <algorithm> 10 11 #include "GLBlitHelper.h" 12 #include "GLContext.h" 13 #include "GLScreenBuffer.h" 14 #include "MozFramebuffer.h" 15 #include "WebGLContext.h" 16 #include "WebGLContextUtils.h" 17 #include "WebGLExtensions.h" 18 #include "WebGLFormats.h" 19 #include "WebGLRenderbuffer.h" 20 #include "WebGLTexture.h" 21 #include "mozilla/IntegerRange.h" 22 #include "mozilla/dom/WebGLRenderingContextBinding.h" 23 #include "nsPrintfCString.h" 24 25 namespace mozilla { 26 27 static bool ShouldDeferAttachment(const WebGLContext* const webgl, 28 const GLenum attachPoint) { 29 if (webgl->IsWebGL2()) return false; 30 31 switch (attachPoint) { 32 case LOCAL_GL_DEPTH_ATTACHMENT: 33 case LOCAL_GL_STENCIL_ATTACHMENT: 34 case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT: 35 return true; 36 default: 37 return false; 38 } 39 } 40 41 WebGLFBAttachPoint::WebGLFBAttachPoint() = default; 42 WebGLFBAttachPoint::WebGLFBAttachPoint(WebGLFBAttachPoint&) = default; 43 44 WebGLFBAttachPoint::WebGLFBAttachPoint(const WebGLContext* const webgl, 45 const GLenum attachmentPoint) 46 : mAttachmentPoint(attachmentPoint), 47 mDeferAttachment(ShouldDeferAttachment(webgl, mAttachmentPoint)) {} 48 49 WebGLFBAttachPoint::~WebGLFBAttachPoint() { 50 MOZ_ASSERT(!mRenderbufferPtr); 51 MOZ_ASSERT(!mTexturePtr); 52 } 53 54 void WebGLFBAttachPoint::Clear() { Set(nullptr, {}); } 55 56 void WebGLFBAttachPoint::Set(gl::GLContext* const gl, 57 const webgl::FbAttachInfo& toAttach) { 58 mRenderbufferPtr = toAttach.rb; 59 mTexturePtr = toAttach.tex; 60 mTexImageLayer = AssertedCast<uint32_t>(toAttach.zLayer); 61 mTexImageZLayerCount = AssertedCast<uint8_t>(toAttach.zLayerCount); 62 mTexImageLevel = AssertedCast<uint8_t>(toAttach.mipLevel); 63 mIsMultiview = toAttach.isMultiview; 64 65 if (gl && !mDeferAttachment) { 66 DoAttachment(gl); 67 } 68 } 69 70 const webgl::ImageInfo* WebGLFBAttachPoint::GetImageInfo() const { 71 if (mTexturePtr) { 72 const auto target = Texture()->Target(); 73 uint8_t face = 0; 74 if (target == LOCAL_GL_TEXTURE_CUBE_MAP) { 75 face = Layer() % 6; 76 } 77 return &mTexturePtr->ImageInfoAtFace(face, mTexImageLevel); 78 } 79 if (mRenderbufferPtr) return &mRenderbufferPtr->ImageInfo(); 80 return nullptr; 81 } 82 83 bool WebGLFBAttachPoint::IsComplete(WebGLContext* webgl, 84 nsCString* const out_info) const { 85 MOZ_ASSERT(HasAttachment()); 86 87 const auto fnWriteErrorInfo = [&](const char* const text) { 88 WebGLContext::EnumName(mAttachmentPoint, out_info); 89 out_info->AppendLiteral(": "); 90 out_info->AppendASCII(text); 91 }; 92 93 const auto& imageInfo = *GetImageInfo(); 94 if (!imageInfo.mWidth || !imageInfo.mHeight) { 95 fnWriteErrorInfo("Attachment has no width or height."); 96 return false; 97 } 98 MOZ_ASSERT(imageInfo.IsDefined()); 99 100 const auto& tex = Texture(); 101 if (tex) { 102 // ES 3.0 spec, pg 213 has giant blocks of text that bake down to requiring 103 // that attached *non-immutable* tex images are within the valid mip-levels 104 // of the texture. We still need to check immutable textures though, because 105 // checking completeness is also when we zero invalidated/no-data tex 106 // images. 107 const auto attachedMipLevel = MipLevel(); 108 109 const bool withinValidMipLevels = [&]() { 110 const bool ensureInit = false; 111 const auto texCompleteness = tex->CalcCompletenessInfo(ensureInit); 112 if (!texCompleteness) return false; // OOM 113 114 if (tex->Immutable()) { 115 // Immutable textures can attach a level that's not valid for sampling. 116 // It still has to exist though! 117 return attachedMipLevel < tex->ImmutableLevelCount(); 118 } 119 120 // Base level must be complete. 121 if (!texCompleteness->levels) return false; 122 123 const auto baseLevel = tex->Es3_level_base(); 124 if (attachedMipLevel == baseLevel) return true; 125 126 // If not base level, must be mip-complete and within mips. 127 if (!texCompleteness->mipmapComplete) return false; 128 const auto maxLevel = baseLevel + texCompleteness->levels - 1; 129 return baseLevel <= attachedMipLevel && attachedMipLevel <= maxLevel; 130 }(); 131 if (!withinValidMipLevels) { 132 fnWriteErrorInfo("Attached mip level is invalid for texture."); 133 return false; 134 } 135 136 const auto& levelInfo = tex->ImageInfoAtFace(0, attachedMipLevel); 137 const auto faceDepth = levelInfo.mDepth * tex->FaceCount(); 138 const bool withinValidZLayers = Layer() + ZLayerCount() - 1 < faceDepth; 139 if (!withinValidZLayers) { 140 fnWriteErrorInfo("Attached z layer is invalid for texture."); 141 return false; 142 } 143 } 144 145 const auto& formatUsage = imageInfo.mFormat; 146 if (!formatUsage->IsRenderable()) { 147 const auto info = nsPrintfCString( 148 "Attachment has an effective format of %s," 149 " which is not renderable.", 150 formatUsage->format->name); 151 fnWriteErrorInfo(info.BeginReading()); 152 return false; 153 } 154 if (!formatUsage->IsExplicitlyRenderable()) { 155 webgl->WarnIfImplicit(formatUsage->GetExtensionID()); 156 } 157 158 const auto format = formatUsage->format; 159 160 bool hasRequiredBits; 161 162 switch (mAttachmentPoint) { 163 case LOCAL_GL_DEPTH_ATTACHMENT: 164 hasRequiredBits = format->d; 165 break; 166 167 case LOCAL_GL_STENCIL_ATTACHMENT: 168 hasRequiredBits = format->s; 169 break; 170 171 case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT: 172 MOZ_ASSERT(!webgl->IsWebGL2()); 173 hasRequiredBits = (format->d && format->s); 174 break; 175 176 default: 177 MOZ_ASSERT(mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0); 178 hasRequiredBits = format->IsColorFormat(); 179 break; 180 } 181 182 if (!hasRequiredBits) { 183 fnWriteErrorInfo( 184 "Attachment's format is missing required color/depth/stencil" 185 " bits."); 186 return false; 187 } 188 189 if (!webgl->IsWebGL2()) { 190 bool hasSurplusPlanes = false; 191 192 switch (mAttachmentPoint) { 193 case LOCAL_GL_DEPTH_ATTACHMENT: 194 hasSurplusPlanes = format->s; 195 break; 196 197 case LOCAL_GL_STENCIL_ATTACHMENT: 198 hasSurplusPlanes = format->d; 199 break; 200 } 201 202 if (hasSurplusPlanes) { 203 fnWriteErrorInfo( 204 "Attachment has depth or stencil bits when it shouldn't."); 205 return false; 206 } 207 } 208 209 return true; 210 } 211 212 void WebGLFBAttachPoint::DoAttachment(gl::GLContext* const gl) const { 213 if (Renderbuffer()) { 214 Renderbuffer()->DoFramebufferRenderbuffer(mAttachmentPoint); 215 return; 216 } 217 218 if (!Texture()) { 219 MOZ_ASSERT(mAttachmentPoint != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT); 220 // WebGL 2 doesn't have a real attachment for this, and WebGL 1 is defered 221 // and only DoAttachment if HasAttachment. 222 223 gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint, 224 LOCAL_GL_RENDERBUFFER, 0); 225 return; 226 } 227 228 const auto& texName = Texture()->mGLName; 229 230 switch (Texture()->Target().get()) { 231 case LOCAL_GL_TEXTURE_2D: 232 case LOCAL_GL_TEXTURE_CUBE_MAP: { 233 TexImageTarget imageTarget = LOCAL_GL_TEXTURE_2D; 234 if (Texture()->Target() == LOCAL_GL_TEXTURE_CUBE_MAP) { 235 imageTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + Layer(); 236 } 237 238 if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { 239 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, 240 LOCAL_GL_DEPTH_ATTACHMENT, imageTarget.get(), 241 texName, MipLevel()); 242 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, 243 LOCAL_GL_STENCIL_ATTACHMENT, 244 imageTarget.get(), texName, MipLevel()); 245 } else { 246 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint, 247 imageTarget.get(), texName, MipLevel()); 248 } 249 break; 250 } 251 252 case LOCAL_GL_TEXTURE_2D_ARRAY: 253 case LOCAL_GL_TEXTURE_3D: 254 if (ZLayerCount() != 1) { 255 gl->fFramebufferTextureMultiview(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint, 256 texName, MipLevel(), Layer(), 257 ZLayerCount()); 258 } else { 259 gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint, 260 texName, MipLevel(), Layer()); 261 } 262 break; 263 } 264 } 265 266 Maybe<double> WebGLFBAttachPoint::GetParameter(WebGLContext* webgl, 267 GLenum attachment, 268 GLenum pname) const { 269 if (!HasAttachment()) { 270 // Divergent between GLES 3 and 2. 271 272 // GLES 2.0.25 p127: 273 // "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is NONE, then 274 // querying any other pname will generate INVALID_ENUM." 275 276 // GLES 3.0.4 p240: 277 // "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is NONE, no 278 // framebuffer is bound to target. In this case querying pname 279 // FRAMEBUFFER_ATTACHMENT_OBJECT_NAME will return zero, and all other 280 // queries will generate an INVALID_OPERATION error." 281 switch (pname) { 282 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE: 283 return Some(LOCAL_GL_NONE); 284 285 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME: 286 if (webgl->IsWebGL2()) return Nothing(); 287 288 break; 289 290 default: 291 break; 292 } 293 nsCString attachmentName; 294 WebGLContext::EnumName(attachment, &attachmentName); 295 if (webgl->IsWebGL2()) { 296 webgl->ErrorInvalidOperation("No attachment at %s.", 297 attachmentName.BeginReading()); 298 } else { 299 webgl->ErrorInvalidEnum("No attachment at %s.", 300 attachmentName.BeginReading()); 301 } 302 return Nothing(); 303 } 304 305 bool isPNameValid = false; 306 switch (pname) { 307 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE: 308 return Some(mTexturePtr ? LOCAL_GL_TEXTURE : LOCAL_GL_RENDERBUFFER); 309 310 ////// 311 312 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL: 313 if (mTexturePtr) return Some(AssertedCast<uint32_t>(MipLevel())); 314 break; 315 316 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE: 317 if (mTexturePtr) { 318 GLenum face = 0; 319 if (mTexturePtr->Target() == LOCAL_GL_TEXTURE_CUBE_MAP) { 320 face = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + Layer(); 321 } 322 return Some(face); 323 } 324 break; 325 326 ////// 327 328 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER: 329 if (webgl->IsWebGL2()) { 330 return Some(AssertedCast<int32_t>(Layer())); 331 } 332 break; 333 334 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR: 335 if (webgl->IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) { 336 return Some(AssertedCast<int32_t>(Layer())); 337 } 338 break; 339 340 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR: 341 if (webgl->IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) { 342 return Some(AssertedCast<uint32_t>(ZLayerCount())); 343 } 344 break; 345 346 ////// 347 348 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE: 349 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE: 350 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE: 351 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE: 352 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE: 353 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE: 354 isPNameValid = webgl->IsWebGL2(); 355 break; 356 357 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE: 358 isPNameValid = (webgl->IsWebGL2() || 359 webgl->IsExtensionEnabled( 360 WebGLExtensionID::WEBGL_color_buffer_float) || 361 webgl->IsExtensionEnabled( 362 WebGLExtensionID::EXT_color_buffer_half_float)); 363 break; 364 365 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING: 366 isPNameValid = (webgl->IsWebGL2() || 367 webgl->IsExtensionEnabled(WebGLExtensionID::EXT_sRGB)); 368 break; 369 } 370 371 if (!isPNameValid) { 372 webgl->ErrorInvalidEnum("Invalid pname: 0x%04x", pname); 373 return Nothing(); 374 } 375 376 const auto& imageInfo = *GetImageInfo(); 377 const auto& usage = imageInfo.mFormat; 378 if (!usage) { 379 if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING) 380 return Some(LOCAL_GL_LINEAR); 381 382 return Nothing(); 383 } 384 385 auto format = usage->format; 386 387 GLint ret = 0; 388 switch (pname) { 389 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE: 390 ret = format->r; 391 break; 392 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE: 393 ret = format->g; 394 break; 395 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE: 396 ret = format->b; 397 break; 398 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE: 399 ret = format->a; 400 break; 401 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE: 402 ret = format->d; 403 break; 404 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE: 405 ret = format->s; 406 break; 407 408 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING: 409 ret = (format->isSRGB ? LOCAL_GL_SRGB : LOCAL_GL_LINEAR); 410 break; 411 412 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE: 413 if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { 414 webgl->ErrorInvalidOperation( 415 "Querying" 416 " FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE" 417 " against DEPTH_STENCIL_ATTACHMENT is an" 418 " error."); 419 return Nothing(); 420 } 421 422 if (format->unsizedFormat == webgl::UnsizedFormat::DEPTH_STENCIL) { 423 MOZ_ASSERT(attachment == LOCAL_GL_DEPTH_ATTACHMENT || 424 attachment == LOCAL_GL_STENCIL_ATTACHMENT); 425 426 if (attachment == LOCAL_GL_DEPTH_ATTACHMENT) { 427 switch (format->effectiveFormat) { 428 case webgl::EffectiveFormat::DEPTH24_STENCIL8: 429 format = 430 webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT24); 431 break; 432 case webgl::EffectiveFormat::DEPTH32F_STENCIL8: 433 format = 434 webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT32F); 435 break; 436 default: 437 MOZ_ASSERT(false, "no matched DS format"); 438 break; 439 } 440 } else if (attachment == LOCAL_GL_STENCIL_ATTACHMENT) { 441 switch (format->effectiveFormat) { 442 case webgl::EffectiveFormat::DEPTH24_STENCIL8: 443 case webgl::EffectiveFormat::DEPTH32F_STENCIL8: 444 format = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8); 445 break; 446 default: 447 MOZ_ASSERT(false, "no matched DS format"); 448 break; 449 } 450 } 451 } 452 453 switch (format->componentType) { 454 case webgl::ComponentType::Int: 455 ret = LOCAL_GL_INT; 456 break; 457 case webgl::ComponentType::UInt: 458 ret = LOCAL_GL_UNSIGNED_INT; 459 break; 460 case webgl::ComponentType::NormInt: 461 ret = LOCAL_GL_SIGNED_NORMALIZED; 462 break; 463 case webgl::ComponentType::NormUInt: 464 ret = LOCAL_GL_UNSIGNED_NORMALIZED; 465 break; 466 case webgl::ComponentType::Float: 467 ret = LOCAL_GL_FLOAT; 468 break; 469 } 470 break; 471 472 default: 473 MOZ_ASSERT(false, "Missing case."); 474 break; 475 } 476 477 return Some(ret); 478 } 479 480 //////////////////////////////////////////////////////////////////////////////// 481 //////////////////////////////////////////////////////////////////////////////// 482 // WebGLFramebuffer 483 484 WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo) 485 : WebGLContextBoundObject(webgl), 486 mGLName(fbo), 487 mDepthAttachment(webgl, LOCAL_GL_DEPTH_ATTACHMENT), 488 mStencilAttachment(webgl, LOCAL_GL_STENCIL_ATTACHMENT), 489 mDepthStencilAttachment(webgl, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { 490 mAttachments.push_back(&mDepthAttachment); 491 mAttachments.push_back(&mStencilAttachment); 492 493 if (!webgl->IsWebGL2()) { 494 // Only WebGL1 has a separate depth+stencil attachment point. 495 mAttachments.push_back(&mDepthStencilAttachment); 496 } 497 498 size_t i = 0; 499 for (auto& cur : mColorAttachments) { 500 new (&cur) WebGLFBAttachPoint(webgl, LOCAL_GL_COLOR_ATTACHMENT0 + i); 501 i++; 502 503 mAttachments.push_back(&cur); 504 } 505 506 mColorDrawBuffers.push_back(&mColorAttachments[0]); 507 mColorReadBuffer = &mColorAttachments[0]; 508 } 509 510 WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, 511 UniquePtr<gl::MozFramebuffer> fbo) 512 : WebGLContextBoundObject(webgl), 513 mGLName(fbo->mFB), 514 mOpaque(std::move(fbo)), 515 mColorReadBuffer(nullptr) { 516 // Opaque Framebuffer is guaranteed to be complete at this point. 517 // Cache the Completeness info. 518 CompletenessInfo info; 519 info.width = mOpaque->mSize.width; 520 info.height = mOpaque->mSize.height; 521 info.zLayerCount = 1; 522 info.isMultiview = false; 523 524 mCompletenessInfo = std::move(info); 525 } 526 527 WebGLFramebuffer::~WebGLFramebuffer() { 528 InvalidateCaches(); 529 530 mDepthAttachment.Clear(); 531 mStencilAttachment.Clear(); 532 mDepthStencilAttachment.Clear(); 533 534 for (auto& cur : mColorAttachments) { 535 cur.Clear(); 536 } 537 538 if (!mContext) return; 539 // If opaque, fDeleteFramebuffers is called in the destructor of 540 // MozFramebuffer. 541 if (!mOpaque) { 542 mContext->gl->fDeleteFramebuffers(1, &mGLName); 543 } 544 } 545 546 //// 547 548 Maybe<WebGLFBAttachPoint*> WebGLFramebuffer::GetColorAttachPoint( 549 GLenum attachPoint) { 550 if (attachPoint == LOCAL_GL_NONE) return Some<WebGLFBAttachPoint*>(nullptr); 551 552 if (attachPoint < LOCAL_GL_COLOR_ATTACHMENT0) return Nothing(); 553 554 const size_t colorId = attachPoint - LOCAL_GL_COLOR_ATTACHMENT0; 555 556 MOZ_ASSERT(mContext->Limits().maxColorDrawBuffers <= webgl::kMaxDrawBuffers); 557 if (colorId >= mContext->MaxValidDrawBuffers()) return Nothing(); 558 559 return Some(&mColorAttachments[colorId]); 560 } 561 562 Maybe<WebGLFBAttachPoint*> WebGLFramebuffer::GetAttachPoint( 563 GLenum attachPoint) { 564 switch (attachPoint) { 565 case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT: 566 return Some(&mDepthStencilAttachment); 567 568 case LOCAL_GL_DEPTH_ATTACHMENT: 569 return Some(&mDepthAttachment); 570 571 case LOCAL_GL_STENCIL_ATTACHMENT: 572 return Some(&mStencilAttachment); 573 574 default: 575 return GetColorAttachPoint(attachPoint); 576 } 577 } 578 579 void WebGLFramebuffer::DetachTexture(const WebGLTexture* tex) { 580 for (const auto& attach : mAttachments) { 581 if (attach->Texture() == tex) { 582 attach->Clear(); 583 } 584 } 585 InvalidateCaches(); 586 } 587 588 void WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb) { 589 for (const auto& attach : mAttachments) { 590 if (attach->Renderbuffer() == rb) { 591 attach->Clear(); 592 } 593 } 594 InvalidateCaches(); 595 } 596 597 //////////////////////////////////////////////////////////////////////////////// 598 // Completeness 599 600 bool WebGLFramebuffer::HasDuplicateAttachments() const { 601 std::set<WebGLFBAttachPoint::Ordered> uniqueAttachSet; 602 603 for (const auto& attach : mColorAttachments) { 604 if (!attach.HasAttachment()) continue; 605 606 const WebGLFBAttachPoint::Ordered ordered(attach); 607 608 const bool didInsert = uniqueAttachSet.insert(ordered).second; 609 if (!didInsert) return true; 610 } 611 612 return false; 613 } 614 615 bool WebGLFramebuffer::HasDefinedAttachments() const { 616 bool hasAttachments = false; 617 for (const auto& attach : mAttachments) { 618 hasAttachments |= attach->HasAttachment(); 619 } 620 return hasAttachments; 621 } 622 623 bool WebGLFramebuffer::HasIncompleteAttachments( 624 nsCString* const out_info) const { 625 bool hasIncomplete = false; 626 for (const auto& cur : mAttachments) { 627 if (!cur->HasAttachment()) 628 continue; // Not defined, so can't count as incomplete. 629 630 hasIncomplete |= !cur->IsComplete(mContext, out_info); 631 } 632 return hasIncomplete; 633 } 634 635 bool WebGLFramebuffer::AllImageRectsMatch() const { 636 MOZ_ASSERT(HasDefinedAttachments()); 637 DebugOnly<nsCString> fbStatusInfo; 638 MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo)); 639 640 bool needsInit = true; 641 uint32_t width = 0; 642 uint32_t height = 0; 643 644 bool hasMismatch = false; 645 for (const auto& attach : mAttachments) { 646 const auto& imageInfo = attach->GetImageInfo(); 647 if (!imageInfo) continue; 648 649 const auto& curWidth = imageInfo->mWidth; 650 const auto& curHeight = imageInfo->mHeight; 651 652 if (needsInit) { 653 needsInit = false; 654 width = curWidth; 655 height = curHeight; 656 continue; 657 } 658 659 hasMismatch |= (curWidth != width || curHeight != height); 660 } 661 return !hasMismatch; 662 } 663 664 bool WebGLFramebuffer::AllImageSamplesMatch() const { 665 MOZ_ASSERT(HasDefinedAttachments()); 666 DebugOnly<nsCString> fbStatusInfo; 667 MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo)); 668 669 bool needsInit = true; 670 uint32_t samples = 0; 671 672 bool hasMismatch = false; 673 for (const auto& attach : mAttachments) { 674 const auto& imageInfo = attach->GetImageInfo(); 675 if (!imageInfo) continue; 676 677 const auto& curSamples = imageInfo->mSamples; 678 679 if (needsInit) { 680 needsInit = false; 681 samples = curSamples; 682 continue; 683 } 684 685 hasMismatch |= (curSamples != samples); 686 }; 687 return !hasMismatch; 688 } 689 690 FBStatus WebGLFramebuffer::PrecheckFramebufferStatus( 691 nsCString* const out_info) const { 692 MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || 693 mContext->mBoundReadFramebuffer == this); 694 if (!HasDefinedAttachments()) 695 return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT; // No 696 // attachments 697 698 if (HasIncompleteAttachments(out_info)) 699 return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; 700 701 if (!AllImageRectsMatch()) 702 return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS; // Inconsistent sizes 703 704 if (!AllImageSamplesMatch()) 705 return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE; // Inconsistent samples 706 707 if (HasDuplicateAttachments()) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED; 708 709 if (mContext->IsWebGL2()) { 710 MOZ_ASSERT(!mDepthStencilAttachment.HasAttachment()); 711 if (mDepthAttachment.HasAttachment() && 712 mStencilAttachment.HasAttachment()) { 713 if (!mDepthAttachment.IsEquivalentForFeedback(mStencilAttachment)) 714 return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED; 715 } 716 } else { 717 const auto depthOrStencilCount = 718 int(mDepthAttachment.HasAttachment()) + 719 int(mStencilAttachment.HasAttachment()) + 720 int(mDepthStencilAttachment.HasAttachment()); 721 if (depthOrStencilCount > 1) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED; 722 } 723 724 { 725 const WebGLFBAttachPoint* example = nullptr; 726 for (const auto& x : mAttachments) { 727 if (!x->HasAttachment()) continue; 728 if (!example) { 729 example = x; 730 continue; 731 } 732 if (x->ZLayerCount() != example->ZLayerCount()) { 733 return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR; 734 } 735 } 736 } 737 738 return LOCAL_GL_FRAMEBUFFER_COMPLETE; 739 } 740 741 //////////////////////////////////////// 742 // Validation 743 744 bool WebGLFramebuffer::ValidateAndInitAttachments( 745 const GLenum incompleteFbError) const { 746 MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || 747 mContext->mBoundReadFramebuffer == this); 748 749 const auto fbStatus = CheckFramebufferStatus(); 750 if (fbStatus == LOCAL_GL_FRAMEBUFFER_COMPLETE) return true; 751 752 mContext->GenerateError(incompleteFbError, "Framebuffer must be complete."); 753 return false; 754 } 755 756 bool WebGLFramebuffer::ValidateClearBufferType( 757 GLenum buffer, uint32_t drawBuffer, 758 const webgl::AttribBaseType funcType) const { 759 if (buffer != LOCAL_GL_COLOR) return true; 760 761 const auto& attach = mColorAttachments[drawBuffer]; 762 const auto& imageInfo = attach.GetImageInfo(); 763 if (!imageInfo) return true; 764 765 if (!count(mColorDrawBuffers.begin(), mColorDrawBuffers.end(), &attach)) 766 return true; // DRAW_BUFFERi set to NONE. 767 768 auto attachType = webgl::AttribBaseType::Float; 769 switch (imageInfo->mFormat->format->componentType) { 770 case webgl::ComponentType::Int: 771 attachType = webgl::AttribBaseType::Int; 772 break; 773 case webgl::ComponentType::UInt: 774 attachType = webgl::AttribBaseType::Uint; 775 break; 776 default: 777 break; 778 } 779 780 if (attachType != funcType) { 781 mContext->ErrorInvalidOperation( 782 "This attachment is of type %s, but" 783 " this function is of type %s.", 784 ToString(attachType), ToString(funcType)); 785 return false; 786 } 787 788 return true; 789 } 790 791 bool WebGLFramebuffer::ValidateForColorRead( 792 const webgl::FormatUsageInfo** const out_format, uint32_t* const out_width, 793 uint32_t* const out_height) const { 794 if (!mColorReadBuffer) { 795 mContext->ErrorInvalidOperation("READ_BUFFER must not be NONE."); 796 return false; 797 } 798 799 if (mColorReadBuffer->ZLayerCount() > 1) { 800 mContext->GenerateError(LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION, 801 "The READ_BUFFER attachment has multiple views."); 802 return false; 803 } 804 805 const auto& imageInfo = mColorReadBuffer->GetImageInfo(); 806 if (!imageInfo) { 807 mContext->ErrorInvalidOperation( 808 "The READ_BUFFER attachment is not defined."); 809 return false; 810 } 811 812 if (imageInfo->mSamples) { 813 mContext->ErrorInvalidOperation( 814 "The READ_BUFFER attachment is multisampled."); 815 return false; 816 } 817 818 *out_format = imageInfo->mFormat; 819 *out_width = imageInfo->mWidth; 820 *out_height = imageInfo->mHeight; 821 return true; 822 } 823 824 //////////////////////////////////////////////////////////////////////////////// 825 // Resolution and caching 826 827 void WebGLFramebuffer::DoDeferredAttachments() const { 828 if (mContext->IsWebGL2()) return; 829 830 const auto& gl = mContext->gl; 831 gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, 832 LOCAL_GL_RENDERBUFFER, 0); 833 gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, 834 LOCAL_GL_STENCIL_ATTACHMENT, 835 LOCAL_GL_RENDERBUFFER, 0); 836 837 const auto fn = [&](const WebGLFBAttachPoint& attach) { 838 MOZ_ASSERT(attach.mDeferAttachment); 839 if (attach.HasAttachment()) { 840 attach.DoAttachment(gl); 841 } 842 }; 843 // Only one of these will have an attachment. 844 fn(mDepthAttachment); 845 fn(mStencilAttachment); 846 fn(mDepthStencilAttachment); 847 } 848 849 void WebGLFramebuffer::ResolveAttachmentData() const { 850 // GLES 3.0.5 p188: 851 // The result of clearing integer color buffers with `Clear` is undefined. 852 853 // Two different approaches: 854 // On WebGL 2, we have glClearBuffer, and *must* use it for integer buffers, 855 // so let's just use it for all the buffers. One WebGL 1, we might not have 856 // glClearBuffer, 857 858 // WebGL 1 is easier, because we can just call glClear, possibly with 859 // glDrawBuffers. 860 861 const auto& gl = mContext->gl; 862 863 const webgl::ScopedPrepForResourceClear scopedPrep(*mContext); 864 865 if (mContext->IsWebGL2()) { 866 const uint32_t uiZeros[4] = {}; 867 const int32_t iZeros[4] = {}; 868 const float fZeros[4] = {}; 869 const float fOne[] = {1.0f}; 870 871 for (const auto& cur : mAttachments) { 872 const auto& imageInfo = cur->GetImageInfo(); 873 if (!imageInfo || !imageInfo->mUninitializedSlices) 874 continue; // Nothing attached, or already has data. 875 876 const auto fnClearBuffer = [&]() { 877 const auto& format = imageInfo->mFormat->format; 878 MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(uiZeros)); 879 MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(iZeros)); 880 MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(fZeros)); 881 882 switch (cur->mAttachmentPoint) { 883 case LOCAL_GL_DEPTH_ATTACHMENT: 884 gl->fClearBufferfv(LOCAL_GL_DEPTH, 0, fOne); 885 break; 886 case LOCAL_GL_STENCIL_ATTACHMENT: 887 gl->fClearBufferiv(LOCAL_GL_STENCIL, 0, iZeros); 888 break; 889 default: 890 MOZ_ASSERT(cur->mAttachmentPoint != 891 LOCAL_GL_DEPTH_STENCIL_ATTACHMENT); 892 const uint32_t drawBuffer = 893 cur->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0; 894 MOZ_ASSERT(drawBuffer <= 100); 895 switch (format->componentType) { 896 case webgl::ComponentType::Int: 897 gl->fClearBufferiv(LOCAL_GL_COLOR, drawBuffer, iZeros); 898 break; 899 case webgl::ComponentType::UInt: 900 gl->fClearBufferuiv(LOCAL_GL_COLOR, drawBuffer, uiZeros); 901 break; 902 default: 903 gl->fClearBufferfv(LOCAL_GL_COLOR, drawBuffer, fZeros); 904 break; 905 } 906 } 907 }; 908 909 if (imageInfo->mDepth > 1) { 910 const auto& tex = cur->Texture(); 911 const gl::ScopedFramebuffer scopedFB(gl); 912 const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB()); 913 for (const auto z : IntegerRange(imageInfo->mDepth)) { 914 if ((*imageInfo->mUninitializedSlices)[z]) { 915 gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, 916 cur->mAttachmentPoint, tex->mGLName, 917 cur->MipLevel(), z); 918 fnClearBuffer(); 919 } 920 } 921 } else { 922 fnClearBuffer(); 923 } 924 imageInfo->mUninitializedSlices.reset(); 925 } 926 return; 927 } 928 929 uint32_t clearBits = 0; 930 std::vector<GLenum> drawBufferForClear; 931 932 const auto fnGather = [&](const WebGLFBAttachPoint& attach, 933 const uint32_t attachClearBits) { 934 const auto& imageInfo = attach.GetImageInfo(); 935 if (!imageInfo || !imageInfo->mUninitializedSlices) return false; 936 937 clearBits |= attachClearBits; 938 imageInfo->mUninitializedSlices.reset(); // Just mark it now. 939 return true; 940 }; 941 942 ////// 943 944 for (const auto& cur : mColorAttachments) { 945 if (fnGather(cur, LOCAL_GL_COLOR_BUFFER_BIT)) { 946 const uint32_t id = cur.mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0; 947 MOZ_ASSERT(id <= 100); 948 drawBufferForClear.resize(id + 1); // Pads with zeros! 949 drawBufferForClear[id] = cur.mAttachmentPoint; 950 } 951 } 952 953 (void)fnGather(mDepthAttachment, LOCAL_GL_DEPTH_BUFFER_BIT); 954 (void)fnGather(mStencilAttachment, LOCAL_GL_STENCIL_BUFFER_BIT); 955 (void)fnGather(mDepthStencilAttachment, 956 LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT); 957 958 ////// 959 960 if (!clearBits) return; 961 962 if (gl->IsSupported(gl::GLFeature::draw_buffers)) { 963 gl->fDrawBuffers(drawBufferForClear.size(), drawBufferForClear.data()); 964 } 965 966 gl->fClear(clearBits); 967 968 RefreshDrawBuffers(); 969 } 970 971 WebGLFramebuffer::CompletenessInfo::~CompletenessInfo() { 972 if (!this->fb) return; 973 const auto& fb = *this->fb; 974 const auto& webgl = fb.mContext; 975 fb.mNumFBStatusInvals++; 976 if (fb.mNumFBStatusInvals > webgl->mMaxAcceptableFBStatusInvals) { 977 webgl->GeneratePerfWarning( 978 "FB was invalidated after being complete %u" 979 " times. [webgl.perf.max-acceptable-fb-status-invals]", 980 uint32_t(fb.mNumFBStatusInvals)); 981 } 982 } 983 984 //////////////////////////////////////////////////////////////////////////////// 985 // Entrypoints 986 987 FBStatus WebGLFramebuffer::CheckFramebufferStatus() const { 988 if (MOZ_UNLIKELY(mOpaque && !mInOpaqueRAF)) { 989 // Opaque Framebuffers are considered incomplete outside of a RAF. 990 return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED; 991 } 992 993 if (mCompletenessInfo) return LOCAL_GL_FRAMEBUFFER_COMPLETE; 994 995 // Ok, let's try to resolve it! 996 997 nsCString statusInfo; 998 FBStatus ret = PrecheckFramebufferStatus(&statusInfo); 999 do { 1000 if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE) break; 1001 1002 // Looks good on our end. Let's ask the driver. 1003 gl::GLContext* const gl = mContext->gl; 1004 1005 const ScopedFBRebinder autoFB(mContext); 1006 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLName); 1007 1008 //// 1009 1010 DoDeferredAttachments(); 1011 RefreshDrawBuffers(); 1012 RefreshReadBuffer(); 1013 1014 ret = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); 1015 1016 //// 1017 1018 if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE) { 1019 const nsPrintfCString text("Bad status according to the driver: 0x%04x", 1020 ret.get()); 1021 statusInfo = text; 1022 break; 1023 } 1024 1025 ResolveAttachmentData(); 1026 1027 // Sweet, let's cache that. 1028 auto info = CompletenessInfo{this}; 1029 mCompletenessInfo.ResetInvalidators({}); 1030 mCompletenessInfo.AddInvalidator(*this); 1031 1032 const auto fnIsFloat32 = [](const webgl::FormatInfo& info) { 1033 if (info.componentType != webgl::ComponentType::Float) return false; 1034 return info.r == 32; 1035 }; 1036 1037 for (const auto& cur : mAttachments) { 1038 const auto& tex = cur->Texture(); 1039 const auto& rb = cur->Renderbuffer(); 1040 if (tex) { 1041 mCompletenessInfo.AddInvalidator(*tex); 1042 info.texAttachments.push_back(cur); 1043 } else if (rb) { 1044 mCompletenessInfo.AddInvalidator(*rb); 1045 } else { 1046 continue; 1047 } 1048 const auto& imageInfo = cur->GetImageInfo(); 1049 MOZ_ASSERT(imageInfo); 1050 1051 const auto maybeColorId = cur->ColorAttachmentId(); 1052 if (maybeColorId) { 1053 const auto id = *maybeColorId; 1054 info.hasAttachment[id] = true; 1055 info.isAttachmentF32[id] = fnIsFloat32(*imageInfo->mFormat->format); 1056 } 1057 1058 info.width = imageInfo->mWidth; 1059 info.height = imageInfo->mHeight; 1060 info.zLayerCount = cur->ZLayerCount(); 1061 info.isMultiview = cur->IsMultiview(); 1062 } 1063 MOZ_ASSERT(info.width && info.height); 1064 mCompletenessInfo = std::move(info); 1065 info.fb = nullptr; // Don't trigger the invalidation warning. 1066 return LOCAL_GL_FRAMEBUFFER_COMPLETE; 1067 } while (false); 1068 1069 MOZ_ASSERT(ret != LOCAL_GL_FRAMEBUFFER_COMPLETE); 1070 mContext->GenerateWarning("Framebuffer not complete. (status: 0x%04x) %s", 1071 ret.get(), statusInfo.BeginReading()); 1072 return ret; 1073 } 1074 1075 //// 1076 1077 void WebGLFramebuffer::RefreshDrawBuffers() const { 1078 const auto& gl = mContext->gl; 1079 if (!gl->IsSupported(gl::GLFeature::draw_buffers)) return; 1080 1081 // Prior to GL4.1, having a no-image FB attachment that's selected by 1082 // DrawBuffers yields a framebuffer status of 1083 // FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER. We could workaround this only on 1084 // affected versions, but it's easier be unconditional. 1085 std::vector<GLenum> driverBuffers(mContext->Limits().maxColorDrawBuffers, 1086 LOCAL_GL_NONE); 1087 for (const auto& attach : mColorDrawBuffers) { 1088 if (attach->HasAttachment()) { 1089 const uint32_t index = 1090 attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0; 1091 driverBuffers[index] = attach->mAttachmentPoint; 1092 } 1093 } 1094 1095 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, mGLName); 1096 gl->fDrawBuffers(driverBuffers.size(), driverBuffers.data()); 1097 } 1098 1099 void WebGLFramebuffer::RefreshReadBuffer() const { 1100 const auto& gl = mContext->gl; 1101 if (!gl->IsSupported(gl::GLFeature::read_buffer)) return; 1102 1103 // Prior to GL4.1, having a no-image FB attachment that's selected by 1104 // ReadBuffer yields a framebuffer status of 1105 // FRAMEBUFFER_INCOMPLETE_READ_BUFFER. We could workaround this only on 1106 // affected versions, but it's easier be unconditional. 1107 GLenum driverBuffer = LOCAL_GL_NONE; 1108 if (mColorReadBuffer && mColorReadBuffer->HasAttachment()) { 1109 driverBuffer = mColorReadBuffer->mAttachmentPoint; 1110 } 1111 1112 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mGLName); 1113 gl->fReadBuffer(driverBuffer); 1114 } 1115 1116 //// 1117 1118 void WebGLFramebuffer::DrawBuffers(const std::vector<GLenum>& buffers) { 1119 if (buffers.size() > mContext->MaxValidDrawBuffers()) { 1120 // "An INVALID_VALUE error is generated if `n` is greater than 1121 // MAX_DRAW_BUFFERS." 1122 mContext->ErrorInvalidValue( 1123 "`buffers` must have a length <=" 1124 " MAX_DRAW_BUFFERS."); 1125 return; 1126 } 1127 1128 std::vector<const WebGLFBAttachPoint*> newColorDrawBuffers; 1129 newColorDrawBuffers.reserve(buffers.size()); 1130 1131 mDrawBufferEnabled.reset(); 1132 for (const auto i : IntegerRange(buffers.size())) { 1133 // "If the GL is bound to a draw framebuffer object, the `i`th buffer listed 1134 // in bufs must be COLOR_ATTACHMENTi or NONE. Specifying a buffer out of 1135 // order, BACK, or COLOR_ATTACHMENTm where `m` is greater than or equal to 1136 // the value of MAX_COLOR_ATTACHMENTS, will generate the error 1137 // INVALID_OPERATION. 1138 1139 // WEBGL_draw_buffers: 1140 // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater 1141 // than or equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter." This 1142 // means that if buffers.Length() isn't larger than MaxDrawBuffers, it won't 1143 // be larger than MaxColorAttachments. 1144 const auto& cur = buffers[i]; 1145 if (cur == LOCAL_GL_COLOR_ATTACHMENT0 + i) { 1146 const auto& attach = mColorAttachments[i]; 1147 newColorDrawBuffers.push_back(&attach); 1148 mDrawBufferEnabled[i] = true; 1149 } else if (cur != LOCAL_GL_NONE) { 1150 const bool isColorEnum = (cur >= LOCAL_GL_COLOR_ATTACHMENT0 && 1151 cur < mContext->LastColorAttachmentEnum()); 1152 if (cur != LOCAL_GL_BACK && !isColorEnum) { 1153 mContext->ErrorInvalidEnum("Unexpected enum in buffers."); 1154 return; 1155 } 1156 1157 mContext->ErrorInvalidOperation( 1158 "`buffers[i]` must be NONE or" 1159 " COLOR_ATTACHMENTi."); 1160 return; 1161 } 1162 } 1163 1164 //// 1165 1166 mColorDrawBuffers = std::move(newColorDrawBuffers); 1167 RefreshDrawBuffers(); // Calls glDrawBuffers. 1168 } 1169 1170 void WebGLFramebuffer::ReadBuffer(GLenum attachPoint) { 1171 const auto& maybeAttach = GetColorAttachPoint(attachPoint); 1172 if (!maybeAttach) { 1173 const char text[] = 1174 "`mode` must be a COLOR_ATTACHMENTi, for 0 <= i <" 1175 " MAX_DRAW_BUFFERS."; 1176 if (attachPoint == LOCAL_GL_BACK) { 1177 mContext->ErrorInvalidOperation(text); 1178 } else { 1179 mContext->ErrorInvalidEnum(text); 1180 } 1181 return; 1182 } 1183 const auto& attach = maybeAttach.value(); // Might be nullptr. 1184 1185 //// 1186 1187 mColorReadBuffer = attach; 1188 RefreshReadBuffer(); // Calls glReadBuffer. 1189 } 1190 1191 //// 1192 1193 bool WebGLFramebuffer::FramebufferAttach(const GLenum attachEnum, 1194 const webgl::FbAttachInfo& toAttach) { 1195 MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || 1196 mContext->mBoundReadFramebuffer == this); 1197 1198 if (MOZ_UNLIKELY(mOpaque)) { 1199 // An opaque framebuffer's attachments cannot be inspected or changed. 1200 return false; 1201 } 1202 1203 // `attachment` 1204 const auto maybeAttach = GetAttachPoint(attachEnum); 1205 if (!maybeAttach || !maybeAttach.value()) return false; 1206 const auto& attach = maybeAttach.value(); 1207 1208 const auto& gl = mContext->gl; 1209 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLName); 1210 if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { 1211 mDepthAttachment.Set(gl, toAttach); 1212 mStencilAttachment.Set(gl, toAttach); 1213 } else { 1214 attach->Set(gl, toAttach); 1215 } 1216 InvalidateCaches(); 1217 return true; 1218 } 1219 1220 Maybe<double> WebGLFramebuffer::GetAttachmentParameter(GLenum attachEnum, 1221 GLenum pname) { 1222 const auto maybeAttach = GetAttachPoint(attachEnum); 1223 if (!maybeAttach || attachEnum == LOCAL_GL_NONE) { 1224 mContext->ErrorInvalidEnum( 1225 "Can only query COLOR_ATTACHMENTi," 1226 " DEPTH_ATTACHMENT, DEPTH_STENCIL_ATTACHMENT, or" 1227 " STENCIL_ATTACHMENT for a framebuffer."); 1228 return Nothing(); 1229 } 1230 if (MOZ_UNLIKELY(mOpaque)) { 1231 mContext->ErrorInvalidOperation( 1232 "An opaque framebuffer's attachments cannot be inspected or changed."); 1233 return Nothing(); 1234 } 1235 auto attach = maybeAttach.value(); 1236 1237 if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { 1238 if (mDepthAttachment.Renderbuffer() != mStencilAttachment.Renderbuffer() || 1239 mDepthAttachment.Texture() != mStencilAttachment.Texture()) { 1240 mContext->ErrorInvalidOperation( 1241 "DEPTH_ATTACHMENT and STENCIL_ATTACHMENT" 1242 " have different objects bound."); 1243 return Nothing(); 1244 } 1245 1246 attach = &mDepthAttachment; 1247 } 1248 1249 return attach->GetParameter(mContext, attachEnum, pname); 1250 } 1251 1252 //////////////////// 1253 1254 static void GetBackbufferFormats(const WebGLContext* webgl, 1255 const webgl::FormatInfo** const out_color, 1256 const webgl::FormatInfo** const out_depth, 1257 const webgl::FormatInfo** const out_stencil) { 1258 const auto& options = webgl->Options(); 1259 1260 const auto effFormat = (options.alpha ? webgl::EffectiveFormat::RGBA8 1261 : webgl::EffectiveFormat::RGB8); 1262 *out_color = webgl::GetFormat(effFormat); 1263 1264 *out_depth = nullptr; 1265 *out_stencil = nullptr; 1266 if (options.depth && options.stencil) { 1267 *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH24_STENCIL8); 1268 *out_stencil = *out_depth; 1269 } else { 1270 if (options.depth) { 1271 *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT16); 1272 } 1273 if (options.stencil) { 1274 *out_stencil = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8); 1275 } 1276 } 1277 } 1278 1279 /*static*/ 1280 void WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl, GLint _srcX0, 1281 GLint _srcY0, GLint _srcX1, GLint _srcY1, 1282 GLint _dstX0, GLint _dstY0, GLint _dstX1, 1283 GLint _dstY1, GLbitfield mask, 1284 GLenum filter) { 1285 auto srcP0 = ivec2{_srcX0, _srcY0}; 1286 auto srcP1 = ivec2{_srcX1, _srcY1}; 1287 auto dstP0 = ivec2{_dstX0, _dstY0}; 1288 auto dstP1 = ivec2{_dstX1, _dstY1}; 1289 1290 const GLbitfield depthAndStencilBits = 1291 LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT; 1292 if (bool(mask & depthAndStencilBits) && filter == LOCAL_GL_LINEAR) { 1293 webgl->ErrorInvalidOperation( 1294 "DEPTH_BUFFER_BIT and STENCIL_BUFFER_BIT can" 1295 " only be used with NEAREST filtering."); 1296 return; 1297 } 1298 1299 const auto& srcFB = webgl->mBoundReadFramebuffer; 1300 const auto& dstFB = webgl->mBoundDrawFramebuffer; 1301 1302 //// 1303 // Collect data 1304 1305 const auto fnGetFormat = 1306 [](const WebGLFBAttachPoint& cur, 1307 bool* const out_hasSamples) -> const webgl::FormatInfo* { 1308 const auto& imageInfo = cur.GetImageInfo(); 1309 if (!imageInfo) return nullptr; // No attachment. 1310 *out_hasSamples = bool(imageInfo->mSamples); 1311 return imageInfo->mFormat->format; 1312 }; 1313 1314 bool srcHasSamples = false; 1315 bool srcIsFilterable = true; 1316 const webgl::FormatInfo* srcColorFormat; 1317 const webgl::FormatInfo* srcDepthFormat; 1318 const webgl::FormatInfo* srcStencilFormat; 1319 gfx::IntSize srcSize; 1320 1321 if (srcFB) { 1322 const auto& info = *srcFB->GetCompletenessInfo(); 1323 if (info.zLayerCount != 1) { 1324 webgl->GenerateError( 1325 LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION, 1326 "Source framebuffer cannot have more than one multiview layer."); 1327 return; 1328 } 1329 srcColorFormat = nullptr; 1330 if (srcFB->mColorReadBuffer) { 1331 const auto& imageInfo = srcFB->mColorReadBuffer->GetImageInfo(); 1332 if (imageInfo) { 1333 srcIsFilterable &= imageInfo->mFormat->isFilterable; 1334 } 1335 srcColorFormat = fnGetFormat(*(srcFB->mColorReadBuffer), &srcHasSamples); 1336 } 1337 srcDepthFormat = fnGetFormat(srcFB->DepthAttachment(), &srcHasSamples); 1338 srcStencilFormat = fnGetFormat(srcFB->StencilAttachment(), &srcHasSamples); 1339 MOZ_ASSERT(!srcFB->DepthStencilAttachment().HasAttachment()); 1340 srcSize = {info.width, info.height}; 1341 } else { 1342 srcHasSamples = false; // Always false. 1343 1344 GetBackbufferFormats(webgl, &srcColorFormat, &srcDepthFormat, 1345 &srcStencilFormat); 1346 const auto& size = webgl->DrawingBufferSize(); 1347 srcSize = {size.x, size.y}; 1348 } 1349 1350 //// 1351 1352 bool dstHasSamples = false; 1353 const webgl::FormatInfo* dstDepthFormat; 1354 const webgl::FormatInfo* dstStencilFormat; 1355 bool dstHasColor = false; 1356 bool colorFormatsMatch = true; 1357 bool colorTypesMatch = true; 1358 bool colorSrgbMatches = true; 1359 gfx::IntSize dstSize; 1360 1361 const auto fnCheckColorFormat = [&](const webgl::FormatInfo* dstFormat) { 1362 MOZ_ASSERT(dstFormat->r || dstFormat->g || dstFormat->b || dstFormat->a); 1363 dstHasColor = true; 1364 colorFormatsMatch &= (dstFormat == srcColorFormat); 1365 colorTypesMatch &= 1366 srcColorFormat && (dstFormat->baseType == srcColorFormat->baseType); 1367 colorSrgbMatches &= 1368 srcColorFormat && (dstFormat->isSRGB == srcColorFormat->isSRGB); 1369 }; 1370 1371 if (dstFB) { 1372 for (const auto& cur : dstFB->mColorDrawBuffers) { 1373 const auto& format = fnGetFormat(*cur, &dstHasSamples); 1374 if (!format) continue; 1375 1376 fnCheckColorFormat(format); 1377 } 1378 1379 dstDepthFormat = fnGetFormat(dstFB->DepthAttachment(), &dstHasSamples); 1380 dstStencilFormat = fnGetFormat(dstFB->StencilAttachment(), &dstHasSamples); 1381 MOZ_ASSERT(!dstFB->DepthStencilAttachment().HasAttachment()); 1382 1383 const auto& info = *dstFB->GetCompletenessInfo(); 1384 if (info.isMultiview) { 1385 webgl->GenerateError( 1386 LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION, 1387 "Destination framebuffer cannot have multiview attachments."); 1388 return; 1389 } 1390 dstSize = {info.width, info.height}; 1391 } else { 1392 dstHasSamples = webgl->Options().antialias; 1393 1394 const webgl::FormatInfo* dstColorFormat; 1395 GetBackbufferFormats(webgl, &dstColorFormat, &dstDepthFormat, 1396 &dstStencilFormat); 1397 1398 fnCheckColorFormat(dstColorFormat); 1399 1400 const auto& size = webgl->DrawingBufferSize(); 1401 dstSize = {size.x, size.y}; 1402 } 1403 1404 //// 1405 // Clear unused buffer bits 1406 1407 if (mask & LOCAL_GL_COLOR_BUFFER_BIT && !srcColorFormat && !dstHasColor) { 1408 mask ^= LOCAL_GL_COLOR_BUFFER_BIT; 1409 } 1410 1411 if (mask & LOCAL_GL_DEPTH_BUFFER_BIT && !srcDepthFormat && !dstDepthFormat) { 1412 mask ^= LOCAL_GL_DEPTH_BUFFER_BIT; 1413 } 1414 1415 if (mask & LOCAL_GL_STENCIL_BUFFER_BIT && !srcStencilFormat && 1416 !dstStencilFormat) { 1417 mask ^= LOCAL_GL_STENCIL_BUFFER_BIT; 1418 } 1419 1420 //// 1421 // Validation 1422 1423 if (dstHasSamples) { 1424 webgl->ErrorInvalidOperation( 1425 "DRAW_FRAMEBUFFER may not have multiple" 1426 " samples."); 1427 return; 1428 } 1429 1430 bool requireFilterable = (filter == LOCAL_GL_LINEAR); 1431 if (srcHasSamples) { 1432 requireFilterable = false; // It picks one. 1433 1434 if (mask & LOCAL_GL_COLOR_BUFFER_BIT && dstHasColor && !colorFormatsMatch) { 1435 webgl->ErrorInvalidOperation( 1436 "Color buffer formats must match if" 1437 " selected, when reading from a multisampled" 1438 " source."); 1439 return; 1440 } 1441 1442 if (srcP0 != dstP0 || srcP1 != dstP1) { 1443 webgl->ErrorInvalidOperation( 1444 "If the source is multisampled, then the" 1445 " source and dest regions must match exactly."); 1446 return; 1447 } 1448 } 1449 1450 // - 1451 1452 if (mask & LOCAL_GL_COLOR_BUFFER_BIT) { 1453 if (requireFilterable && !srcIsFilterable) { 1454 webgl->ErrorInvalidOperation( 1455 "`filter` is LINEAR and READ_BUFFER" 1456 " contains integer data."); 1457 return; 1458 } 1459 1460 if (!colorTypesMatch) { 1461 webgl->ErrorInvalidOperation( 1462 "Color component types (float/uint/" 1463 "int) must match."); 1464 return; 1465 } 1466 } 1467 1468 /* GLES 3.0.4, p199: 1469 * Calling BlitFramebuffer will result in an INVALID_OPERATION error if 1470 * mask includes DEPTH_BUFFER_BIT or STENCIL_BUFFER_BIT, and the source 1471 * and destination depth and stencil buffer formats do not match. 1472 * 1473 * jgilbert: The wording is such that if only DEPTH_BUFFER_BIT is specified, 1474 * the stencil formats must match. This seems wrong. It could be a spec bug, 1475 * or I could be missing an interaction in one of the earlier paragraphs. 1476 */ 1477 if (mask & LOCAL_GL_DEPTH_BUFFER_BIT && dstDepthFormat && 1478 dstDepthFormat != srcDepthFormat) { 1479 webgl->ErrorInvalidOperation( 1480 "Depth buffer formats must match if selected."); 1481 return; 1482 } 1483 1484 if (mask & LOCAL_GL_STENCIL_BUFFER_BIT && dstStencilFormat && 1485 dstStencilFormat != srcStencilFormat) { 1486 webgl->ErrorInvalidOperation( 1487 "Stencil buffer formats must match if selected."); 1488 return; 1489 } 1490 1491 //// 1492 // Check for feedback 1493 1494 if (srcFB && dstFB) { 1495 const WebGLFBAttachPoint* feedback = nullptr; 1496 1497 if (mask & LOCAL_GL_COLOR_BUFFER_BIT) { 1498 MOZ_ASSERT(srcFB->mColorReadBuffer->HasAttachment()); 1499 for (const auto& cur : dstFB->mColorDrawBuffers) { 1500 if (srcFB->mColorReadBuffer->IsEquivalentForFeedback(*cur)) { 1501 feedback = cur; 1502 break; 1503 } 1504 } 1505 } 1506 1507 if (mask & LOCAL_GL_DEPTH_BUFFER_BIT && 1508 srcFB->DepthAttachment().IsEquivalentForFeedback( 1509 dstFB->DepthAttachment())) { 1510 feedback = &dstFB->DepthAttachment(); 1511 } 1512 1513 if (mask & LOCAL_GL_STENCIL_BUFFER_BIT && 1514 srcFB->StencilAttachment().IsEquivalentForFeedback( 1515 dstFB->StencilAttachment())) { 1516 feedback = &dstFB->StencilAttachment(); 1517 } 1518 1519 if (feedback) { 1520 webgl->ErrorInvalidOperation( 1521 "Feedback detected into DRAW_FRAMEBUFFER's" 1522 " 0x%04x attachment.", 1523 feedback->mAttachmentPoint); 1524 return; 1525 } 1526 } else if (!srcFB && !dstFB) { 1527 webgl->ErrorInvalidOperation("Feedback with default framebuffer."); 1528 return; 1529 } 1530 1531 // - 1532 // Mutually constrain src and dst rects for eldritch blits. 1533 1534 [&] { 1535 using fvec2 = avec2<float>; // Switch to float, because there's no perfect 1536 // solution anyway. 1537 1538 const auto zero2f = fvec2{0, 0}; 1539 const auto srcSizef = AsVec(srcSize).StaticCast<fvec2>(); 1540 const auto dstSizef = AsVec(dstSize).StaticCast<fvec2>(); 1541 1542 const auto srcP0f = srcP0.StaticCast<fvec2>(); 1543 const auto srcP1f = srcP1.StaticCast<fvec2>(); 1544 const auto dstP0f = dstP0.StaticCast<fvec2>(); 1545 const auto dstP1f = dstP1.StaticCast<fvec2>(); 1546 1547 const auto srcRectDiff = srcP1f - srcP0f; 1548 const auto dstRectDiff = dstP1f - dstP0f; 1549 1550 // Skip if zero-sized. 1551 if (!srcRectDiff.x || !srcRectDiff.y || !dstRectDiff.x || !dstRectDiff.y) { 1552 srcP0 = srcP1 = dstP0 = dstP1 = {0, 0}; 1553 return; 1554 } 1555 1556 // Clamp the rect points 1557 const auto srcQ0 = srcP0f.Clamp(zero2f, srcSizef); 1558 const auto srcQ1 = srcP1f.Clamp(zero2f, srcSizef); 1559 1560 // Normalized to the [0,1] abstact copy rect 1561 const auto srcQ0Norm = (srcQ0 - srcP0f) / srcRectDiff; 1562 const auto srcQ1Norm = (srcQ1 - srcP0f) / srcRectDiff; 1563 1564 // Map into dst 1565 const auto srcQ0InDst = dstP0f + srcQ0Norm * dstRectDiff; 1566 const auto srcQ1InDst = dstP0f + srcQ1Norm * dstRectDiff; 1567 1568 // Clamp the rect points 1569 const auto dstQ0 = srcQ0InDst.Clamp(zero2f, dstSizef); 1570 const auto dstQ1 = srcQ1InDst.Clamp(zero2f, dstSizef); 1571 1572 // Alright, time to go back to src! 1573 // Normalized to the [0,1] abstact copy rect 1574 const auto dstQ0Norm = (dstQ0 - dstP0f) / dstRectDiff; 1575 const auto dstQ1Norm = (dstQ1 - dstP0f) / dstRectDiff; 1576 1577 // Map into src 1578 const auto dstQ0InSrc = srcP0f + dstQ0Norm * srcRectDiff; 1579 const auto dstQ1InSrc = srcP0f + dstQ1Norm * srcRectDiff; 1580 1581 const auto srcQ0Constrained = dstQ0InSrc.Clamp(zero2f, srcSizef); 1582 const auto srcQ1Constrained = dstQ1InSrc.Clamp(zero2f, srcSizef); 1583 1584 // Round, don't floor: 1585 srcP0 = (srcQ0Constrained + 0.5).StaticCast<ivec2>(); 1586 srcP1 = (srcQ1Constrained + 0.5).StaticCast<ivec2>(); 1587 dstP0 = (dstQ0 + 0.5).StaticCast<ivec2>(); 1588 dstP1 = (dstQ1 + 0.5).StaticCast<ivec2>(); 1589 }(); 1590 1591 bool inBounds = true; 1592 inBounds &= (srcP0 == srcP0.Clamp({0, 0}, AsVec(srcSize))); 1593 inBounds &= (srcP1 == srcP1.Clamp({0, 0}, AsVec(srcSize))); 1594 inBounds &= (dstP0 == dstP0.Clamp({0, 0}, AsVec(dstSize))); 1595 inBounds &= (dstP1 == dstP1.Clamp({0, 0}, AsVec(dstSize))); 1596 if (!inBounds) { 1597 webgl->ErrorImplementationBug( 1598 "Subrects still not within src and dst after constraining."); 1599 return; 1600 } 1601 1602 // - 1603 // Execute as constrained 1604 1605 const auto& gl = webgl->gl; 1606 const ScopedDrawCallWrapper wrapper(*webgl); 1607 1608 gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x, dstP0.y, 1609 dstP1.x, dstP1.y, mask, filter); 1610 1611 // - 1612 1613 if (mask & LOCAL_GL_COLOR_BUFFER_BIT && !colorSrgbMatches && !gl->IsGLES() && 1614 gl->Version() < 440) { 1615 // Mostly for Mac. 1616 // Remember, we have to filter in the *linear* format blit. 1617 1618 // src -Blit-> fbB -DrawBlit-> fbC -Blit-> dst 1619 1620 const auto fbB = gl::MozFramebuffer::Create(gl, {1, 1}, 0, false); 1621 const auto fbC = gl::MozFramebuffer::Create(gl, {1, 1}, 0, false); 1622 1623 // - 1624 1625 auto sizeBC = srcSize; 1626 GLenum formatC = LOCAL_GL_RGBA8; 1627 if (srcColorFormat->isSRGB) { 1628 // srgb -> linear 1629 } else { 1630 // linear -> srgb 1631 sizeBC = dstSize; 1632 formatC = LOCAL_GL_SRGB8_ALPHA8; 1633 } 1634 1635 const auto fnSetTex = [&](const gl::MozFramebuffer& fb, 1636 const GLenum format) { 1637 const gl::ScopedBindTexture bindTex(gl, fb.ColorTex()); 1638 gl->fTexStorage2D(LOCAL_GL_TEXTURE_2D, 1, format, sizeBC.width, 1639 sizeBC.height); 1640 }; 1641 fnSetTex(*fbB, srcColorFormat->sizedFormat); 1642 fnSetTex(*fbC, formatC); 1643 1644 // - 1645 1646 { 1647 const gl::ScopedBindFramebuffer bindFb(gl); 1648 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fbB->mFB); 1649 1650 if (srcColorFormat->isSRGB) { 1651 // srgb -> linear 1652 gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, srcP0.x, 1653 srcP0.y, srcP1.x, srcP1.y, 1654 LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_NEAREST); 1655 } else { 1656 // linear -> srgb 1657 gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x, 1658 dstP0.y, dstP1.x, dstP1.y, 1659 LOCAL_GL_COLOR_BUFFER_BIT, filter); 1660 } 1661 1662 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fbC->mFB); 1663 gl->BlitHelper()->DrawBlitTextureToFramebuffer(fbB->ColorTex(), sizeBC, 1664 sizeBC); 1665 } 1666 1667 { 1668 const gl::ScopedBindFramebuffer bindFb(gl); 1669 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbC->mFB); 1670 1671 if (srcColorFormat->isSRGB) { 1672 // srgb -> linear 1673 gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x, 1674 dstP0.y, dstP1.x, dstP1.y, 1675 LOCAL_GL_COLOR_BUFFER_BIT, filter); 1676 } else { 1677 // linear -> srgb 1678 gl->fBlitFramebuffer(dstP0.x, dstP0.y, dstP1.x, dstP1.y, dstP0.x, 1679 dstP0.y, dstP1.x, dstP1.y, 1680 LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_NEAREST); 1681 } 1682 } 1683 } 1684 1685 // - 1686 // glBlitFramebuffer ignores glColorMask! 1687 1688 if (!webgl->mBoundDrawFramebuffer && webgl->mNeedsFakeNoAlpha) { 1689 const auto dstRectMin = MinExtents(dstP0, dstP1); 1690 const auto dstRectMax = MaxExtents(dstP0, dstP1); 1691 const auto dstRectSize = dstRectMax - dstRectMin; 1692 const WebGLContext::ScissorRect dstRect = {dstRectMin.x, dstRectMin.y, 1693 dstRectSize.x, dstRectSize.y}; 1694 dstRect.Apply(*gl); 1695 1696 const auto forClear = webgl::ScopedPrepForResourceClear{*webgl}; 1697 1698 gl->fClearColor(0, 0, 0, 1); 1699 webgl->DoColorMask(Some(0), 0b1000); // Only alpha. 1700 gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT); 1701 1702 webgl->mScissorRect.Apply(*gl); 1703 } 1704 } 1705 1706 } // namespace mozilla