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