Bug 1732219 - Add API for fetching the preview image. r=geckoview-reviewers,agi,mconley
[gecko.git] / dom / canvas / WebGLFramebuffer.cpp
blob9217d6910ed08a1fb8f0d88f4c1dd12caf8e965e
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 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
356 isPNameValid = webgl->IsWebGL2();
357 break;
359 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
360 isPNameValid = (webgl->IsWebGL2() ||
361 webgl->IsExtensionEnabled(WebGLExtensionID::EXT_sRGB));
362 break;
365 if (!isPNameValid) {
366 webgl->ErrorInvalidEnum("Invalid pname: 0x%04x", pname);
367 return Nothing();
370 const auto& imageInfo = *GetImageInfo();
371 const auto& usage = imageInfo.mFormat;
372 if (!usage) {
373 if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING)
374 return Some(LOCAL_GL_LINEAR);
376 return Nothing();
379 auto format = usage->format;
381 GLint ret = 0;
382 switch (pname) {
383 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
384 ret = format->r;
385 break;
386 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
387 ret = format->g;
388 break;
389 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
390 ret = format->b;
391 break;
392 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
393 ret = format->a;
394 break;
395 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
396 ret = format->d;
397 break;
398 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
399 ret = format->s;
400 break;
402 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
403 ret = (format->isSRGB ? LOCAL_GL_SRGB : LOCAL_GL_LINEAR);
404 break;
406 case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
407 MOZ_ASSERT(attachment != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
409 if (format->unsizedFormat == webgl::UnsizedFormat::DEPTH_STENCIL) {
410 MOZ_ASSERT(attachment == LOCAL_GL_DEPTH_ATTACHMENT ||
411 attachment == LOCAL_GL_STENCIL_ATTACHMENT);
413 if (attachment == LOCAL_GL_DEPTH_ATTACHMENT) {
414 switch (format->effectiveFormat) {
415 case webgl::EffectiveFormat::DEPTH24_STENCIL8:
416 format =
417 webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT24);
418 break;
419 case webgl::EffectiveFormat::DEPTH32F_STENCIL8:
420 format =
421 webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT32F);
422 break;
423 default:
424 MOZ_ASSERT(false, "no matched DS format");
425 break;
427 } else if (attachment == LOCAL_GL_STENCIL_ATTACHMENT) {
428 switch (format->effectiveFormat) {
429 case webgl::EffectiveFormat::DEPTH24_STENCIL8:
430 case webgl::EffectiveFormat::DEPTH32F_STENCIL8:
431 format = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8);
432 break;
433 default:
434 MOZ_ASSERT(false, "no matched DS format");
435 break;
440 switch (format->componentType) {
441 case webgl::ComponentType::Int:
442 ret = LOCAL_GL_INT;
443 break;
444 case webgl::ComponentType::UInt:
445 ret = LOCAL_GL_UNSIGNED_INT;
446 break;
447 case webgl::ComponentType::NormInt:
448 ret = LOCAL_GL_SIGNED_NORMALIZED;
449 break;
450 case webgl::ComponentType::NormUInt:
451 ret = LOCAL_GL_UNSIGNED_NORMALIZED;
452 break;
453 case webgl::ComponentType::Float:
454 ret = LOCAL_GL_FLOAT;
455 break;
457 break;
459 default:
460 MOZ_ASSERT(false, "Missing case.");
461 break;
464 return Some(ret);
467 ////////////////////////////////////////////////////////////////////////////////
468 ////////////////////////////////////////////////////////////////////////////////
469 // WebGLFramebuffer
471 WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo)
472 : WebGLContextBoundObject(webgl),
473 mGLName(fbo),
474 mDepthAttachment(webgl, LOCAL_GL_DEPTH_ATTACHMENT),
475 mStencilAttachment(webgl, LOCAL_GL_STENCIL_ATTACHMENT),
476 mDepthStencilAttachment(webgl, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
477 mAttachments.push_back(&mDepthAttachment);
478 mAttachments.push_back(&mStencilAttachment);
480 if (!webgl->IsWebGL2()) {
481 // Only WebGL1 has a separate depth+stencil attachment point.
482 mAttachments.push_back(&mDepthStencilAttachment);
485 size_t i = 0;
486 for (auto& cur : mColorAttachments) {
487 new (&cur) WebGLFBAttachPoint(webgl, LOCAL_GL_COLOR_ATTACHMENT0 + i);
488 i++;
490 mAttachments.push_back(&cur);
493 mColorDrawBuffers.push_back(&mColorAttachments[0]);
494 mColorReadBuffer = &mColorAttachments[0];
497 WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl,
498 UniquePtr<gl::MozFramebuffer> fbo)
499 : WebGLContextBoundObject(webgl),
500 mGLName(fbo->mFB),
501 mOpaque(std::move(fbo)),
502 mColorReadBuffer(nullptr) {
503 // Opaque Framebuffer is guaranteed to be complete at this point.
504 // Cache the Completeness info.
505 CompletenessInfo info;
506 info.width = mOpaque->mSize.width;
507 info.height = mOpaque->mSize.height;
508 info.zLayerCount = 1;
509 info.isMultiview = false;
511 mCompletenessInfo = Some(std::move(info));
514 WebGLFramebuffer::~WebGLFramebuffer() {
515 InvalidateCaches();
517 mDepthAttachment.Clear();
518 mStencilAttachment.Clear();
519 mDepthStencilAttachment.Clear();
521 for (auto& cur : mColorAttachments) {
522 cur.Clear();
525 if (!mContext) return;
526 // If opaque, fDeleteFramebuffers is called in the destructor of
527 // MozFramebuffer.
528 if (!mOpaque) {
529 mContext->gl->fDeleteFramebuffers(1, &mGLName);
533 ////
535 Maybe<WebGLFBAttachPoint*> WebGLFramebuffer::GetColorAttachPoint(
536 GLenum attachPoint) {
537 if (attachPoint == LOCAL_GL_NONE) return Some<WebGLFBAttachPoint*>(nullptr);
539 if (attachPoint < LOCAL_GL_COLOR_ATTACHMENT0) return Nothing();
541 const size_t colorId = attachPoint - LOCAL_GL_COLOR_ATTACHMENT0;
543 MOZ_ASSERT(mContext->Limits().maxColorDrawBuffers <= webgl::kMaxDrawBuffers);
544 if (colorId >= mContext->MaxValidDrawBuffers()) return Nothing();
546 return Some(&mColorAttachments[colorId]);
549 Maybe<WebGLFBAttachPoint*> WebGLFramebuffer::GetAttachPoint(
550 GLenum attachPoint) {
551 switch (attachPoint) {
552 case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
553 return Some(&mDepthStencilAttachment);
555 case LOCAL_GL_DEPTH_ATTACHMENT:
556 return Some(&mDepthAttachment);
558 case LOCAL_GL_STENCIL_ATTACHMENT:
559 return Some(&mStencilAttachment);
561 default:
562 return GetColorAttachPoint(attachPoint);
566 void WebGLFramebuffer::DetachTexture(const WebGLTexture* tex) {
567 for (const auto& attach : mAttachments) {
568 if (attach->Texture() == tex) {
569 attach->Clear();
572 InvalidateCaches();
575 void WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb) {
576 for (const auto& attach : mAttachments) {
577 if (attach->Renderbuffer() == rb) {
578 attach->Clear();
581 InvalidateCaches();
584 ////////////////////////////////////////////////////////////////////////////////
585 // Completeness
587 bool WebGLFramebuffer::HasDuplicateAttachments() const {
588 std::set<WebGLFBAttachPoint::Ordered> uniqueAttachSet;
590 for (const auto& attach : mColorAttachments) {
591 if (!attach.HasAttachment()) continue;
593 const WebGLFBAttachPoint::Ordered ordered(attach);
595 const bool didInsert = uniqueAttachSet.insert(ordered).second;
596 if (!didInsert) return true;
599 return false;
602 bool WebGLFramebuffer::HasDefinedAttachments() const {
603 bool hasAttachments = false;
604 for (const auto& attach : mAttachments) {
605 hasAttachments |= attach->HasAttachment();
607 return hasAttachments;
610 bool WebGLFramebuffer::HasIncompleteAttachments(
611 nsCString* const out_info) const {
612 bool hasIncomplete = false;
613 for (const auto& cur : mAttachments) {
614 if (!cur->HasAttachment())
615 continue; // Not defined, so can't count as incomplete.
617 hasIncomplete |= !cur->IsComplete(mContext, out_info);
619 return hasIncomplete;
622 bool WebGLFramebuffer::AllImageRectsMatch() const {
623 MOZ_ASSERT(HasDefinedAttachments());
624 DebugOnly<nsCString> fbStatusInfo;
625 MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo));
627 bool needsInit = true;
628 uint32_t width = 0;
629 uint32_t height = 0;
631 bool hasMismatch = false;
632 for (const auto& attach : mAttachments) {
633 const auto& imageInfo = attach->GetImageInfo();
634 if (!imageInfo) continue;
636 const auto& curWidth = imageInfo->mWidth;
637 const auto& curHeight = imageInfo->mHeight;
639 if (needsInit) {
640 needsInit = false;
641 width = curWidth;
642 height = curHeight;
643 continue;
646 hasMismatch |= (curWidth != width || curHeight != height);
648 return !hasMismatch;
651 bool WebGLFramebuffer::AllImageSamplesMatch() const {
652 MOZ_ASSERT(HasDefinedAttachments());
653 DebugOnly<nsCString> fbStatusInfo;
654 MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo));
656 bool needsInit = true;
657 uint32_t samples = 0;
659 bool hasMismatch = false;
660 for (const auto& attach : mAttachments) {
661 const auto& imageInfo = attach->GetImageInfo();
662 if (!imageInfo) continue;
664 const auto& curSamples = imageInfo->mSamples;
666 if (needsInit) {
667 needsInit = false;
668 samples = curSamples;
669 continue;
672 hasMismatch |= (curSamples != samples);
674 return !hasMismatch;
677 FBStatus WebGLFramebuffer::PrecheckFramebufferStatus(
678 nsCString* const out_info) const {
679 MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
680 mContext->mBoundReadFramebuffer == this);
681 if (!HasDefinedAttachments())
682 return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT; // No
683 // attachments
685 if (HasIncompleteAttachments(out_info))
686 return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
688 if (!AllImageRectsMatch())
689 return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS; // Inconsistent sizes
691 if (!AllImageSamplesMatch())
692 return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE; // Inconsistent samples
694 if (HasDuplicateAttachments()) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
696 if (mContext->IsWebGL2()) {
697 MOZ_ASSERT(!mDepthStencilAttachment.HasAttachment());
698 if (mDepthAttachment.HasAttachment() &&
699 mStencilAttachment.HasAttachment()) {
700 if (!mDepthAttachment.IsEquivalentForFeedback(mStencilAttachment))
701 return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
703 } else {
704 const auto depthOrStencilCount =
705 int(mDepthAttachment.HasAttachment()) +
706 int(mStencilAttachment.HasAttachment()) +
707 int(mDepthStencilAttachment.HasAttachment());
708 if (depthOrStencilCount > 1) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
712 const WebGLFBAttachPoint* example = nullptr;
713 for (const auto& x : mAttachments) {
714 if (!x->HasAttachment()) continue;
715 if (!example) {
716 example = x;
717 continue;
719 if (x->ZLayerCount() != example->ZLayerCount()) {
720 return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR;
725 return LOCAL_GL_FRAMEBUFFER_COMPLETE;
728 ////////////////////////////////////////
729 // Validation
731 bool WebGLFramebuffer::ValidateAndInitAttachments(
732 const GLenum incompleteFbError) const {
733 MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
734 mContext->mBoundReadFramebuffer == this);
736 const auto fbStatus = CheckFramebufferStatus();
737 if (fbStatus == LOCAL_GL_FRAMEBUFFER_COMPLETE) return true;
739 mContext->GenerateError(incompleteFbError, "Framebuffer must be complete.");
740 return false;
743 bool WebGLFramebuffer::ValidateClearBufferType(
744 GLenum buffer, uint32_t drawBuffer,
745 const webgl::AttribBaseType funcType) const {
746 if (buffer != LOCAL_GL_COLOR) return true;
748 const auto& attach = mColorAttachments[drawBuffer];
749 const auto& imageInfo = attach.GetImageInfo();
750 if (!imageInfo) return true;
752 if (!count(mColorDrawBuffers.begin(), mColorDrawBuffers.end(), &attach))
753 return true; // DRAW_BUFFERi set to NONE.
755 auto attachType = webgl::AttribBaseType::Float;
756 switch (imageInfo->mFormat->format->componentType) {
757 case webgl::ComponentType::Int:
758 attachType = webgl::AttribBaseType::Int;
759 break;
760 case webgl::ComponentType::UInt:
761 attachType = webgl::AttribBaseType::Uint;
762 break;
763 default:
764 break;
767 if (attachType != funcType) {
768 mContext->ErrorInvalidOperation(
769 "This attachment is of type %s, but"
770 " this function is of type %s.",
771 ToString(attachType), ToString(funcType));
772 return false;
775 return true;
778 bool WebGLFramebuffer::ValidateForColorRead(
779 const webgl::FormatUsageInfo** const out_format, uint32_t* const out_width,
780 uint32_t* const out_height) const {
781 if (!mColorReadBuffer) {
782 mContext->ErrorInvalidOperation("READ_BUFFER must not be NONE.");
783 return false;
786 if (mColorReadBuffer->ZLayerCount() > 1) {
787 mContext->GenerateError(LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION,
788 "The READ_BUFFER attachment has multiple views.");
789 return false;
792 const auto& imageInfo = mColorReadBuffer->GetImageInfo();
793 if (!imageInfo) {
794 mContext->ErrorInvalidOperation(
795 "The READ_BUFFER attachment is not defined.");
796 return false;
799 if (imageInfo->mSamples) {
800 mContext->ErrorInvalidOperation(
801 "The READ_BUFFER attachment is multisampled.");
802 return false;
805 *out_format = imageInfo->mFormat;
806 *out_width = imageInfo->mWidth;
807 *out_height = imageInfo->mHeight;
808 return true;
811 ////////////////////////////////////////////////////////////////////////////////
812 // Resolution and caching
814 void WebGLFramebuffer::DoDeferredAttachments() const {
815 if (mContext->IsWebGL2()) return;
817 const auto& gl = mContext->gl;
818 gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
819 LOCAL_GL_RENDERBUFFER, 0);
820 gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
821 LOCAL_GL_STENCIL_ATTACHMENT,
822 LOCAL_GL_RENDERBUFFER, 0);
824 const auto fn = [&](const WebGLFBAttachPoint& attach) {
825 MOZ_ASSERT(attach.mDeferAttachment);
826 if (attach.HasAttachment()) {
827 attach.DoAttachment(gl);
830 // Only one of these will have an attachment.
831 fn(mDepthAttachment);
832 fn(mStencilAttachment);
833 fn(mDepthStencilAttachment);
836 void WebGLFramebuffer::ResolveAttachmentData() const {
837 // GLES 3.0.5 p188:
838 // The result of clearing integer color buffers with `Clear` is undefined.
840 // Two different approaches:
841 // On WebGL 2, we have glClearBuffer, and *must* use it for integer buffers,
842 // so let's just use it for all the buffers. One WebGL 1, we might not have
843 // glClearBuffer,
845 // WebGL 1 is easier, because we can just call glClear, possibly with
846 // glDrawBuffers.
848 const auto& gl = mContext->gl;
850 const webgl::ScopedPrepForResourceClear scopedPrep(*mContext);
852 if (mContext->IsWebGL2()) {
853 const uint32_t uiZeros[4] = {};
854 const int32_t iZeros[4] = {};
855 const float fZeros[4] = {};
856 const float fOne[] = {1.0f};
858 for (const auto& cur : mAttachments) {
859 const auto& imageInfo = cur->GetImageInfo();
860 if (!imageInfo || !imageInfo->mUninitializedSlices)
861 continue; // Nothing attached, or already has data.
863 const auto fnClearBuffer = [&]() {
864 const auto& format = imageInfo->mFormat->format;
865 MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(uiZeros));
866 MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(iZeros));
867 MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(fZeros));
869 switch (cur->mAttachmentPoint) {
870 case LOCAL_GL_DEPTH_ATTACHMENT:
871 gl->fClearBufferfv(LOCAL_GL_DEPTH, 0, fOne);
872 break;
873 case LOCAL_GL_STENCIL_ATTACHMENT:
874 gl->fClearBufferiv(LOCAL_GL_STENCIL, 0, iZeros);
875 break;
876 default:
877 MOZ_ASSERT(cur->mAttachmentPoint !=
878 LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
879 const uint32_t drawBuffer =
880 cur->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0;
881 MOZ_ASSERT(drawBuffer <= 100);
882 switch (format->componentType) {
883 case webgl::ComponentType::Int:
884 gl->fClearBufferiv(LOCAL_GL_COLOR, drawBuffer, iZeros);
885 break;
886 case webgl::ComponentType::UInt:
887 gl->fClearBufferuiv(LOCAL_GL_COLOR, drawBuffer, uiZeros);
888 break;
889 default:
890 gl->fClearBufferfv(LOCAL_GL_COLOR, drawBuffer, fZeros);
891 break;
896 if (imageInfo->mDepth > 1) {
897 const auto& tex = cur->Texture();
898 const gl::ScopedFramebuffer scopedFB(gl);
899 const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB());
900 for (const auto z : IntegerRange(imageInfo->mDepth)) {
901 if ((*imageInfo->mUninitializedSlices)[z]) {
902 gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER,
903 cur->mAttachmentPoint, tex->mGLName,
904 cur->MipLevel(), z);
905 fnClearBuffer();
908 } else {
909 fnClearBuffer();
911 imageInfo->mUninitializedSlices = Nothing();
913 return;
916 uint32_t clearBits = 0;
917 std::vector<GLenum> drawBufferForClear;
919 const auto fnGather = [&](const WebGLFBAttachPoint& attach,
920 const uint32_t attachClearBits) {
921 const auto& imageInfo = attach.GetImageInfo();
922 if (!imageInfo || !imageInfo->mUninitializedSlices) return false;
924 clearBits |= attachClearBits;
925 imageInfo->mUninitializedSlices = Nothing(); // Just mark it now.
926 return true;
929 //////
931 for (const auto& cur : mColorAttachments) {
932 if (fnGather(cur, LOCAL_GL_COLOR_BUFFER_BIT)) {
933 const uint32_t id = cur.mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0;
934 MOZ_ASSERT(id <= 100);
935 drawBufferForClear.resize(id + 1); // Pads with zeros!
936 drawBufferForClear[id] = cur.mAttachmentPoint;
940 (void)fnGather(mDepthAttachment, LOCAL_GL_DEPTH_BUFFER_BIT);
941 (void)fnGather(mStencilAttachment, LOCAL_GL_STENCIL_BUFFER_BIT);
942 (void)fnGather(mDepthStencilAttachment,
943 LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT);
945 //////
947 if (!clearBits) return;
949 if (gl->IsSupported(gl::GLFeature::draw_buffers)) {
950 gl->fDrawBuffers(drawBufferForClear.size(), drawBufferForClear.data());
953 gl->fClear(clearBits);
955 RefreshDrawBuffers();
958 WebGLFramebuffer::CompletenessInfo::~CompletenessInfo() {
959 if (!this->fb) return;
960 const auto& fb = *this->fb;
961 const auto& webgl = fb.mContext;
962 fb.mNumFBStatusInvals++;
963 if (fb.mNumFBStatusInvals > webgl->mMaxAcceptableFBStatusInvals) {
964 webgl->GeneratePerfWarning(
965 "FB was invalidated after being complete %u"
966 " times. [webgl.perf.max-acceptable-fb-status-invals]",
967 uint32_t(fb.mNumFBStatusInvals));
971 ////////////////////////////////////////////////////////////////////////////////
972 // Entrypoints
974 FBStatus WebGLFramebuffer::CheckFramebufferStatus() const {
975 if (MOZ_UNLIKELY(mOpaque && !mInOpaqueRAF)) {
976 // Opaque Framebuffers are considered incomplete outside of a RAF.
977 return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
980 if (mCompletenessInfo) return LOCAL_GL_FRAMEBUFFER_COMPLETE;
982 // Ok, let's try to resolve it!
984 nsCString statusInfo;
985 FBStatus ret = PrecheckFramebufferStatus(&statusInfo);
986 do {
987 if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE) break;
989 // Looks good on our end. Let's ask the driver.
990 gl::GLContext* const gl = mContext->gl;
992 const ScopedFBRebinder autoFB(mContext);
993 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLName);
995 ////
997 DoDeferredAttachments();
998 RefreshDrawBuffers();
999 RefreshReadBuffer();
1001 ret = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
1003 ////
1005 if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
1006 const nsPrintfCString text("Bad status according to the driver: 0x%04x",
1007 ret.get());
1008 statusInfo = text;
1009 break;
1012 ResolveAttachmentData();
1014 // Sweet, let's cache that.
1015 auto info = CompletenessInfo{this};
1016 mCompletenessInfo.ResetInvalidators({});
1017 mCompletenessInfo.AddInvalidator(*this);
1019 const auto fnIsFloat32 = [](const webgl::FormatInfo& info) {
1020 if (info.componentType != webgl::ComponentType::Float) return false;
1021 return info.r == 32;
1024 for (const auto& cur : mAttachments) {
1025 const auto& tex = cur->Texture();
1026 const auto& rb = cur->Renderbuffer();
1027 if (tex) {
1028 mCompletenessInfo.AddInvalidator(*tex);
1029 info.texAttachments.push_back(cur);
1030 } else if (rb) {
1031 mCompletenessInfo.AddInvalidator(*rb);
1032 } else {
1033 continue;
1035 const auto& imageInfo = cur->GetImageInfo();
1036 MOZ_ASSERT(imageInfo);
1038 const auto maybeColorId = cur->ColorAttachmentId();
1039 if (maybeColorId) {
1040 const auto id = *maybeColorId;
1041 info.hasAttachment[id] = true;
1042 info.isAttachmentF32[id] = fnIsFloat32(*imageInfo->mFormat->format);
1045 info.width = imageInfo->mWidth;
1046 info.height = imageInfo->mHeight;
1047 info.zLayerCount = cur->ZLayerCount();
1048 info.isMultiview = cur->IsMultiview();
1050 MOZ_ASSERT(info.width && info.height);
1051 mCompletenessInfo = Some(std::move(info));
1052 info.fb = nullptr; // Don't trigger the invalidation warning.
1053 return LOCAL_GL_FRAMEBUFFER_COMPLETE;
1054 } while (false);
1056 MOZ_ASSERT(ret != LOCAL_GL_FRAMEBUFFER_COMPLETE);
1057 mContext->GenerateWarning("Framebuffer not complete. (status: 0x%04x) %s",
1058 ret.get(), statusInfo.BeginReading());
1059 return ret;
1062 ////
1064 void WebGLFramebuffer::RefreshDrawBuffers() const {
1065 const auto& gl = mContext->gl;
1066 if (!gl->IsSupported(gl::GLFeature::draw_buffers)) return;
1068 // Prior to GL4.1, having a no-image FB attachment that's selected by
1069 // DrawBuffers yields a framebuffer status of
1070 // FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER. We could workaround this only on
1071 // affected versions, but it's easier be unconditional.
1072 std::vector<GLenum> driverBuffers(mContext->Limits().maxColorDrawBuffers,
1073 LOCAL_GL_NONE);
1074 for (const auto& attach : mColorDrawBuffers) {
1075 if (attach->HasAttachment()) {
1076 const uint32_t index =
1077 attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0;
1078 driverBuffers[index] = attach->mAttachmentPoint;
1082 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, mGLName);
1083 gl->fDrawBuffers(driverBuffers.size(), driverBuffers.data());
1086 void WebGLFramebuffer::RefreshReadBuffer() const {
1087 const auto& gl = mContext->gl;
1088 if (!gl->IsSupported(gl::GLFeature::read_buffer)) return;
1090 // Prior to GL4.1, having a no-image FB attachment that's selected by
1091 // ReadBuffer yields a framebuffer status of
1092 // FRAMEBUFFER_INCOMPLETE_READ_BUFFER. We could workaround this only on
1093 // affected versions, but it's easier be unconditional.
1094 GLenum driverBuffer = LOCAL_GL_NONE;
1095 if (mColorReadBuffer && mColorReadBuffer->HasAttachment()) {
1096 driverBuffer = mColorReadBuffer->mAttachmentPoint;
1099 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mGLName);
1100 gl->fReadBuffer(driverBuffer);
1103 ////
1105 void WebGLFramebuffer::DrawBuffers(const std::vector<GLenum>& buffers) {
1106 if (buffers.size() > mContext->MaxValidDrawBuffers()) {
1107 // "An INVALID_VALUE error is generated if `n` is greater than
1108 // MAX_DRAW_BUFFERS."
1109 mContext->ErrorInvalidValue(
1110 "`buffers` must have a length <="
1111 " MAX_DRAW_BUFFERS.");
1112 return;
1115 std::vector<const WebGLFBAttachPoint*> newColorDrawBuffers;
1116 newColorDrawBuffers.reserve(buffers.size());
1118 mDrawBufferEnabled.reset();
1119 for (const auto i : IntegerRange(buffers.size())) {
1120 // "If the GL is bound to a draw framebuffer object, the `i`th buffer listed
1121 // in bufs must be COLOR_ATTACHMENTi or NONE. Specifying a buffer out of
1122 // order, BACK, or COLOR_ATTACHMENTm where `m` is greater than or equal to
1123 // the value of MAX_COLOR_ATTACHMENTS, will generate the error
1124 // INVALID_OPERATION.
1126 // WEBGL_draw_buffers:
1127 // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater
1128 // than or equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter." This
1129 // means that if buffers.Length() isn't larger than MaxDrawBuffers, it won't
1130 // be larger than MaxColorAttachments.
1131 const auto& cur = buffers[i];
1132 if (cur == LOCAL_GL_COLOR_ATTACHMENT0 + i) {
1133 const auto& attach = mColorAttachments[i];
1134 newColorDrawBuffers.push_back(&attach);
1135 mDrawBufferEnabled[i] = true;
1136 } else if (cur != LOCAL_GL_NONE) {
1137 const bool isColorEnum = (cur >= LOCAL_GL_COLOR_ATTACHMENT0 &&
1138 cur < mContext->LastColorAttachmentEnum());
1139 if (cur != LOCAL_GL_BACK && !isColorEnum) {
1140 mContext->ErrorInvalidEnum("Unexpected enum in buffers.");
1141 return;
1144 mContext->ErrorInvalidOperation(
1145 "`buffers[i]` must be NONE or"
1146 " COLOR_ATTACHMENTi.");
1147 return;
1151 ////
1153 mColorDrawBuffers = std::move(newColorDrawBuffers);
1154 RefreshDrawBuffers(); // Calls glDrawBuffers.
1157 void WebGLFramebuffer::ReadBuffer(GLenum attachPoint) {
1158 const auto& maybeAttach = GetColorAttachPoint(attachPoint);
1159 if (!maybeAttach) {
1160 const char text[] =
1161 "`mode` must be a COLOR_ATTACHMENTi, for 0 <= i <"
1162 " MAX_DRAW_BUFFERS.";
1163 if (attachPoint == LOCAL_GL_BACK) {
1164 mContext->ErrorInvalidOperation(text);
1165 } else {
1166 mContext->ErrorInvalidEnum(text);
1168 return;
1170 const auto& attach = maybeAttach.value(); // Might be nullptr.
1172 ////
1174 mColorReadBuffer = attach;
1175 RefreshReadBuffer(); // Calls glReadBuffer.
1178 ////
1180 bool WebGLFramebuffer::FramebufferAttach(const GLenum attachEnum,
1181 const webgl::FbAttachInfo& toAttach) {
1182 MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
1183 mContext->mBoundReadFramebuffer == this);
1185 if (MOZ_UNLIKELY(mOpaque)) {
1186 // An opaque framebuffer's attachments cannot be inspected or changed.
1187 return false;
1190 // `attachment`
1191 const auto maybeAttach = GetAttachPoint(attachEnum);
1192 if (!maybeAttach || !maybeAttach.value()) return false;
1193 const auto& attach = maybeAttach.value();
1195 const auto& gl = mContext->gl;
1196 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLName);
1197 if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
1198 mDepthAttachment.Set(gl, toAttach);
1199 mStencilAttachment.Set(gl, toAttach);
1200 } else {
1201 attach->Set(gl, toAttach);
1203 InvalidateCaches();
1204 return true;
1207 Maybe<double> WebGLFramebuffer::GetAttachmentParameter(GLenum attachEnum,
1208 GLenum pname) {
1209 const auto maybeAttach = GetAttachPoint(attachEnum);
1210 if (!maybeAttach || attachEnum == LOCAL_GL_NONE) {
1211 mContext->ErrorInvalidEnum(
1212 "Can only query COLOR_ATTACHMENTi,"
1213 " DEPTH_ATTACHMENT, DEPTH_STENCIL_ATTACHMENT, or"
1214 " STENCIL_ATTACHMENT for a framebuffer.");
1215 return Nothing();
1217 if (MOZ_UNLIKELY(mOpaque)) {
1218 mContext->ErrorInvalidOperation(
1219 "An opaque framebuffer's attachments cannot be inspected or changed.");
1220 return Nothing();
1222 auto attach = maybeAttach.value();
1224 if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
1225 // There are a couple special rules for this one.
1227 if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE) {
1228 mContext->ErrorInvalidOperation(
1229 "Querying"
1230 " FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE"
1231 " against DEPTH_STENCIL_ATTACHMENT is an"
1232 " error.");
1233 return Nothing();
1236 if (mDepthAttachment.Renderbuffer() != mStencilAttachment.Renderbuffer() ||
1237 mDepthAttachment.Texture() != mStencilAttachment.Texture()) {
1238 mContext->ErrorInvalidOperation(
1239 "DEPTH_ATTACHMENT and STENCIL_ATTACHMENT"
1240 " have different objects bound.");
1241 return Nothing();
1244 attach = &mDepthAttachment;
1247 return attach->GetParameter(mContext, attachEnum, pname);
1250 ////////////////////
1252 static void GetBackbufferFormats(const WebGLContext* webgl,
1253 const webgl::FormatInfo** const out_color,
1254 const webgl::FormatInfo** const out_depth,
1255 const webgl::FormatInfo** const out_stencil) {
1256 const auto& options = webgl->Options();
1258 const auto effFormat = (options.alpha ? webgl::EffectiveFormat::RGBA8
1259 : webgl::EffectiveFormat::RGB8);
1260 *out_color = webgl::GetFormat(effFormat);
1262 *out_depth = nullptr;
1263 *out_stencil = nullptr;
1264 if (options.depth && options.stencil) {
1265 *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH24_STENCIL8);
1266 *out_stencil = *out_depth;
1267 } else {
1268 if (options.depth) {
1269 *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT16);
1271 if (options.stencil) {
1272 *out_stencil = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8);
1277 /*static*/
1278 void WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl, GLint _srcX0,
1279 GLint _srcY0, GLint _srcX1, GLint _srcY1,
1280 GLint _dstX0, GLint _dstY0, GLint _dstX1,
1281 GLint _dstY1, GLbitfield mask,
1282 GLenum filter) {
1283 auto srcP0 = ivec2{_srcX0, _srcY0};
1284 auto srcP1 = ivec2{_srcX1, _srcY1};
1285 auto dstP0 = ivec2{_dstX0, _dstY0};
1286 auto dstP1 = ivec2{_dstX1, _dstY1};
1288 const GLbitfield depthAndStencilBits =
1289 LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT;
1290 if (bool(mask & depthAndStencilBits) && filter == LOCAL_GL_LINEAR) {
1291 webgl->ErrorInvalidOperation(
1292 "DEPTH_BUFFER_BIT and STENCIL_BUFFER_BIT can"
1293 " only be used with NEAREST filtering.");
1294 return;
1297 const auto& srcFB = webgl->mBoundReadFramebuffer;
1298 const auto& dstFB = webgl->mBoundDrawFramebuffer;
1300 ////
1301 // Collect data
1303 const auto fnGetFormat =
1304 [](const WebGLFBAttachPoint& cur,
1305 bool* const out_hasSamples) -> const webgl::FormatInfo* {
1306 const auto& imageInfo = cur.GetImageInfo();
1307 if (!imageInfo) return nullptr; // No attachment.
1308 *out_hasSamples = bool(imageInfo->mSamples);
1309 return imageInfo->mFormat->format;
1312 bool srcHasSamples = false;
1313 bool srcIsFilterable = true;
1314 const webgl::FormatInfo* srcColorFormat;
1315 const webgl::FormatInfo* srcDepthFormat;
1316 const webgl::FormatInfo* srcStencilFormat;
1317 gfx::IntSize srcSize;
1319 if (srcFB) {
1320 const auto& info = *srcFB->GetCompletenessInfo();
1321 if (info.zLayerCount != 1) {
1322 webgl->GenerateError(
1323 LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION,
1324 "Source framebuffer cannot have more than one multiview layer.");
1325 return;
1327 srcColorFormat = nullptr;
1328 if (srcFB->mColorReadBuffer) {
1329 const auto& imageInfo = srcFB->mColorReadBuffer->GetImageInfo();
1330 if (imageInfo) {
1331 srcIsFilterable &= imageInfo->mFormat->isFilterable;
1333 srcColorFormat = fnGetFormat(*(srcFB->mColorReadBuffer), &srcHasSamples);
1335 srcDepthFormat = fnGetFormat(srcFB->DepthAttachment(), &srcHasSamples);
1336 srcStencilFormat = fnGetFormat(srcFB->StencilAttachment(), &srcHasSamples);
1337 MOZ_ASSERT(!srcFB->DepthStencilAttachment().HasAttachment());
1338 srcSize = {info.width, info.height};
1339 } else {
1340 srcHasSamples = false; // Always false.
1342 GetBackbufferFormats(webgl, &srcColorFormat, &srcDepthFormat,
1343 &srcStencilFormat);
1344 const auto& size = webgl->DrawingBufferSize();
1345 srcSize = {size.x, size.y};
1348 ////
1350 bool dstHasSamples = false;
1351 const webgl::FormatInfo* dstDepthFormat;
1352 const webgl::FormatInfo* dstStencilFormat;
1353 bool dstHasColor = false;
1354 bool colorFormatsMatch = true;
1355 bool colorTypesMatch = true;
1356 bool colorSrgbMatches = true;
1357 gfx::IntSize dstSize;
1359 const auto fnCheckColorFormat = [&](const webgl::FormatInfo* dstFormat) {
1360 MOZ_ASSERT(dstFormat->r || dstFormat->g || dstFormat->b || dstFormat->a);
1361 dstHasColor = true;
1362 colorFormatsMatch &= (dstFormat == srcColorFormat);
1363 colorTypesMatch &=
1364 srcColorFormat && (dstFormat->baseType == srcColorFormat->baseType);
1365 colorSrgbMatches &=
1366 srcColorFormat && (dstFormat->isSRGB == srcColorFormat->isSRGB);
1369 if (dstFB) {
1370 for (const auto& cur : dstFB->mColorDrawBuffers) {
1371 const auto& format = fnGetFormat(*cur, &dstHasSamples);
1372 if (!format) continue;
1374 fnCheckColorFormat(format);
1377 dstDepthFormat = fnGetFormat(dstFB->DepthAttachment(), &dstHasSamples);
1378 dstStencilFormat = fnGetFormat(dstFB->StencilAttachment(), &dstHasSamples);
1379 MOZ_ASSERT(!dstFB->DepthStencilAttachment().HasAttachment());
1381 const auto& info = *dstFB->GetCompletenessInfo();
1382 if (info.isMultiview) {
1383 webgl->GenerateError(
1384 LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION,
1385 "Destination framebuffer cannot have multiview attachments.");
1386 return;
1388 dstSize = {info.width, info.height};
1389 } else {
1390 dstHasSamples = webgl->Options().antialias;
1392 const webgl::FormatInfo* dstColorFormat;
1393 GetBackbufferFormats(webgl, &dstColorFormat, &dstDepthFormat,
1394 &dstStencilFormat);
1396 fnCheckColorFormat(dstColorFormat);
1398 const auto& size = webgl->DrawingBufferSize();
1399 dstSize = {size.x, size.y};
1402 ////
1403 // Clear unused buffer bits
1405 if (mask & LOCAL_GL_COLOR_BUFFER_BIT && !srcColorFormat && !dstHasColor) {
1406 mask ^= LOCAL_GL_COLOR_BUFFER_BIT;
1409 if (mask & LOCAL_GL_DEPTH_BUFFER_BIT && !srcDepthFormat && !dstDepthFormat) {
1410 mask ^= LOCAL_GL_DEPTH_BUFFER_BIT;
1413 if (mask & LOCAL_GL_STENCIL_BUFFER_BIT && !srcStencilFormat &&
1414 !dstStencilFormat) {
1415 mask ^= LOCAL_GL_STENCIL_BUFFER_BIT;
1418 ////
1419 // Validation
1421 if (dstHasSamples) {
1422 webgl->ErrorInvalidOperation(
1423 "DRAW_FRAMEBUFFER may not have multiple"
1424 " samples.");
1425 return;
1428 bool requireFilterable = (filter == LOCAL_GL_LINEAR);
1429 if (srcHasSamples) {
1430 requireFilterable = false; // It picks one.
1432 if (mask & LOCAL_GL_COLOR_BUFFER_BIT && dstHasColor && !colorFormatsMatch) {
1433 webgl->ErrorInvalidOperation(
1434 "Color buffer formats must match if"
1435 " selected, when reading from a multisampled"
1436 " source.");
1437 return;
1440 if (srcP0 != dstP0 || srcP1 != dstP1) {
1441 webgl->ErrorInvalidOperation(
1442 "If the source is multisampled, then the"
1443 " source and dest regions must match exactly.");
1444 return;
1448 // -
1450 if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
1451 if (requireFilterable && !srcIsFilterable) {
1452 webgl->ErrorInvalidOperation(
1453 "`filter` is LINEAR and READ_BUFFER"
1454 " contains integer data.");
1455 return;
1458 if (!colorTypesMatch) {
1459 webgl->ErrorInvalidOperation(
1460 "Color component types (float/uint/"
1461 "int) must match.");
1462 return;
1466 /* GLES 3.0.4, p199:
1467 * Calling BlitFramebuffer will result in an INVALID_OPERATION error if
1468 * mask includes DEPTH_BUFFER_BIT or STENCIL_BUFFER_BIT, and the source
1469 * and destination depth and stencil buffer formats do not match.
1471 * jgilbert: The wording is such that if only DEPTH_BUFFER_BIT is specified,
1472 * the stencil formats must match. This seems wrong. It could be a spec bug,
1473 * or I could be missing an interaction in one of the earlier paragraphs.
1475 if (mask & LOCAL_GL_DEPTH_BUFFER_BIT && dstDepthFormat &&
1476 dstDepthFormat != srcDepthFormat) {
1477 webgl->ErrorInvalidOperation(
1478 "Depth buffer formats must match if selected.");
1479 return;
1482 if (mask & LOCAL_GL_STENCIL_BUFFER_BIT && dstStencilFormat &&
1483 dstStencilFormat != srcStencilFormat) {
1484 webgl->ErrorInvalidOperation(
1485 "Stencil buffer formats must match if selected.");
1486 return;
1489 ////
1490 // Check for feedback
1492 if (srcFB && dstFB) {
1493 const WebGLFBAttachPoint* feedback = nullptr;
1495 if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
1496 MOZ_ASSERT(srcFB->mColorReadBuffer->HasAttachment());
1497 for (const auto& cur : dstFB->mColorDrawBuffers) {
1498 if (srcFB->mColorReadBuffer->IsEquivalentForFeedback(*cur)) {
1499 feedback = cur;
1500 break;
1505 if (mask & LOCAL_GL_DEPTH_BUFFER_BIT &&
1506 srcFB->DepthAttachment().IsEquivalentForFeedback(
1507 dstFB->DepthAttachment())) {
1508 feedback = &dstFB->DepthAttachment();
1511 if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
1512 srcFB->StencilAttachment().IsEquivalentForFeedback(
1513 dstFB->StencilAttachment())) {
1514 feedback = &dstFB->StencilAttachment();
1517 if (feedback) {
1518 webgl->ErrorInvalidOperation(
1519 "Feedback detected into DRAW_FRAMEBUFFER's"
1520 " 0x%04x attachment.",
1521 feedback->mAttachmentPoint);
1522 return;
1524 } else if (!srcFB && !dstFB) {
1525 webgl->ErrorInvalidOperation("Feedback with default framebuffer.");
1526 return;
1529 // -
1530 // Mutually constrain src and dst rects for eldritch blits.
1532 [&] {
1533 using fvec2 = avec2<float>; // Switch to float, because there's no perfect
1534 // solution anyway.
1536 const auto zero2f = fvec2{0, 0};
1537 const auto srcSizef = AsVec(srcSize).StaticCast<fvec2>();
1538 const auto dstSizef = AsVec(dstSize).StaticCast<fvec2>();
1540 const auto srcP0f = srcP0.StaticCast<fvec2>();
1541 const auto srcP1f = srcP1.StaticCast<fvec2>();
1542 const auto dstP0f = dstP0.StaticCast<fvec2>();
1543 const auto dstP1f = dstP1.StaticCast<fvec2>();
1545 const auto srcRectDiff = srcP1f - srcP0f;
1546 const auto dstRectDiff = dstP1f - dstP0f;
1548 // Skip if zero-sized.
1549 if (!srcRectDiff.x || !srcRectDiff.y || !dstRectDiff.x || !dstRectDiff.y) {
1550 srcP0 = srcP1 = dstP0 = dstP1 = {0, 0};
1551 return;
1554 // Clamp the rect points
1555 const auto srcQ0 = srcP0f.ClampMinMax(zero2f, srcSizef);
1556 const auto srcQ1 = srcP1f.ClampMinMax(zero2f, srcSizef);
1558 // Normalized to the [0,1] abstact copy rect
1559 const auto srcQ0Norm = (srcQ0 - srcP0f) / srcRectDiff;
1560 const auto srcQ1Norm = (srcQ1 - srcP0f) / srcRectDiff;
1562 // Map into dst
1563 const auto srcQ0InDst = dstP0f + srcQ0Norm * dstRectDiff;
1564 const auto srcQ1InDst = dstP0f + srcQ1Norm * dstRectDiff;
1566 // Clamp the rect points
1567 const auto dstQ0 = srcQ0InDst.ClampMinMax(zero2f, dstSizef);
1568 const auto dstQ1 = srcQ1InDst.ClampMinMax(zero2f, dstSizef);
1570 // Alright, time to go back to src!
1571 // Normalized to the [0,1] abstact copy rect
1572 const auto dstQ0Norm = (dstQ0 - dstP0f) / dstRectDiff;
1573 const auto dstQ1Norm = (dstQ1 - dstP0f) / dstRectDiff;
1575 // Map into src
1576 const auto dstQ0InSrc = srcP0f + dstQ0Norm * srcRectDiff;
1577 const auto dstQ1InSrc = srcP0f + dstQ1Norm * srcRectDiff;
1579 const auto srcQ0Constrained = dstQ0InSrc.ClampMinMax(zero2f, srcSizef);
1580 const auto srcQ1Constrained = dstQ1InSrc.ClampMinMax(zero2f, srcSizef);
1582 // Round, don't floor:
1583 srcP0 = (srcQ0Constrained + 0.5).StaticCast<ivec2>();
1584 srcP1 = (srcQ1Constrained + 0.5).StaticCast<ivec2>();
1585 dstP0 = (dstQ0 + 0.5).StaticCast<ivec2>();
1586 dstP1 = (dstQ1 + 0.5).StaticCast<ivec2>();
1587 }();
1589 bool inBounds = true;
1590 inBounds &= (srcP0 == srcP0.Clamp({0, 0}, AsVec(srcSize)));
1591 inBounds &= (srcP1 == srcP1.Clamp({0, 0}, AsVec(srcSize)));
1592 inBounds &= (dstP0 == dstP0.Clamp({0, 0}, AsVec(dstSize)));
1593 inBounds &= (dstP1 == dstP1.Clamp({0, 0}, AsVec(dstSize)));
1594 if (!inBounds) {
1595 webgl->ErrorImplementationBug(
1596 "Subrects still not within src and dst after constraining.");
1597 return;
1600 // -
1601 // Execute as constrained
1603 const auto& gl = webgl->gl;
1604 const ScopedDrawCallWrapper wrapper(*webgl);
1606 gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x, dstP0.y,
1607 dstP1.x, dstP1.y, mask, filter);
1609 // -
1611 if (mask & LOCAL_GL_COLOR_BUFFER_BIT && !colorSrgbMatches && !gl->IsGLES() &&
1612 gl->Version() < 440) {
1613 // Mostly for Mac.
1614 // Remember, we have to filter in the *linear* format blit.
1616 // src -Blit-> fbB -DrawBlit-> fbC -Blit-> dst
1618 const auto fbB = gl::MozFramebuffer::Create(gl, {1, 1}, 0, false);
1619 const auto fbC = gl::MozFramebuffer::Create(gl, {1, 1}, 0, false);
1621 // -
1623 auto sizeBC = srcSize;
1624 GLenum formatC = LOCAL_GL_RGBA8;
1625 if (srcColorFormat->isSRGB) {
1626 // srgb -> linear
1627 } else {
1628 // linear -> srgb
1629 sizeBC = dstSize;
1630 formatC = LOCAL_GL_SRGB8_ALPHA8;
1633 const auto fnSetTex = [&](const gl::MozFramebuffer& fb,
1634 const GLenum format) {
1635 const gl::ScopedBindTexture bindTex(gl, fb.ColorTex());
1636 gl->fTexStorage2D(LOCAL_GL_TEXTURE_2D, 1, format, sizeBC.width,
1637 sizeBC.height);
1639 fnSetTex(*fbB, srcColorFormat->sizedFormat);
1640 fnSetTex(*fbC, formatC);
1642 // -
1645 const gl::ScopedBindFramebuffer bindFb(gl);
1646 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fbB->mFB);
1648 if (srcColorFormat->isSRGB) {
1649 // srgb -> linear
1650 gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, srcP0.x,
1651 srcP0.y, srcP1.x, srcP1.y,
1652 LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_NEAREST);
1653 } else {
1654 // linear -> srgb
1655 gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x,
1656 dstP0.y, dstP1.x, dstP1.y,
1657 LOCAL_GL_COLOR_BUFFER_BIT, filter);
1660 const auto& blitHelper = *gl->BlitHelper();
1661 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fbC->mFB);
1662 blitHelper.DrawBlitTextureToFramebuffer(fbB->ColorTex(), sizeBC, sizeBC);
1666 const gl::ScopedBindFramebuffer bindFb(gl);
1667 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbC->mFB);
1669 if (srcColorFormat->isSRGB) {
1670 // srgb -> linear
1671 gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x,
1672 dstP0.y, dstP1.x, dstP1.y,
1673 LOCAL_GL_COLOR_BUFFER_BIT, filter);
1674 } else {
1675 // linear -> srgb
1676 gl->fBlitFramebuffer(dstP0.x, dstP0.y, dstP1.x, dstP1.y, dstP0.x,
1677 dstP0.y, dstP1.x, dstP1.y,
1678 LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_NEAREST);
1683 // -
1684 // glBlitFramebuffer ignores glColorMask!
1686 if (!webgl->mBoundDrawFramebuffer && webgl->mNeedsFakeNoAlpha) {
1687 if (!webgl->mScissorTestEnabled) {
1688 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
1690 if (webgl->mRasterizerDiscardEnabled) {
1691 gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD);
1694 const auto dstRectMin = MinExtents(dstP0, dstP1);
1695 const auto dstRectMax = MaxExtents(dstP0, dstP1);
1696 const auto dstRectSize = dstRectMax - dstRectMin;
1697 const WebGLContext::ScissorRect dstRect = {dstRectMin.x, dstRectMin.y,
1698 dstRectSize.x, dstRectSize.y};
1699 dstRect.Apply(*gl);
1701 gl->fClearColor(0, 0, 0, 1);
1702 webgl->DoColorMask(1 << 3);
1703 gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
1705 if (!webgl->mScissorTestEnabled) {
1706 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
1708 if (webgl->mRasterizerDiscardEnabled) {
1709 gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
1711 webgl->mScissorRect.Apply(*gl);
1712 gl->fClearColor(webgl->mColorClearValue[0], webgl->mColorClearValue[1],
1713 webgl->mColorClearValue[2], webgl->mColorClearValue[3]);
1717 } // namespace mozilla