Bug 1831122 [wpt PR 39823] - Update wpt metadata, a=testonly
[gecko.git] / dom / canvas / WebGLContext.cpp
blob6bd07bd78fe66ef14c1f2ba9e4dd3d43d61ae123
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 "WebGLContext.h"
8 #include <algorithm>
9 #include <bitset>
10 #include <queue>
11 #include <regex>
13 #include "AccessCheck.h"
14 #include "CompositableHost.h"
15 #include "gfxConfig.h"
16 #include "gfxContext.h"
17 #include "gfxCrashReporterUtils.h"
18 #include "gfxEnv.h"
19 #include "gfxPattern.h"
20 #include "MozFramebuffer.h"
21 #include "GLBlitHelper.h"
22 #include "GLContext.h"
23 #include "GLContextProvider.h"
24 #include "GLReadTexImageHelper.h"
25 #include "GLScreenBuffer.h"
26 #include "ImageContainer.h"
27 #include "ImageEncoder.h"
28 #include "LayerUserData.h"
29 #include "mozilla/dom/BindingUtils.h"
30 #include "mozilla/dom/Document.h"
31 #include "mozilla/dom/Event.h"
32 #include "mozilla/dom/HTMLVideoElement.h"
33 #include "mozilla/dom/ImageData.h"
34 #include "mozilla/dom/WebGLContextEvent.h"
35 #include "mozilla/EnumeratedArrayCycleCollection.h"
36 #include "mozilla/EnumeratedRange.h"
37 #include "mozilla/gfx/gfxVars.h"
38 #include "mozilla/Preferences.h"
39 #include "mozilla/ProcessPriorityManager.h"
40 #include "mozilla/ResultVariant.h"
41 #include "mozilla/ScopeExit.h"
42 #include "mozilla/Services.h"
43 #include "mozilla/StaticPrefs_webgl.h"
44 #include "mozilla/SVGObserverUtils.h"
45 #include "mozilla/Telemetry.h"
46 #include "nsContentUtils.h"
47 #include "nsDisplayList.h"
48 #include "nsError.h"
49 #include "nsIClassInfoImpl.h"
50 #include "nsIWidget.h"
51 #include "nsServiceManagerUtils.h"
52 #include "SharedSurfaceGL.h"
53 #include "prenv.h"
54 #include "ScopedGLHelpers.h"
55 #include "VRManagerChild.h"
56 #include "mozilla/gfx/Swizzle.h"
57 #include "mozilla/layers/BufferTexture.h"
58 #include "mozilla/layers/RemoteTextureMap.h"
59 #include "mozilla/layers/CompositorBridgeChild.h"
60 #include "mozilla/layers/ImageBridgeChild.h"
61 #include "mozilla/layers/TextureClientSharedSurface.h"
62 #include "mozilla/layers/WebRenderUserData.h"
63 #include "mozilla/layers/WebRenderCanvasRenderer.h"
65 // Local
66 #include "CanvasUtils.h"
67 #include "ClientWebGLContext.h"
68 #include "HostWebGLContext.h"
69 #include "WebGLBuffer.h"
70 #include "WebGLChild.h"
71 #include "WebGLContextLossHandler.h"
72 #include "WebGLContextUtils.h"
73 #include "WebGLExtensions.h"
74 #include "WebGLFormats.h"
75 #include "WebGLFramebuffer.h"
76 #include "WebGLMemoryTracker.h"
77 #include "WebGLObjectModel.h"
78 #include "WebGLParent.h"
79 #include "WebGLProgram.h"
80 #include "WebGLQuery.h"
81 #include "WebGLSampler.h"
82 #include "WebGLShader.h"
83 #include "WebGLShaderValidator.h"
84 #include "WebGLSync.h"
85 #include "WebGLTransformFeedback.h"
86 #include "WebGLValidateStrings.h"
87 #include "WebGLVertexArray.h"
89 #ifdef MOZ_WIDGET_COCOA
90 # include "nsCocoaFeatures.h"
91 #endif
93 #ifdef XP_WIN
94 # include "WGLLibrary.h"
95 #endif
97 // Generated
98 #include "mozilla/dom/WebGLRenderingContextBinding.h"
100 namespace mozilla {
102 WebGLContextOptions::WebGLContextOptions() {
103 // Set default alpha state based on preference.
104 alpha = !StaticPrefs::webgl_default_no_alpha();
105 antialias = StaticPrefs::webgl_default_antialias();
108 StaticMutex WebGLContext::sLruMutex;
109 std::list<WebGLContext*> WebGLContext::sLru;
111 WebGLContext::LruPosition::LruPosition() {
112 StaticMutexAutoLock lock(sLruMutex);
113 mItr = sLru.end();
114 } // NOLINT
116 WebGLContext::LruPosition::LruPosition(WebGLContext& context) {
117 StaticMutexAutoLock lock(sLruMutex);
118 mItr = sLru.insert(sLru.end(), &context);
121 void WebGLContext::LruPosition::AssignLocked(WebGLContext& aContext) {
122 ResetLocked();
123 mItr = sLru.insert(sLru.end(), &aContext);
126 void WebGLContext::LruPosition::ResetLocked() {
127 const auto end = sLru.end();
128 if (mItr != end) {
129 sLru.erase(mItr);
130 mItr = end;
134 void WebGLContext::LruPosition::Reset() {
135 StaticMutexAutoLock lock(sLruMutex);
136 ResetLocked();
139 bool WebGLContext::LruPosition::IsInsertedLocked() const {
140 return mItr != sLru.end();
143 WebGLContext::WebGLContext(HostWebGLContext& host,
144 const webgl::InitContextDesc& desc)
145 : gl(mGL_OnlyClearInDestroyResourcesAndContext), // const reference
146 mHost(&host),
147 mResistFingerprinting(desc.resistFingerprinting),
148 mOptions(desc.options),
149 mPrincipalKey(desc.principalKey),
150 mPendingContextLoss(false),
151 mMaxPerfWarnings(StaticPrefs::webgl_perf_max_warnings()),
152 mMaxAcceptableFBStatusInvals(
153 StaticPrefs::webgl_perf_max_acceptable_fb_status_invals()),
154 mContextLossHandler(this),
155 mMaxWarnings(StaticPrefs::webgl_max_warnings_per_context()),
156 mAllowFBInvalidation(StaticPrefs::webgl_allow_fb_invalidation()),
157 mMsaaSamples((uint8_t)StaticPrefs::webgl_msaa_samples()),
158 mRequestedSize(desc.size) {
159 host.mContext = this;
160 const FuncScope funcScope(*this, "<Create>");
163 WebGLContext::~WebGLContext() { DestroyResourcesAndContext(); }
165 void WebGLContext::DestroyResourcesAndContext() {
166 if (mRemoteTextureOwner) {
167 // Clean up any remote textures registered for framebuffer swap chains.
168 mRemoteTextureOwner->UnregisterAllTextureOwners();
169 mRemoteTextureOwner = nullptr;
172 if (!gl) return;
174 mDefaultFB = nullptr;
175 mResolvedDefaultFB = nullptr;
177 mBound2DTextures.Clear();
178 mBoundCubeMapTextures.Clear();
179 mBound3DTextures.Clear();
180 mBound2DArrayTextures.Clear();
181 mBoundSamplers.Clear();
182 mBoundArrayBuffer = nullptr;
183 mBoundCopyReadBuffer = nullptr;
184 mBoundCopyWriteBuffer = nullptr;
185 mBoundPixelPackBuffer = nullptr;
186 mBoundPixelUnpackBuffer = nullptr;
187 mBoundTransformFeedbackBuffer = nullptr;
188 mBoundUniformBuffer = nullptr;
189 mCurrentProgram = nullptr;
190 mActiveProgramLinkInfo = nullptr;
191 mBoundDrawFramebuffer = nullptr;
192 mBoundReadFramebuffer = nullptr;
193 mBoundVertexArray = nullptr;
194 mDefaultVertexArray = nullptr;
195 mBoundTransformFeedback = nullptr;
196 mDefaultTransformFeedback = nullptr;
198 mQuerySlot_SamplesPassed = nullptr;
199 mQuerySlot_TFPrimsWritten = nullptr;
200 mQuerySlot_TimeElapsed = nullptr;
202 mIndexedUniformBufferBindings.clear();
204 //////
206 if (mEmptyTFO) {
207 gl->fDeleteTransformFeedbacks(1, &mEmptyTFO);
208 mEmptyTFO = 0;
211 //////
213 if (mFakeVertexAttrib0BufferObject) {
214 gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject);
215 mFakeVertexAttrib0BufferObject = 0;
218 // disable all extensions except "WEBGL_lose_context". see bug #927969
219 // spec: http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
220 for (size_t i = 0; i < size_t(WebGLExtensionID::Max); ++i) {
221 WebGLExtensionID extension = WebGLExtensionID(i);
222 if (extension == WebGLExtensionID::WEBGL_lose_context) continue;
223 mExtensions[extension] = nullptr;
226 // We just got rid of everything, so the context had better
227 // have been going away.
228 if (gl::GLContext::ShouldSpew()) {
229 printf_stderr("--- WebGL context destroyed: %p\n", gl.get());
232 MOZ_ASSERT(gl);
233 gl->MarkDestroyed();
234 mGL_OnlyClearInDestroyResourcesAndContext = nullptr;
235 MOZ_ASSERT(!gl);
238 void ClientWebGLContext::MarkCanvasDirty() {
239 if (!mCanvasElement && !mOffscreenCanvas) return;
241 mFrameCaptureState = FrameCaptureState::DIRTY;
243 if (mIsCanvasDirty) return;
244 mIsCanvasDirty = true;
246 if (mCanvasElement) {
247 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
248 mCanvasElement->InvalidateCanvasContent(nullptr);
249 } else if (mOffscreenCanvas) {
250 mOffscreenCanvas->QueueCommitToCompositor();
254 void WebGLContext::OnMemoryPressure() {
255 bool shouldLoseContext = mLoseContextOnMemoryPressure;
257 if (!mCanLoseContextInForeground &&
258 ProcessPriorityManager::CurrentProcessIsForeground()) {
259 shouldLoseContext = false;
262 if (shouldLoseContext) LoseContext();
265 // --
267 bool WebGLContext::CreateAndInitGL(
268 bool forceEnabled, std::vector<FailureReason>* const out_failReasons) {
269 const FuncScope funcScope(*this, "<Create>");
271 // WebGL2 is separately blocked:
272 if (IsWebGL2() && !forceEnabled) {
273 FailureReason reason;
274 if (!gfx::gfxVars::AllowWebgl2()) {
275 reason.info =
276 "AllowWebgl2:false restricts context creation on this system.";
277 out_failReasons->push_back(reason);
278 GenerateWarning("%s", reason.info.BeginReading());
279 return false;
283 gl::CreateContextFlags flags = (gl::CreateContextFlags::NO_VALIDATION |
284 gl::CreateContextFlags::PREFER_ROBUSTNESS);
285 bool tryNativeGL = true;
286 bool tryANGLE = false;
288 // -
290 if (StaticPrefs::webgl_forbid_hardware()) {
291 flags |= gl::CreateContextFlags::FORBID_HARDWARE;
293 if (StaticPrefs::webgl_forbid_software()) {
294 flags |= gl::CreateContextFlags::FORBID_SOFTWARE;
297 if (forceEnabled) {
298 flags &= ~gl::CreateContextFlags::FORBID_HARDWARE;
299 flags &= ~gl::CreateContextFlags::FORBID_SOFTWARE;
302 if ((flags & gl::CreateContextFlags::FORBID_HARDWARE) &&
303 (flags & gl::CreateContextFlags::FORBID_SOFTWARE)) {
304 FailureReason reason;
305 reason.info = "Both hardware and software were forbidden by config.";
306 out_failReasons->push_back(reason);
307 GenerateWarning("%s", reason.info.BeginReading());
308 return false;
311 // -
313 if (StaticPrefs::webgl_cgl_multithreaded()) {
314 flags |= gl::CreateContextFlags::PREFER_MULTITHREADED;
317 if (IsWebGL2()) {
318 flags |= gl::CreateContextFlags::PREFER_ES3;
319 } else {
320 // Request and prefer ES2 context for WebGL1.
321 flags |= gl::CreateContextFlags::PREFER_EXACT_VERSION;
323 if (!StaticPrefs::webgl_1_allow_core_profiles()) {
324 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
329 auto powerPref = mOptions.powerPreference;
331 // If "Use hardware acceleration when available" option is disabled:
332 if (!gfx::gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING)) {
333 powerPref = dom::WebGLPowerPreference::Low_power;
336 const auto overrideVal = StaticPrefs::webgl_power_preference_override();
337 if (overrideVal > 0) {
338 powerPref = dom::WebGLPowerPreference::High_performance;
339 } else if (overrideVal < 0) {
340 powerPref = dom::WebGLPowerPreference::Low_power;
343 if (powerPref == dom::WebGLPowerPreference::High_performance) {
344 flags |= gl::CreateContextFlags::HIGH_POWER;
348 if (!gfx::gfxVars::WebglAllowCoreProfile()) {
349 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
352 // --
354 const bool useEGL = PR_GetEnv("MOZ_WEBGL_FORCE_EGL");
356 #ifdef XP_WIN
357 tryNativeGL = false;
358 tryANGLE = true;
360 if (StaticPrefs::webgl_disable_wgl()) {
361 tryNativeGL = false;
364 if (StaticPrefs::webgl_disable_angle() ||
365 PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL") || useEGL) {
366 tryNativeGL = true;
367 tryANGLE = false;
369 #endif
371 if (tryNativeGL && !forceEnabled) {
372 FailureReason reason;
373 if (!gfx::gfxVars::WebglAllowWindowsNativeGl()) {
374 reason.info =
375 "WebglAllowWindowsNativeGl:false restricts context creation on this "
376 "system.";
378 out_failReasons->push_back(reason);
380 GenerateWarning("%s", reason.info.BeginReading());
381 tryNativeGL = false;
385 // --
387 using fnCreateT = decltype(gl::GLContextProviderEGL::CreateHeadless);
388 const auto fnCreate = [&](fnCreateT* const pfnCreate,
389 const char* const info) {
390 nsCString failureId;
391 const RefPtr<gl::GLContext> gl = pfnCreate({flags}, &failureId);
392 if (!gl) {
393 out_failReasons->push_back(WebGLContext::FailureReason(failureId, info));
395 return gl;
398 const auto newGL = [&]() -> RefPtr<gl::GLContext> {
399 if (tryNativeGL) {
400 if (useEGL)
401 return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "useEGL");
403 const auto ret =
404 fnCreate(&gl::GLContextProvider::CreateHeadless, "tryNativeGL");
405 if (ret) return ret;
408 if (tryANGLE) {
409 return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "tryANGLE");
411 return nullptr;
412 }();
414 if (!newGL) {
415 out_failReasons->push_back(
416 FailureReason("FEATURE_FAILURE_WEBGL_EXHAUSTED_DRIVERS",
417 "Exhausted GL driver options."));
418 return false;
421 // --
423 FailureReason reason;
425 mGL_OnlyClearInDestroyResourcesAndContext = newGL;
426 MOZ_RELEASE_ASSERT(gl);
427 if (!InitAndValidateGL(&reason)) {
428 DestroyResourcesAndContext();
429 MOZ_RELEASE_ASSERT(!gl);
431 // The fail reason here should be specific enough for now.
432 out_failReasons->push_back(reason);
433 return false;
436 const auto val = StaticPrefs::webgl_debug_incomplete_tex_color();
437 if (val) {
438 mIncompleteTexOverride.reset(new gl::Texture(*gl));
439 const gl::ScopedBindTexture autoBind(gl, mIncompleteTexOverride->name);
440 const auto heapVal = std::make_unique<uint32_t>(val);
441 gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 1, 1, 0,
442 LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, heapVal.get());
445 return true;
448 // Fallback for resizes:
450 bool WebGLContext::EnsureDefaultFB() {
451 if (mDefaultFB) {
452 MOZ_ASSERT(*uvec2::FromSize(mDefaultFB->mSize) == mRequestedSize);
453 return true;
456 const bool depthStencil = mOptions.depth || mOptions.stencil;
457 auto attemptSize = gfx::IntSize{mRequestedSize.x, mRequestedSize.y};
459 while (attemptSize.width || attemptSize.height) {
460 attemptSize.width = std::max(attemptSize.width, 1);
461 attemptSize.height = std::max(attemptSize.height, 1);
463 [&]() {
464 if (mOptions.antialias) {
465 MOZ_ASSERT(!mDefaultFB);
466 mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, mMsaaSamples,
467 depthStencil);
468 if (mDefaultFB) return;
469 if (mOptionsFrozen) return;
472 MOZ_ASSERT(!mDefaultFB);
473 mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, 0, depthStencil);
474 }();
476 if (mDefaultFB) break;
478 attemptSize.width /= 2;
479 attemptSize.height /= 2;
482 if (!mDefaultFB) {
483 GenerateWarning("Backbuffer resize failed. Losing context.");
484 LoseContext();
485 return false;
488 mDefaultFB_IsInvalid = true;
490 const auto actualSize = *uvec2::FromSize(mDefaultFB->mSize);
491 if (actualSize != mRequestedSize) {
492 GenerateWarning(
493 "Requested size %ux%u was too large, but resize"
494 " to %ux%u succeeded.",
495 mRequestedSize.x, mRequestedSize.y, actualSize.x, actualSize.y);
497 mRequestedSize = actualSize;
498 return true;
501 void WebGLContext::Resize(uvec2 requestedSize) {
502 // Zero-sized surfaces can cause problems.
503 if (!requestedSize.x) {
504 requestedSize.x = 1;
506 if (!requestedSize.y) {
507 requestedSize.y = 1;
510 // Kill our current default fb(s), for later lazy allocation.
511 mRequestedSize = requestedSize;
512 mDefaultFB = nullptr;
513 mResetLayer = true; // New size means new Layer.
516 UniquePtr<webgl::FormatUsageAuthority> WebGLContext::CreateFormatUsage(
517 gl::GLContext* gl) const {
518 return webgl::FormatUsageAuthority::CreateForWebGL1(gl);
521 /*static*/
522 RefPtr<WebGLContext> WebGLContext::Create(HostWebGLContext& host,
523 const webgl::InitContextDesc& desc,
524 webgl::InitContextResult* const out) {
525 AUTO_PROFILER_LABEL("WebGLContext::Create", GRAPHICS);
526 nsCString failureId = "FEATURE_FAILURE_WEBGL_UNKOWN"_ns;
527 const bool forceEnabled = StaticPrefs::webgl_force_enabled();
528 ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
530 auto res = [&]() -> Result<RefPtr<WebGLContext>, std::string> {
531 bool disabled = StaticPrefs::webgl_disabled();
533 // TODO: When we have software webgl support we should use that instead.
534 disabled |= gfxPlatform::InSafeMode();
536 if (disabled) {
537 if (gfxPlatform::InSafeMode()) {
538 failureId = "FEATURE_FAILURE_WEBGL_SAFEMODE"_ns;
539 } else {
540 failureId = "FEATURE_FAILURE_WEBGL_DISABLED"_ns;
542 return Err("WebGL is currently disabled.");
545 // Alright, now let's start trying.
547 RefPtr<WebGLContext> webgl;
548 if (desc.isWebgl2) {
549 webgl = new WebGL2Context(host, desc);
550 } else {
551 webgl = new WebGLContext(host, desc);
554 MOZ_ASSERT(!webgl->gl);
555 std::vector<FailureReason> failReasons;
556 if (!webgl->CreateAndInitGL(forceEnabled, &failReasons)) {
557 nsCString text("WebGL creation failed: ");
558 for (const auto& cur : failReasons) {
559 // Don't try to accumulate using an empty key if |cur.key| is empty.
560 if (cur.key.IsEmpty()) {
561 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID,
562 "FEATURE_FAILURE_REASON_UNKNOWN"_ns);
563 } else {
564 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, cur.key);
567 const auto str = nsPrintfCString("\n* %s (%s)", cur.info.BeginReading(),
568 cur.key.BeginReading());
569 text.Append(str);
571 failureId = "FEATURE_FAILURE_REASON"_ns;
572 return Err(text.BeginReading());
574 MOZ_ASSERT(webgl->gl);
576 if (desc.options.failIfMajorPerformanceCaveat) {
577 if (webgl->gl->IsWARP()) {
578 failureId = "FEATURE_FAILURE_WEBGL_PERF_WARP"_ns;
579 return Err(
580 "failIfMajorPerformanceCaveat: Driver is not"
581 " hardware-accelerated.");
584 #ifdef XP_WIN
585 if (webgl->gl->GetContextType() == gl::GLContextType::WGL &&
586 !gl::sWGLLib.HasDXInterop2()) {
587 failureId = "FEATURE_FAILURE_WEBGL_DXGL_INTEROP2"_ns;
588 return Err("failIfMajorPerformanceCaveat: WGL without DXGLInterop2.");
590 #endif
593 const FuncScope funcScope(*webgl, "getContext/restoreContext");
595 MOZ_ASSERT(!webgl->mDefaultFB);
596 if (!webgl->EnsureDefaultFB()) {
597 MOZ_ASSERT(!webgl->mDefaultFB);
598 MOZ_ASSERT(webgl->IsContextLost());
599 failureId = "FEATURE_FAILURE_WEBGL_BACKBUFFER"_ns;
600 return Err("Initializing WebGL backbuffer failed.");
603 return webgl;
604 }();
605 if (res.isOk()) {
606 failureId = "SUCCESS"_ns;
608 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, failureId);
610 if (!res.isOk()) {
611 out->error = res.unwrapErr();
612 return nullptr;
614 const auto webgl = res.unwrap();
616 // Update our internal stuff:
617 webgl->FinishInit();
619 reporter.SetSuccessful();
620 if (gl::GLContext::ShouldSpew()) {
621 printf_stderr("--- WebGL context created: %p\n", webgl.get());
624 // -
626 const auto UploadableSdTypes = [&]() {
627 webgl::EnumMask<layers::SurfaceDescriptor::Type> types;
628 types[layers::SurfaceDescriptor::TSurfaceDescriptorBuffer] = true;
629 if (webgl->gl->IsANGLE()) {
630 types[layers::SurfaceDescriptor::TSurfaceDescriptorD3D10] = true;
631 types[layers::SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr] = true;
633 if (kIsMacOS) {
634 types[layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface] = true;
636 if (kIsAndroid) {
637 types[layers::SurfaceDescriptor::TSurfaceTextureDescriptor] = true;
639 if (kIsX11 || kIsWayland) {
640 types[layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf] = true;
642 return types;
645 // -
647 out->options = webgl->mOptions;
648 out->limits = *webgl->mLimits;
649 out->uploadableSdTypes = UploadableSdTypes();
650 out->vendor = webgl->gl->Vendor();
652 return webgl;
655 void WebGLContext::FinishInit() {
656 mOptions.antialias &= bool(mDefaultFB->mSamples);
658 if (!mOptions.alpha) {
659 // We always have alpha.
660 mNeedsFakeNoAlpha = true;
663 if (mOptions.depth || mOptions.stencil) {
664 // We always have depth+stencil if we have either.
665 if (!mOptions.depth) {
666 mNeedsFakeNoDepth = true;
668 if (!mOptions.stencil) {
669 mNeedsFakeNoStencil = true;
673 mNeedsFakeNoStencil_UserFBs = false;
674 #ifdef MOZ_WIDGET_COCOA
675 if (!nsCocoaFeatures::IsAtLeastVersion(10, 12) &&
676 gl->Vendor() == gl::GLVendor::Intel) {
677 mNeedsFakeNoStencil_UserFBs = true;
679 #endif
681 mResetLayer = true;
682 mOptionsFrozen = true;
684 //////
685 // Initial setup.
687 gl->mImplicitMakeCurrent = true;
688 gl->mElideDuplicateBindFramebuffers = true;
690 const auto& size = mDefaultFB->mSize;
692 mViewportX = mViewportY = 0;
693 mViewportWidth = size.width;
694 mViewportHeight = size.height;
695 gl->fViewport(mViewportX, mViewportY, mViewportWidth, mViewportHeight);
697 mScissorRect = {0, 0, size.width, size.height};
698 mScissorRect.Apply(*gl);
700 //////
701 // Check everything
703 AssertCachedBindings();
704 AssertCachedGlobalState();
706 mShouldPresent = true;
708 //////
710 gl->ResetSyncCallCount("WebGLContext Initialization");
711 LoseLruContextIfLimitExceeded();
714 void WebGLContext::SetCompositableHost(
715 RefPtr<layers::CompositableHost>& aCompositableHost) {
716 mCompositableHost = aCompositableHost;
719 void WebGLContext::BumpLruLocked() {
720 if (!mIsContextLost && !mPendingContextLoss) {
721 mLruPosition.AssignLocked(*this);
722 } else {
723 MOZ_ASSERT(!mLruPosition.IsInsertedLocked());
727 void WebGLContext::BumpLru() {
728 StaticMutexAutoLock lock(sLruMutex);
729 BumpLruLocked();
732 void WebGLContext::LoseLruContextIfLimitExceeded() {
733 StaticMutexAutoLock lock(sLruMutex);
735 const auto maxContexts = std::max(1u, StaticPrefs::webgl_max_contexts());
736 const auto maxContextsPerPrincipal =
737 std::max(1u, StaticPrefs::webgl_max_contexts_per_principal());
739 // it's important to update the index on a new context before losing old
740 // contexts, otherwise new unused contexts would all have index 0 and we
741 // couldn't distinguish older ones when choosing which one to lose first.
742 BumpLruLocked();
745 size_t forPrincipal = 0;
746 for (const auto& context : sLru) {
747 if (context->mPrincipalKey == mPrincipalKey) {
748 forPrincipal += 1;
752 while (forPrincipal > maxContextsPerPrincipal) {
753 const auto text = nsPrintfCString(
754 "Exceeded %u live WebGL contexts for this principal, losing the "
755 "least recently used one.",
756 maxContextsPerPrincipal);
757 mHost->JsWarning(ToString(text));
759 for (const auto& context : sLru) {
760 if (context->mPrincipalKey == mPrincipalKey) {
761 MOZ_ASSERT(context != this);
762 context->LoseContextLruLocked(webgl::ContextLossReason::None);
763 forPrincipal -= 1;
764 break;
770 auto total = sLru.size();
771 while (total > maxContexts) {
772 const auto text = nsPrintfCString(
773 "Exceeded %u live WebGL contexts, losing the least "
774 "recently used one.",
775 maxContexts);
776 mHost->JsWarning(ToString(text));
778 const auto& context = sLru.front();
779 MOZ_ASSERT(context != this);
780 context->LoseContextLruLocked(webgl::ContextLossReason::None);
781 total -= 1;
785 // -
787 namespace webgl {
789 ScopedPrepForResourceClear::ScopedPrepForResourceClear(
790 const WebGLContext& webgl_)
791 : webgl(webgl_) {
792 const auto& gl = webgl.gl;
794 if (webgl.mScissorTestEnabled) {
795 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
797 if (webgl.mRasterizerDiscardEnabled) {
798 gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD);
801 // "The clear operation always uses the front stencil write mask
802 // when clearing the stencil buffer."
803 webgl.DoColorMask(Some(0), 0b1111);
804 gl->fDepthMask(true);
805 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff);
807 gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
808 gl->fClearDepth(1.0f); // Depth formats are always cleared to 1.0f, not 0.0f.
809 gl->fClearStencil(0);
812 ScopedPrepForResourceClear::~ScopedPrepForResourceClear() {
813 const auto& gl = webgl.gl;
815 if (webgl.mScissorTestEnabled) {
816 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
818 if (webgl.mRasterizerDiscardEnabled) {
819 gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
822 webgl.DoColorMask(Some(0), webgl.mColorWriteMask0);
823 gl->fDepthMask(webgl.mDepthWriteMask);
824 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, webgl.mStencilWriteMaskFront);
826 gl->fClearColor(webgl.mColorClearValue[0], webgl.mColorClearValue[1],
827 webgl.mColorClearValue[2], webgl.mColorClearValue[3]);
828 gl->fClearDepth(webgl.mDepthClearValue);
829 gl->fClearStencil(webgl.mStencilClearValue);
832 } // namespace webgl
834 // -
836 void WebGLContext::OnEndOfFrame() {
837 if (StaticPrefs::webgl_perf_spew_frame_allocs()) {
838 GeneratePerfWarning("[webgl.perf.spew-frame-allocs] %" PRIu64
839 " data allocations this frame.",
840 mDataAllocGLCallCount);
842 mDataAllocGLCallCount = 0;
843 gl->ResetSyncCallCount("WebGLContext PresentScreenBuffer");
845 mDrawCallsSinceLastFlush = 0;
847 BumpLru();
850 void WebGLContext::BlitBackbufferToCurDriverFB(
851 WebGLFramebuffer* const srcAsWebglFb,
852 const gl::MozFramebuffer* const srcAsMozFb, bool srcIsBGRA) const {
853 // BlitFramebuffer ignores ColorMask().
855 if (mScissorTestEnabled) {
856 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
859 [&]() {
860 // If a MozFramebuffer is supplied, ensure that a WebGLFramebuffer is not
861 // used since it might not have completeness info, while the MozFramebuffer
862 // can still supply the needed information.
863 MOZ_ASSERT(!(srcAsMozFb && srcAsWebglFb));
864 const auto* mozFb = srcAsMozFb ? srcAsMozFb : mDefaultFB.get();
865 GLuint fbo = 0;
866 gfx::IntSize size;
867 if (srcAsWebglFb) {
868 fbo = srcAsWebglFb->mGLName;
869 const auto* info = srcAsWebglFb->GetCompletenessInfo();
870 MOZ_ASSERT(info);
871 size = gfx::IntSize(info->width, info->height);
872 } else {
873 fbo = mozFb->mFB;
874 size = mozFb->mSize;
877 // If no format conversion is necessary, then attempt to directly blit
878 // between framebuffers. Otherwise, if we need to convert to RGBA from
879 // the source format, then we will need to use the texture blit path
880 // below.
881 if (!srcIsBGRA) {
882 if (gl->IsSupported(gl::GLFeature::framebuffer_blit)) {
883 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
884 gl->fBlitFramebuffer(0, 0, size.width, size.height, 0, 0, size.width,
885 size.height, LOCAL_GL_COLOR_BUFFER_BIT,
886 LOCAL_GL_NEAREST);
887 return;
889 if (mDefaultFB->mSamples &&
890 gl->IsExtensionSupported(
891 gl::GLContext::APPLE_framebuffer_multisample)) {
892 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
893 gl->fResolveMultisampleFramebufferAPPLE();
894 return;
898 GLuint colorTex = 0;
899 if (srcAsWebglFb) {
900 const auto& attach = srcAsWebglFb->ColorAttachment0();
901 MOZ_ASSERT(attach.Texture());
902 colorTex = attach.Texture()->mGLName;
903 } else {
904 colorTex = mozFb->ColorTex();
907 // DrawBlit handles ColorMask itself.
908 gl->BlitHelper()->DrawBlitTextureToFramebuffer(
909 colorTex, size, size, LOCAL_GL_TEXTURE_2D, srcIsBGRA);
910 }();
912 if (mScissorTestEnabled) {
913 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
917 // -
919 template <typename T, typename... Args>
920 constexpr auto MakeArray(Args... args) -> std::array<T, sizeof...(Args)> {
921 return {{static_cast<T>(args)...}};
924 inline gfx::ColorSpace2 ToColorSpace2(const WebGLContextOptions& options) {
925 auto ret = gfx::ColorSpace2::UNKNOWN;
926 if (true) {
927 ret = gfx::ColorSpace2::SRGB;
929 if (!options.ignoreColorSpace) {
930 ret = gfx::ToColorSpace2(options.colorSpace);
932 return ret;
935 // -
937 // For an overview of how WebGL compositing works, see:
938 // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
939 bool WebGLContext::PresentInto(gl::SwapChain& swapChain) {
940 OnEndOfFrame();
942 if (!ValidateAndInitFB(nullptr)) return false;
945 const auto colorSpace = ToColorSpace2(mOptions);
946 auto presenter = swapChain.Acquire(mDefaultFB->mSize, colorSpace);
947 if (!presenter) {
948 GenerateWarning("Swap chain surface creation failed.");
949 LoseContext();
950 return false;
953 const auto destFb = presenter->Fb();
954 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
956 BlitBackbufferToCurDriverFB();
958 if (!mOptions.preserveDrawingBuffer) {
959 if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
960 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB);
961 constexpr auto attachments = MakeArray<GLenum>(
962 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
963 gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
964 attachments.size(), attachments.data());
966 mDefaultFB_IsInvalid = true;
969 #ifdef DEBUG
970 if (!mOptions.alpha) {
971 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
972 gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
973 if (IsWebGL2()) {
974 gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, 0);
975 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
976 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
978 uint32_t pixel = 0xffbadbad;
979 gl->fReadPixels(0, 0, 1, 1, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
980 &pixel);
981 MOZ_ASSERT((pixel & 0xff000000) == 0xff000000);
983 #endif
986 return true;
989 bool WebGLContext::PresentIntoXR(gl::SwapChain& swapChain,
990 const gl::MozFramebuffer& fb) {
991 OnEndOfFrame();
993 const auto colorSpace = ToColorSpace2(mOptions);
994 auto presenter = swapChain.Acquire(fb.mSize, colorSpace);
995 if (!presenter) {
996 GenerateWarning("Swap chain surface creation failed.");
997 LoseContext();
998 return false;
1001 const auto destFb = presenter->Fb();
1002 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
1004 BlitBackbufferToCurDriverFB(nullptr, &fb);
1006 // https://immersive-web.github.io/webxr/#opaque-framebuffer
1007 // Opaque framebuffers will always be cleared regardless of the
1008 // associated WebGL context’s preserveDrawingBuffer value.
1009 if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
1010 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb.mFB);
1011 constexpr auto attachments = MakeArray<GLenum>(
1012 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
1013 gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, attachments.size(),
1014 attachments.data());
1017 return true;
1020 // Initialize a swap chain's surface factory given the desired surface type.
1021 void InitSwapChain(gl::GLContext& gl, gl::SwapChain& swapChain,
1022 const layers::TextureType consumerType) {
1023 if (!swapChain.mFactory) {
1024 auto typedFactory = gl::SurfaceFactory::Create(&gl, consumerType);
1025 if (typedFactory) {
1026 swapChain.mFactory = std::move(typedFactory);
1029 if (!swapChain.mFactory) {
1030 NS_WARNING("Failed to make an ideal SurfaceFactory.");
1031 swapChain.mFactory = MakeUnique<gl::SurfaceFactory_Basic>(gl);
1033 MOZ_ASSERT(swapChain.mFactory);
1036 void WebGLContext::Present(WebGLFramebuffer* const xrFb,
1037 const layers::TextureType consumerType,
1038 const bool webvr,
1039 const webgl::SwapChainOptions& options) {
1040 const FuncScope funcScope(*this, "<Present>");
1041 if (IsContextLost()) return;
1043 auto swapChain = GetSwapChain(xrFb, webvr);
1044 const gl::MozFramebuffer* maybeFB = nullptr;
1045 if (xrFb) {
1046 maybeFB = xrFb->mOpaque.get();
1047 } else {
1048 mResolvedDefaultFB = nullptr;
1051 InitSwapChain(*gl, *swapChain, consumerType);
1053 bool valid =
1054 maybeFB ? PresentIntoXR(*swapChain, *maybeFB) : PresentInto(*swapChain);
1055 if (!valid) {
1056 return;
1059 bool useAsync = options.remoteTextureOwnerId.IsValid() &&
1060 options.remoteTextureId.IsValid();
1061 if (useAsync) {
1062 PushRemoteTexture(nullptr, *swapChain, swapChain->FrontBuffer(), options);
1066 void WebGLContext::CopyToSwapChain(WebGLFramebuffer* const srcFb,
1067 const layers::TextureType consumerType,
1068 const webgl::SwapChainOptions& options) {
1069 const FuncScope funcScope(*this, "<CopyToSwapChain>");
1070 if (IsContextLost()) return;
1072 OnEndOfFrame();
1074 if (!srcFb) return;
1075 const auto* info = srcFb->GetCompletenessInfo();
1076 if (!info) {
1077 return;
1079 gfx::IntSize size(info->width, info->height);
1081 InitSwapChain(*gl, srcFb->mSwapChain, consumerType);
1083 bool useAsync = options.remoteTextureOwnerId.IsValid() &&
1084 options.remoteTextureId.IsValid();
1085 // If we're using async present and if there is no way to serialize surfaces,
1086 // then a readback is required to do the copy. In this case, there's no reason
1087 // to copy into a separate shared surface for the front buffer. Just directly
1088 // read back the WebGL framebuffer into and push it as a remote texture.
1089 if (useAsync && srcFb->mSwapChain.mFactory->GetConsumerType() ==
1090 layers::TextureType::Unknown) {
1091 PushRemoteTexture(srcFb, srcFb->mSwapChain, nullptr, options);
1092 return;
1096 // ColorSpace will need to be part of SwapChainOptions for DTWebgl.
1097 const auto colorSpace = ToColorSpace2(mOptions);
1098 auto presenter = srcFb->mSwapChain.Acquire(size, colorSpace);
1099 if (!presenter) {
1100 GenerateWarning("Swap chain surface creation failed.");
1101 LoseContext();
1102 return;
1105 const ScopedFBRebinder saveFB(this);
1107 const auto destFb = presenter->Fb();
1108 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
1110 BlitBackbufferToCurDriverFB(srcFb, nullptr, options.bgra);
1113 if (useAsync) {
1114 PushRemoteTexture(srcFb, srcFb->mSwapChain, srcFb->mSwapChain.FrontBuffer(),
1115 options);
1119 bool WebGLContext::PushRemoteTexture(WebGLFramebuffer* fb,
1120 gl::SwapChain& swapChain,
1121 std::shared_ptr<gl::SharedSurface> surf,
1122 const webgl::SwapChainOptions& options) {
1123 const auto onFailure = [&]() -> bool {
1124 GenerateWarning("Remote texture creation failed.");
1125 LoseContext();
1126 if (mRemoteTextureOwner) {
1127 mRemoteTextureOwner->PushDummyTexture(options.remoteTextureId,
1128 options.remoteTextureOwnerId);
1130 return false;
1133 if (!mRemoteTextureOwner) {
1134 // Ensure we have a remote texture owner client for WebGLParent.
1135 const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr;
1136 if (!outOfProcess) {
1137 return onFailure();
1139 mRemoteTextureOwner =
1140 MakeRefPtr<layers::RemoteTextureOwnerClient>(outOfProcess->OtherPid());
1143 layers::RemoteTextureOwnerId ownerId = options.remoteTextureOwnerId;
1144 layers::RemoteTextureId textureId = options.remoteTextureId;
1146 if (!mRemoteTextureOwner->IsRegistered(ownerId)) {
1147 // Register a texture owner to represent the swap chain.
1148 RefPtr<layers::RemoteTextureOwnerClient> textureOwner = mRemoteTextureOwner;
1149 auto destroyedCallback = [textureOwner, ownerId]() {
1150 textureOwner->UnregisterTextureOwner(ownerId);
1153 swapChain.SetDestroyedCallback(destroyedCallback);
1154 mRemoteTextureOwner->RegisterTextureOwner(
1155 ownerId,
1156 /* aIsSyncMode */ gfx::gfxVars::WebglOopAsyncPresentForceSync());
1159 MOZ_ASSERT(fb || surf);
1160 gfx::IntSize size;
1161 if (surf) {
1162 size = surf->mDesc.size;
1163 } else {
1164 const auto* info = fb->GetCompletenessInfo();
1165 MOZ_ASSERT(info);
1166 size = gfx::IntSize(info->width, info->height);
1169 const auto surfaceFormat = mOptions.alpha ? gfx::SurfaceFormat::B8G8R8A8
1170 : gfx::SurfaceFormat::B8G8R8X8;
1171 Maybe<layers::SurfaceDescriptor> desc;
1172 if (surf) {
1173 desc = surf->ToSurfaceDescriptor();
1175 if (!desc) {
1176 // If we can't serialize to a surface descriptor, then we need to create
1177 // a buffer to read back into that will become the remote texture.
1178 auto data = mRemoteTextureOwner->CreateOrRecycleBufferTextureData(
1179 ownerId, size, surfaceFormat);
1180 if (!data) {
1181 gfxCriticalNoteOnce << "Failed to allocate BufferTextureData";
1182 return onFailure();
1185 layers::MappedTextureData mappedData;
1186 if (!data->BorrowMappedData(mappedData)) {
1187 return onFailure();
1190 Range<uint8_t> range = {mappedData.data,
1191 data->AsBufferTextureData()->GetBufferSize()};
1193 // If we have a surface representing the front buffer, then try to snapshot
1194 // that. Otherwise, when there is no surface, we read back directly from the
1195 // WebGL framebuffer.
1196 auto valid =
1197 surf ? FrontBufferSnapshotInto(surf, Some(range),
1198 Some(mappedData.stride))
1199 : SnapshotInto(fb->mGLName, size, range, Some(mappedData.stride));
1200 if (!valid) {
1201 return onFailure();
1204 if (!options.bgra) {
1205 // If the buffer is already BGRA, we don't need to swizzle. However, if it
1206 // is RGBA, then a swizzle to BGRA is required.
1207 bool rv = gfx::SwizzleData(mappedData.data, mappedData.stride,
1208 gfx::SurfaceFormat::R8G8B8A8, mappedData.data,
1209 mappedData.stride,
1210 gfx::SurfaceFormat::B8G8R8A8, mappedData.size);
1211 MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
1214 mRemoteTextureOwner->PushTexture(textureId, ownerId, std::move(data),
1215 /* aSharedSurface */ nullptr);
1216 return true;
1219 // SharedSurfaces of SurfaceDescriptorD3D10 and SurfaceDescriptorMacIOSurface
1220 // need to be kept alive. They will be recycled by
1221 // RemoteTextureOwnerClient::GetRecycledSharedSurface() when their usages are
1222 // ended.
1223 std::shared_ptr<gl::SharedSurface> keepAlive;
1224 switch (desc->type()) {
1225 case layers::SurfaceDescriptor::TSurfaceDescriptorD3D10:
1226 case layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface:
1227 case layers::SurfaceDescriptor::TSurfaceTextureDescriptor:
1228 case layers::SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer:
1229 keepAlive = surf;
1230 break;
1231 default:
1232 break;
1235 auto data =
1236 MakeUnique<layers::SharedSurfaceTextureData>(*desc, surfaceFormat, size);
1237 mRemoteTextureOwner->PushTexture(textureId, ownerId, std::move(data),
1238 keepAlive);
1239 auto recycledSurface = mRemoteTextureOwner->GetRecycledSharedSurface(ownerId);
1240 if (recycledSurface) {
1241 swapChain.StoreRecycledSurface(recycledSurface);
1243 return true;
1246 void WebGLContext::EndOfFrame() {
1247 const FuncScope funcScope(*this, "<EndOfFrame>");
1248 if (IsContextLost()) return;
1250 OnEndOfFrame();
1253 gl::SwapChain* WebGLContext::GetSwapChain(WebGLFramebuffer* const xrFb,
1254 const bool webvr) {
1255 auto swapChain = webvr ? &mWebVRSwapChain : &mSwapChain;
1256 if (xrFb) {
1257 swapChain = &xrFb->mSwapChain;
1259 return swapChain;
1262 Maybe<layers::SurfaceDescriptor> WebGLContext::GetFrontBuffer(
1263 WebGLFramebuffer* const xrFb, const bool webvr) {
1264 auto* swapChain = GetSwapChain(xrFb, webvr);
1265 if (!swapChain) return {};
1266 const auto& front = swapChain->FrontBuffer();
1267 if (!front) return {};
1269 return front->ToSurfaceDescriptor();
1272 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
1273 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
1274 const auto& front = mSwapChain.FrontBuffer();
1275 if (!front) return {};
1276 return FrontBufferSnapshotInto(front, maybeDest, destStride);
1279 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
1280 const std::shared_ptr<gl::SharedSurface>& front,
1281 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
1282 const auto& size = front->mDesc.size;
1283 if (!maybeDest) return Some(*uvec2::FromSize(size));
1285 // -
1287 front->WaitForBufferOwnership();
1288 front->LockProd();
1289 front->ProducerReadAcquire();
1290 auto reset = MakeScopeExit([&] {
1291 front->ProducerReadRelease();
1292 front->UnlockProd();
1295 // -
1297 return SnapshotInto(front->mFb ? front->mFb->mFB : 0, size, *maybeDest,
1298 destStride);
1301 Maybe<uvec2> WebGLContext::SnapshotInto(GLuint srcFb, const gfx::IntSize& size,
1302 const Range<uint8_t>& dest,
1303 const Maybe<size_t> destStride) {
1304 const auto minStride = CheckedInt<size_t>(size.width) * 4;
1305 if (!minStride.isValid()) {
1306 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width;
1307 return {};
1309 size_t stride = destStride.valueOr(minStride.value());
1310 if (stride < minStride.value() || (stride % 4) != 0) {
1311 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width
1312 << ", stride:" << stride;
1313 return {};
1316 gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
1317 if (IsWebGL2()) {
1318 gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH,
1319 stride > minStride.value() ? stride / 4 : 0);
1320 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
1321 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
1324 // -
1326 const auto readFbWas = mBoundReadFramebuffer;
1327 const auto pboWas = mBoundPixelPackBuffer;
1329 GLenum fbTarget = LOCAL_GL_READ_FRAMEBUFFER;
1330 if (!IsWebGL2()) {
1331 fbTarget = LOCAL_GL_FRAMEBUFFER;
1333 auto reset2 = MakeScopeExit([&] {
1334 DoBindFB(readFbWas, fbTarget);
1335 if (pboWas) {
1336 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
1340 gl->fBindFramebuffer(fbTarget, srcFb);
1341 if (pboWas) {
1342 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
1345 // -
1347 const auto srcByteCount = CheckedInt<size_t>(stride) * size.height;
1348 if (!srcByteCount.isValid()) {
1349 gfxCriticalError() << "SnapshotInto: invalid srcByteCount, width:"
1350 << size.width << ", height:" << size.height;
1351 return {};
1353 const auto dstByteCount = dest.length();
1354 if (srcByteCount.value() > dstByteCount) {
1355 gfxCriticalError() << "SnapshotInto: srcByteCount:" << srcByteCount.value()
1356 << " > dstByteCount:" << dstByteCount;
1357 return {};
1359 uint8_t* dstPtr = dest.begin().get();
1360 gl->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA,
1361 LOCAL_GL_UNSIGNED_BYTE, dstPtr);
1363 if (!IsWebGL2() && stride > minStride.value() && size.height > 1) {
1364 // WebGL 1 doesn't support PACK_ROW_LENGTH. Instead, we read the data tight
1365 // into the front of the buffer, and use memmove (since the source and dest
1366 // may overlap) starting from the back to move it to the correct stride
1367 // offsets. We don't move the first row as it is already in the right place.
1368 uint8_t* destRow = dstPtr + stride * (size.height - 1);
1369 const uint8_t* srcRow = dstPtr + minStride.value() * (size.height - 1);
1370 while (destRow > dstPtr) {
1371 memmove(destRow, srcRow, minStride.value());
1372 destRow -= stride;
1373 srcRow -= minStride.value();
1377 return Some(*uvec2::FromSize(size));
1380 void WebGLContext::ClearVRSwapChain() { mWebVRSwapChain.ClearPool(); }
1382 // ------------------------
1384 RefPtr<gfx::DataSourceSurface> GetTempSurface(const gfx::IntSize& aSize,
1385 gfx::SurfaceFormat& aFormat) {
1386 uint32_t stride =
1387 gfx::GetAlignedStride<8>(aSize.width, BytesPerPixel(aFormat));
1388 return gfx::Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat,
1389 stride);
1392 void WebGLContext::DummyReadFramebufferOperation() {
1393 if (!mBoundReadFramebuffer) return; // Infallible.
1395 const auto status = mBoundReadFramebuffer->CheckFramebufferStatus();
1396 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
1397 ErrorInvalidFramebufferOperation("Framebuffer must be complete.");
1401 bool WebGLContext::Has64BitTimestamps() const {
1402 // 'sync' provides glGetInteger64v either by supporting ARB_sync, GL3+, or
1403 // GLES3+.
1404 return gl->IsSupported(gl::GLFeature::sync);
1407 static bool CheckContextLost(gl::GLContext* gl, bool* const out_isGuilty) {
1408 MOZ_ASSERT(gl);
1410 const auto resetStatus = gl->fGetGraphicsResetStatus();
1411 if (resetStatus == LOCAL_GL_NO_ERROR) {
1412 *out_isGuilty = false;
1413 return false;
1416 // Assume guilty unless we find otherwise!
1417 bool isGuilty = true;
1418 switch (resetStatus) {
1419 case LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB:
1420 case LOCAL_GL_PURGED_CONTEXT_RESET_NV:
1421 // Either nothing wrong, or not our fault.
1422 isGuilty = false;
1423 break;
1424 case LOCAL_GL_GUILTY_CONTEXT_RESET_ARB:
1425 NS_WARNING(
1426 "WebGL content on the page definitely caused the graphics"
1427 " card to reset.");
1428 break;
1429 case LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB:
1430 NS_WARNING(
1431 "WebGL content on the page might have caused the graphics"
1432 " card to reset");
1433 // If we can't tell, assume not-guilty.
1434 // Todo: Implement max number of "unknown" resets per document or time.
1435 isGuilty = false;
1436 break;
1437 default:
1438 gfxCriticalError() << "Unexpected glGetGraphicsResetStatus: "
1439 << gfx::hexa(resetStatus);
1440 break;
1443 if (isGuilty) {
1444 NS_WARNING(
1445 "WebGL context on this page is considered guilty, and will"
1446 " not be restored.");
1449 *out_isGuilty = isGuilty;
1450 return true;
1453 void WebGLContext::RunContextLossTimer() { mContextLossHandler.RunTimer(); }
1455 // We use this timer for many things. Here are the things that it is activated
1456 // for:
1457 // 1) If a script is using the MOZ_WEBGL_lose_context extension.
1458 // 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the
1459 // CONTEXT_LOST_WEBGL error has been triggered.
1460 // 3) If we are using ANGLE, or anything that supports ARB_robustness, query the
1461 // GPU periodically to see if the reset status bit has been set.
1462 // In all of these situations, we use this timer to send the script context lost
1463 // and restored events asynchronously. For example, if it triggers a context
1464 // loss, the webglcontextlost event will be sent to it the next time the
1465 // robustness timer fires.
1466 // Note that this timer mechanism is not used unless one of these 3 criteria are
1467 // met.
1468 // At a bare minimum, from context lost to context restores, it would take 3
1469 // full timer iterations: detection, webglcontextlost, webglcontextrestored.
1470 void WebGLContext::CheckForContextLoss() {
1471 bool isGuilty = true;
1472 const auto isContextLost = CheckContextLost(gl, &isGuilty);
1473 if (!isContextLost) return;
1475 mWebGLError = LOCAL_GL_CONTEXT_LOST;
1477 auto reason = webgl::ContextLossReason::None;
1478 if (isGuilty) {
1479 reason = webgl::ContextLossReason::Guilty;
1481 LoseContext(reason);
1484 void WebGLContext::HandlePendingContextLoss() {
1485 mIsContextLost = true;
1486 mHost->OnContextLoss(mPendingContextLossReason);
1489 void WebGLContext::LoseContextLruLocked(const webgl::ContextLossReason reason) {
1490 printf_stderr("WebGL(%p)::LoseContext(%u)\n", this,
1491 static_cast<uint32_t>(reason));
1492 mLruPosition.ResetLocked();
1493 mPendingContextLossReason = reason;
1494 mPendingContextLoss = true;
1497 void WebGLContext::LoseContext(const webgl::ContextLossReason reason) {
1498 StaticMutexAutoLock lock(sLruMutex);
1499 LoseContextLruLocked(reason);
1500 HandlePendingContextLoss();
1501 if (mRemoteTextureOwner) {
1502 mRemoteTextureOwner->NotifyContextLost();
1506 void WebGLContext::DidRefresh() {
1507 if (gl) {
1508 gl->FlushIfHeavyGLCallsSinceLastFlush();
1512 ////////////////////////////////////////////////////////////////////////////////
1514 uvec2 WebGLContext::DrawingBufferSize() {
1515 const FuncScope funcScope(*this, "width/height");
1516 if (IsContextLost()) return {};
1518 if (!EnsureDefaultFB()) return {};
1520 return *uvec2::FromSize(mDefaultFB->mSize);
1523 bool WebGLContext::ValidateAndInitFB(const WebGLFramebuffer* const fb,
1524 const GLenum incompleteFbError) {
1525 if (fb) return fb->ValidateAndInitAttachments(incompleteFbError);
1527 if (!EnsureDefaultFB()) return false;
1529 if (mDefaultFB_IsInvalid) {
1530 // Clear it!
1531 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
1532 const webgl::ScopedPrepForResourceClear scopedPrep(*this);
1533 if (!mOptions.alpha) {
1534 gl->fClearColor(0, 0, 0, 1);
1536 const GLbitfield bits = LOCAL_GL_COLOR_BUFFER_BIT |
1537 LOCAL_GL_DEPTH_BUFFER_BIT |
1538 LOCAL_GL_STENCIL_BUFFER_BIT;
1539 gl->fClear(bits);
1541 mDefaultFB_IsInvalid = false;
1543 return true;
1546 void WebGLContext::DoBindFB(const WebGLFramebuffer* const fb,
1547 const GLenum target) const {
1548 const GLenum driverFB = fb ? fb->mGLName : mDefaultFB->mFB;
1549 gl->fBindFramebuffer(target, driverFB);
1552 bool WebGLContext::BindCurFBForDraw() {
1553 const auto& fb = mBoundDrawFramebuffer;
1554 if (!ValidateAndInitFB(fb)) return false;
1556 DoBindFB(fb);
1557 return true;
1560 bool WebGLContext::BindCurFBForColorRead(
1561 const webgl::FormatUsageInfo** const out_format, uint32_t* const out_width,
1562 uint32_t* const out_height, const GLenum incompleteFbError) {
1563 const auto& fb = mBoundReadFramebuffer;
1565 if (fb) {
1566 if (!ValidateAndInitFB(fb, incompleteFbError)) return false;
1567 if (!fb->ValidateForColorRead(out_format, out_width, out_height))
1568 return false;
1570 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb->mGLName);
1571 return true;
1574 if (!BindDefaultFBForRead()) return false;
1576 if (mDefaultFB_ReadBuffer == LOCAL_GL_NONE) {
1577 ErrorInvalidOperation(
1578 "Can't read from backbuffer when readBuffer mode is NONE.");
1579 return false;
1582 auto effFormat = mOptions.alpha ? webgl::EffectiveFormat::RGBA8
1583 : webgl::EffectiveFormat::RGB8;
1585 *out_format = mFormatUsage->GetUsage(effFormat);
1586 MOZ_ASSERT(*out_format);
1588 *out_width = mDefaultFB->mSize.width;
1589 *out_height = mDefaultFB->mSize.height;
1590 return true;
1593 bool WebGLContext::BindDefaultFBForRead() {
1594 if (!ValidateAndInitFB(nullptr)) return false;
1596 if (!mDefaultFB->mSamples) {
1597 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
1598 return true;
1601 if (!mResolvedDefaultFB) {
1602 mResolvedDefaultFB =
1603 gl::MozFramebuffer::Create(gl, mDefaultFB->mSize, 0, false);
1604 if (!mResolvedDefaultFB) {
1605 gfxCriticalNote << FuncName() << ": Failed to create mResolvedDefaultFB.";
1606 return false;
1610 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
1611 BlitBackbufferToCurDriverFB();
1613 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
1614 return true;
1617 void WebGLContext::DoColorMask(Maybe<GLuint> i, const uint8_t bitmask) const {
1618 if (!IsExtensionEnabled(WebGLExtensionID::OES_draw_buffers_indexed)) {
1619 i = Nothing();
1621 const auto bs = std::bitset<4>(bitmask);
1622 if (i) {
1623 gl->fColorMaski(*i, bs[0], bs[1], bs[2], bs[3]);
1624 } else {
1625 gl->fColorMask(bs[0], bs[1], bs[2], bs[3]);
1629 ////////////////////////////////////////////////////////////////////////////////
1631 ScopedDrawCallWrapper::ScopedDrawCallWrapper(WebGLContext& webgl)
1632 : mWebGL(webgl) {
1633 uint8_t driverColorMask0 = mWebGL.mColorWriteMask0;
1634 bool driverDepthTest = mWebGL.mDepthTestEnabled;
1635 bool driverStencilTest = mWebGL.mStencilTestEnabled;
1636 const auto& fb = mWebGL.mBoundDrawFramebuffer;
1637 if (!fb) {
1638 if (mWebGL.mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE) {
1639 driverColorMask0 = 0; // Is this well-optimized enough for depth-first
1640 // rendering?
1641 } else {
1642 driverColorMask0 &= ~(uint8_t(mWebGL.mNeedsFakeNoAlpha) << 3);
1644 driverDepthTest &= !mWebGL.mNeedsFakeNoDepth;
1645 driverStencilTest &= !mWebGL.mNeedsFakeNoStencil;
1646 } else {
1647 if (mWebGL.mNeedsFakeNoStencil_UserFBs &&
1648 fb->DepthAttachment().HasAttachment() &&
1649 !fb->StencilAttachment().HasAttachment()) {
1650 driverStencilTest = false;
1654 const auto& gl = mWebGL.gl;
1655 mWebGL.DoColorMask(Some(0), driverColorMask0);
1656 if (mWebGL.mDriverDepthTest != driverDepthTest) {
1657 // "When disabled, the depth comparison and subsequent possible updates to
1658 // the
1659 // depth buffer value are bypassed and the fragment is passed to the next
1660 // operation." [GLES 3.0.5, p177]
1661 mWebGL.mDriverDepthTest = driverDepthTest;
1662 gl->SetEnabled(LOCAL_GL_DEPTH_TEST, mWebGL.mDriverDepthTest);
1664 if (mWebGL.mDriverStencilTest != driverStencilTest) {
1665 // "When disabled, the stencil test and associated modifications are not
1666 // made, and
1667 // the fragment is always passed." [GLES 3.0.5, p175]
1668 mWebGL.mDriverStencilTest = driverStencilTest;
1669 gl->SetEnabled(LOCAL_GL_STENCIL_TEST, mWebGL.mDriverStencilTest);
1673 ScopedDrawCallWrapper::~ScopedDrawCallWrapper() {
1674 if (mWebGL.mBoundDrawFramebuffer) return;
1676 mWebGL.mResolvedDefaultFB = nullptr;
1677 mWebGL.mShouldPresent = true;
1680 // -
1682 void WebGLContext::ScissorRect::Apply(gl::GLContext& gl) const {
1683 gl.fScissor(x, y, w, h);
1686 ////////////////////////////////////////
1688 IndexedBufferBinding::IndexedBufferBinding() : mRangeStart(0), mRangeSize(0) {}
1690 uint64_t IndexedBufferBinding::ByteCount() const {
1691 if (!mBufferBinding) return 0;
1693 uint64_t bufferSize = mBufferBinding->ByteLength();
1694 if (!mRangeSize) // BindBufferBase
1695 return bufferSize;
1697 if (mRangeStart >= bufferSize) return 0;
1698 bufferSize -= mRangeStart;
1700 return std::min(bufferSize, mRangeSize);
1703 ////////////////////////////////////////
1705 ScopedFBRebinder::~ScopedFBRebinder() {
1706 const auto fnName = [&](WebGLFramebuffer* fb) {
1707 return fb ? fb->mGLName : 0;
1710 const auto& gl = mWebGL->gl;
1711 if (mWebGL->IsWebGL2()) {
1712 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
1713 fnName(mWebGL->mBoundDrawFramebuffer));
1714 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
1715 fnName(mWebGL->mBoundReadFramebuffer));
1716 } else {
1717 MOZ_ASSERT(mWebGL->mBoundDrawFramebuffer == mWebGL->mBoundReadFramebuffer);
1718 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER,
1719 fnName(mWebGL->mBoundDrawFramebuffer));
1723 ////////////////////
1725 void DoBindBuffer(gl::GLContext& gl, const GLenum target,
1726 const WebGLBuffer* const buffer) {
1727 gl.fBindBuffer(target, buffer ? buffer->mGLName : 0);
1730 ////////////////////////////////////////
1732 bool Intersect(const int32_t srcSize, const int32_t read0,
1733 const int32_t readSize, int32_t* const out_intRead0,
1734 int32_t* const out_intWrite0, int32_t* const out_intSize) {
1735 MOZ_ASSERT(srcSize >= 0);
1736 MOZ_ASSERT(readSize >= 0);
1737 const auto read1 = int64_t(read0) + readSize;
1739 int32_t intRead0 = read0; // Clearly doesn't need validation.
1740 int64_t intWrite0 = 0;
1741 int64_t intSize = readSize;
1743 if (read1 <= 0 || read0 >= srcSize) {
1744 // Disjoint ranges.
1745 intSize = 0;
1746 } else {
1747 if (read0 < 0) {
1748 const auto diff = int64_t(0) - read0;
1749 MOZ_ASSERT(diff >= 0);
1750 intRead0 = 0;
1751 intWrite0 = diff;
1752 intSize -= diff;
1754 if (read1 > srcSize) {
1755 const auto diff = int64_t(read1) - srcSize;
1756 MOZ_ASSERT(diff >= 0);
1757 intSize -= diff;
1760 if (!CheckedInt<int32_t>(intWrite0).isValid() ||
1761 !CheckedInt<int32_t>(intSize).isValid()) {
1762 return false;
1766 *out_intRead0 = intRead0;
1767 *out_intWrite0 = intWrite0;
1768 *out_intSize = intSize;
1769 return true;
1772 // --
1774 uint64_t AvailGroups(const uint64_t totalAvailItems,
1775 const uint64_t firstItemOffset, const uint32_t groupSize,
1776 const uint32_t groupStride) {
1777 MOZ_ASSERT(groupSize && groupStride);
1778 MOZ_ASSERT(groupSize <= groupStride);
1780 if (totalAvailItems <= firstItemOffset) return 0;
1781 const size_t availItems = totalAvailItems - firstItemOffset;
1783 size_t availGroups = availItems / groupStride;
1784 const size_t tailItems = availItems % groupStride;
1785 if (tailItems >= groupSize) {
1786 availGroups += 1;
1788 return availGroups;
1791 ////////////////////////////////////////////////////////////////////////////////
1793 const char* WebGLContext::FuncName() const {
1794 const char* ret;
1795 if (MOZ_LIKELY(mFuncScope)) {
1796 ret = mFuncScope->mFuncName;
1797 } else {
1798 NS_WARNING("FuncScope not on stack!");
1799 ret = "<unknown function>";
1801 return ret;
1804 // -
1806 WebGLContext::FuncScope::FuncScope(const WebGLContext& webgl,
1807 const char* const funcName)
1808 : mWebGL(webgl), mFuncName(bool(mWebGL.mFuncScope) ? nullptr : funcName) {
1809 if (!mFuncName) return;
1810 mWebGL.mFuncScope = this;
1813 WebGLContext::FuncScope::~FuncScope() {
1814 if (mBindFailureGuard) {
1815 gfxCriticalError() << "mBindFailureGuard failure: Early exit from "
1816 << mWebGL.FuncName();
1819 if (!mFuncName) return;
1820 mWebGL.mFuncScope = nullptr;
1823 // --
1825 bool ClientWebGLContext::IsXRCompatible() const { return mXRCompatible; }
1827 already_AddRefed<dom::Promise> ClientWebGLContext::MakeXRCompatible(
1828 ErrorResult& aRv) {
1829 const FuncScope funcScope(*this, "MakeXRCompatible");
1830 nsCOMPtr<nsIGlobalObject> global = GetParentObject();
1831 if (!global) {
1832 aRv.ThrowInvalidAccessError(
1833 "Using a WebGL context that is not attached to either a canvas or an "
1834 "OffscreenCanvas");
1835 return nullptr;
1837 RefPtr<dom::Promise> promise = dom::Promise::Create(global, aRv);
1838 NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
1840 if (IsContextLost()) {
1841 promise->MaybeRejectWithInvalidStateError(
1842 "Can not make context XR compatible when context is already lost.");
1843 return promise.forget();
1846 // TODO: Bug 1580258 - WebGLContext.MakeXRCompatible needs to switch to
1847 // the device connected to the XR hardware
1848 // This should update `options` and lose+restore the context.
1849 mXRCompatible = true;
1850 promise->MaybeResolveWithUndefined();
1851 return promise.forget();
1854 // --
1856 webgl::AvailabilityRunnable& ClientWebGLContext::EnsureAvailabilityRunnable()
1857 const {
1858 if (!mAvailabilityRunnable) {
1859 mAvailabilityRunnable = new webgl::AvailabilityRunnable(this);
1860 auto forgettable = mAvailabilityRunnable;
1861 NS_DispatchToCurrentThread(forgettable.forget());
1863 return *mAvailabilityRunnable;
1866 webgl::AvailabilityRunnable::AvailabilityRunnable(
1867 const ClientWebGLContext* const webgl)
1868 : DiscardableRunnable("webgl::AvailabilityRunnable"), mWebGL(webgl) {}
1870 webgl::AvailabilityRunnable::~AvailabilityRunnable() {
1871 MOZ_ASSERT(mQueries.empty());
1872 MOZ_ASSERT(mSyncs.empty());
1875 nsresult webgl::AvailabilityRunnable::Run() {
1876 for (const auto& cur : mQueries) {
1877 if (!cur) continue;
1878 cur->mCanBeAvailable = true;
1880 mQueries.clear();
1882 for (const auto& cur : mSyncs) {
1883 if (!cur) continue;
1884 cur->mCanBeAvailable = true;
1886 mSyncs.clear();
1888 if (mWebGL) {
1889 mWebGL->mAvailabilityRunnable = nullptr;
1891 return NS_OK;
1894 // -
1896 void WebGLContext::GenerateErrorImpl(const GLenum errOrWarning,
1897 const std::string& text) const {
1898 auto err = errOrWarning;
1899 bool isPerfWarning = false;
1900 if (err == webgl::kErrorPerfWarning) {
1901 err = 0;
1902 isPerfWarning = true;
1905 if (err && mFuncScope && mFuncScope->mBindFailureGuard) {
1906 gfxCriticalError() << "mBindFailureGuard failure: Generating error "
1907 << EnumString(err) << ": " << text;
1910 /* ES2 section 2.5 "GL Errors" states that implementations can have
1911 * multiple 'flags', as errors might be caught in different parts of
1912 * a distributed implementation.
1913 * We're signing up as a distributed implementation here, with
1914 * separate flags for WebGL and the underlying GLContext.
1916 if (!mWebGLError) mWebGLError = err;
1918 if (!mHost) return; // Impossible?
1920 // -
1922 const auto ShouldWarn = [&]() {
1923 if (isPerfWarning) {
1924 return ShouldGeneratePerfWarnings();
1926 return ShouldGenerateWarnings();
1928 if (!ShouldWarn()) return;
1930 // -
1932 auto* pNumWarnings = &mWarningCount;
1933 const char* warningsType = "warnings";
1934 if (isPerfWarning) {
1935 pNumWarnings = &mNumPerfWarnings;
1936 warningsType = "perf warnings";
1939 if (isPerfWarning) {
1940 const auto perfText = std::string("WebGL perf warning: ") + text;
1941 mHost->JsWarning(perfText);
1942 } else {
1943 mHost->JsWarning(text);
1945 *pNumWarnings += 1;
1947 if (!ShouldWarn()) {
1948 const auto& msg = nsPrintfCString(
1949 "After reporting %i, no further %s will be reported for this WebGL "
1950 "context.",
1951 int(*pNumWarnings), warningsType);
1952 mHost->JsWarning(ToString(msg));
1956 // -
1958 Maybe<std::string> WebGLContext::GetString(const GLenum pname) const {
1959 const WebGLContext::FuncScope funcScope(*this, "getParameter");
1960 if (IsContextLost()) return {};
1962 const auto FromRaw = [](const char* const raw) -> Maybe<std::string> {
1963 if (!raw) return {};
1964 return Some(std::string(raw));
1967 switch (pname) {
1968 case LOCAL_GL_EXTENSIONS: {
1969 if (!gl->IsCoreProfile()) {
1970 const auto rawExt = (const char*)gl->fGetString(LOCAL_GL_EXTENSIONS);
1971 return FromRaw(rawExt);
1973 std::string ret;
1974 const auto& numExts = gl->GetIntAs<GLuint>(LOCAL_GL_NUM_EXTENSIONS);
1975 for (GLuint i = 0; i < numExts; i++) {
1976 const auto rawExt =
1977 (const char*)gl->fGetStringi(LOCAL_GL_EXTENSIONS, i);
1978 if (!rawExt) continue;
1980 if (i > 0) {
1981 ret += " ";
1983 ret += rawExt;
1985 return Some(std::move(ret));
1988 case LOCAL_GL_RENDERER:
1989 case LOCAL_GL_VENDOR:
1990 case LOCAL_GL_VERSION: {
1991 const auto raw = (const char*)gl->fGetString(pname);
1992 return FromRaw(raw);
1995 case dom::MOZ_debug_Binding::WSI_INFO: {
1996 nsCString info;
1997 gl->GetWSIInfo(&info);
1998 return Some(std::string(info.BeginReading()));
2001 default:
2002 ErrorInvalidEnumArg("pname", pname);
2003 return {};
2007 // ---------------------------------
2009 Maybe<webgl::IndexedName> webgl::ParseIndexed(const std::string& str) {
2010 static const std::regex kRegex("(.*)\\[([0-9]+)\\]");
2012 std::smatch match;
2013 if (!std::regex_match(str, match, kRegex)) return {};
2015 const auto index = std::stoull(match[2]);
2016 return Some(webgl::IndexedName{match[1], index});
2019 // ExplodeName("foo.bar[3].x") -> ["foo", ".", "bar", "[", "3", "]", ".", "x"]
2020 static std::vector<std::string> ExplodeName(const std::string& str) {
2021 std::vector<std::string> ret;
2023 static const std::regex kSep("[.[\\]]");
2025 auto itr = std::regex_token_iterator<decltype(str.begin())>(
2026 str.begin(), str.end(), kSep, {-1, 0});
2027 const auto end = decltype(itr)();
2029 for (; itr != end; ++itr) {
2030 const auto& part = itr->str();
2031 if (part.size()) {
2032 ret.push_back(part);
2035 return ret;
2040 // #define DUMP_MakeLinkResult
2042 webgl::LinkActiveInfo GetLinkActiveInfo(
2043 gl::GLContext& gl, const GLuint prog, const bool webgl2,
2044 const std::unordered_map<std::string, std::string>& nameUnmap) {
2045 webgl::LinkActiveInfo ret;
2046 [&]() {
2047 const auto fnGetProgramui = [&](const GLenum pname) {
2048 GLint ret = 0;
2049 gl.fGetProgramiv(prog, pname, &ret);
2050 return static_cast<uint32_t>(ret);
2053 std::vector<char> stringBuffer(1);
2054 const auto fnEnsureCapacity = [&](const GLenum pname) {
2055 const auto maxWithNull = fnGetProgramui(pname);
2056 if (maxWithNull > stringBuffer.size()) {
2057 stringBuffer.resize(maxWithNull);
2061 fnEnsureCapacity(LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH);
2062 fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH);
2063 if (webgl2) {
2064 fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH);
2065 fnEnsureCapacity(LOCAL_GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH);
2068 // -
2070 const auto fnUnmapName = [&](const std::string& mappedName) {
2071 const auto parts = ExplodeName(mappedName);
2073 std::ostringstream ret;
2074 for (const auto& part : parts) {
2075 const auto maybe = MaybeFind(nameUnmap, part);
2076 if (maybe) {
2077 ret << *maybe;
2078 } else {
2079 ret << part;
2082 return ret.str();
2085 // -
2088 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_ATTRIBUTES);
2089 ret.activeAttribs.reserve(count);
2090 for (const auto i : IntegerRange(count)) {
2091 GLsizei lengthWithoutNull = 0;
2092 GLint elemCount = 0; // `size`
2093 GLenum elemType = 0; // `type`
2094 gl.fGetActiveAttrib(prog, i, stringBuffer.size(), &lengthWithoutNull,
2095 &elemCount, &elemType, stringBuffer.data());
2096 if (!elemType) {
2097 const auto error = gl.fGetError();
2098 if (error != LOCAL_GL_CONTEXT_LOST) {
2099 gfxCriticalError() << "Failed to do glGetActiveAttrib: " << error;
2101 return;
2103 const auto mappedName =
2104 std::string(stringBuffer.data(), lengthWithoutNull);
2105 const auto userName = fnUnmapName(mappedName);
2107 auto loc = gl.fGetAttribLocation(prog, mappedName.c_str());
2108 if (mappedName.find("gl_") == 0) {
2109 // Bug 1328559: Appears problematic on ANGLE and OSX, but not Linux or
2110 // Win+GL.
2111 loc = -1;
2114 #ifdef DUMP_MakeLinkResult
2115 printf_stderr("[attrib %u/%u] @%i %s->%s\n", i, count, loc,
2116 userName.c_str(), mappedName.c_str());
2117 #endif
2118 webgl::ActiveAttribInfo info;
2119 info.elemType = elemType;
2120 info.elemCount = elemCount;
2121 info.name = userName;
2122 info.location = loc;
2123 info.baseType = webgl::ToAttribBaseType(info.elemType);
2124 ret.activeAttribs.push_back(std::move(info));
2128 // -
2131 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORMS);
2132 ret.activeUniforms.reserve(count);
2134 std::vector<GLint> blockIndexList(count, -1);
2135 std::vector<GLint> blockOffsetList(count, -1);
2136 std::vector<GLint> blockArrayStrideList(count, -1);
2137 std::vector<GLint> blockMatrixStrideList(count, -1);
2138 std::vector<GLint> blockIsRowMajorList(count, 0);
2140 if (webgl2 && count) {
2141 std::vector<GLuint> activeIndices;
2142 activeIndices.reserve(count);
2143 for (const auto i : IntegerRange(count)) {
2144 activeIndices.push_back(i);
2147 gl.fGetActiveUniformsiv(
2148 prog, activeIndices.size(), activeIndices.data(),
2149 LOCAL_GL_UNIFORM_BLOCK_INDEX, blockIndexList.data());
2151 gl.fGetActiveUniformsiv(prog, activeIndices.size(),
2152 activeIndices.data(), LOCAL_GL_UNIFORM_OFFSET,
2153 blockOffsetList.data());
2155 gl.fGetActiveUniformsiv(
2156 prog, activeIndices.size(), activeIndices.data(),
2157 LOCAL_GL_UNIFORM_ARRAY_STRIDE, blockArrayStrideList.data());
2159 gl.fGetActiveUniformsiv(
2160 prog, activeIndices.size(), activeIndices.data(),
2161 LOCAL_GL_UNIFORM_MATRIX_STRIDE, blockMatrixStrideList.data());
2163 gl.fGetActiveUniformsiv(
2164 prog, activeIndices.size(), activeIndices.data(),
2165 LOCAL_GL_UNIFORM_IS_ROW_MAJOR, blockIsRowMajorList.data());
2168 for (const auto i : IntegerRange(count)) {
2169 GLsizei lengthWithoutNull = 0;
2170 GLint elemCount = 0; // `size`
2171 GLenum elemType = 0; // `type`
2172 gl.fGetActiveUniform(prog, i, stringBuffer.size(), &lengthWithoutNull,
2173 &elemCount, &elemType, stringBuffer.data());
2174 if (!elemType) {
2175 const auto error = gl.fGetError();
2176 if (error != LOCAL_GL_CONTEXT_LOST) {
2177 gfxCriticalError() << "Failed to do glGetActiveUniform: " << error;
2179 return;
2181 auto mappedName = std::string(stringBuffer.data(), lengthWithoutNull);
2183 // Get true name
2185 auto baseMappedName = mappedName;
2187 const bool isArray = [&]() {
2188 const auto maybe = webgl::ParseIndexed(mappedName);
2189 if (maybe) {
2190 MOZ_ASSERT(maybe->index == 0);
2191 baseMappedName = std::move(maybe->name);
2192 return true;
2194 return false;
2195 }();
2197 const auto userName = fnUnmapName(mappedName);
2198 if (StartsWith(userName, "webgl_")) continue;
2200 // -
2202 webgl::ActiveUniformInfo info;
2203 info.elemType = elemType;
2204 info.elemCount = static_cast<uint32_t>(elemCount);
2205 info.name = userName;
2206 info.block_index = blockIndexList[i];
2207 info.block_offset = blockOffsetList[i];
2208 info.block_arrayStride = blockArrayStrideList[i];
2209 info.block_matrixStride = blockMatrixStrideList[i];
2210 info.block_isRowMajor = bool(blockIsRowMajorList[i]);
2212 #ifdef DUMP_MakeLinkResult
2213 printf_stderr("[uniform %u/%u] %s->%s\n", i + 1, count,
2214 userName.c_str(), mappedName.c_str());
2215 #endif
2217 // Get uniform locations
2219 auto locName = baseMappedName;
2220 const auto baseLength = locName.size();
2221 for (const auto i : IntegerRange(info.elemCount)) {
2222 if (isArray) {
2223 locName.erase(
2224 baseLength); // Erase previous [N], but retain capacity.
2225 locName += '[';
2226 locName += std::to_string(i);
2227 locName += ']';
2229 const auto loc = gl.fGetUniformLocation(prog, locName.c_str());
2230 if (loc != -1) {
2231 info.locByIndex[i] = static_cast<uint32_t>(loc);
2232 #ifdef DUMP_MakeLinkResult
2233 printf_stderr(" [%u] @%i\n", i, loc);
2234 #endif
2237 } // anon
2239 ret.activeUniforms.push_back(std::move(info));
2240 } // for i
2241 } // anon
2243 if (webgl2) {
2244 // -------------------------------------
2245 // active uniform blocks
2247 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORM_BLOCKS);
2248 ret.activeUniformBlocks.reserve(count);
2250 for (const auto i : IntegerRange(count)) {
2251 GLsizei lengthWithoutNull = 0;
2252 gl.fGetActiveUniformBlockName(prog, i, stringBuffer.size(),
2253 &lengthWithoutNull,
2254 stringBuffer.data());
2255 const auto mappedName =
2256 std::string(stringBuffer.data(), lengthWithoutNull);
2257 const auto userName = fnUnmapName(mappedName);
2259 // -
2261 auto info = webgl::ActiveUniformBlockInfo{userName};
2262 GLint val = 0;
2264 gl.fGetActiveUniformBlockiv(prog, i, LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE,
2265 &val);
2266 info.dataSize = static_cast<uint32_t>(val);
2268 gl.fGetActiveUniformBlockiv(
2269 prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &val);
2270 info.activeUniformIndices.resize(val);
2271 gl.fGetActiveUniformBlockiv(
2272 prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES,
2273 reinterpret_cast<GLint*>(info.activeUniformIndices.data()));
2275 gl.fGetActiveUniformBlockiv(
2276 prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER,
2277 &val);
2278 info.referencedByVertexShader = bool(val);
2280 gl.fGetActiveUniformBlockiv(
2281 prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER,
2282 &val);
2283 info.referencedByFragmentShader = bool(val);
2285 ret.activeUniformBlocks.push_back(std::move(info));
2286 } // for i
2287 } // anon
2289 // -------------------------------------
2290 // active tf varyings
2292 const auto count = fnGetProgramui(LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS);
2293 ret.activeTfVaryings.reserve(count);
2295 for (const auto i : IntegerRange(count)) {
2296 GLsizei lengthWithoutNull = 0;
2297 GLsizei elemCount = 0; // `size`
2298 GLenum elemType = 0; // `type`
2299 gl.fGetTransformFeedbackVarying(prog, i, stringBuffer.size(),
2300 &lengthWithoutNull, &elemCount,
2301 &elemType, stringBuffer.data());
2302 const auto mappedName =
2303 std::string(stringBuffer.data(), lengthWithoutNull);
2304 const auto userName = fnUnmapName(mappedName);
2306 ret.activeTfVaryings.push_back(
2307 {elemType, static_cast<uint32_t>(elemCount), userName});
2310 } // if webgl2
2311 }();
2312 return ret;
2315 nsCString ToCString(const std::string& s) {
2316 return nsCString(s.data(), s.size());
2319 webgl::CompileResult WebGLContext::GetCompileResult(
2320 const WebGLShader& shader) const {
2321 webgl::CompileResult ret;
2322 [&]() {
2323 ret.pending = false;
2324 const auto& info = shader.CompileResults();
2325 if (!info) return;
2326 if (!info->mValid) {
2327 ret.log = info->mInfoLog.c_str();
2328 return;
2330 // TODO: These could be large and should be made fallible.
2331 ret.translatedSource = ToCString(info->mObjectCode);
2332 ret.log = ToCString(shader.CompileLog());
2333 if (!shader.IsCompiled()) return;
2334 ret.success = true;
2335 }();
2336 return ret;
2339 webgl::LinkResult WebGLContext::GetLinkResult(const WebGLProgram& prog) const {
2340 webgl::LinkResult ret;
2341 [&]() {
2342 ret.pending = false; // Link status polling not yet implemented.
2343 ret.log = ToCString(prog.LinkLog());
2344 const auto& info = prog.LinkInfo();
2345 if (!info) return;
2346 ret.success = true;
2347 ret.active = info->active;
2348 ret.tfBufferMode = info->transformFeedbackBufferMode;
2349 }();
2350 return ret;
2353 // -
2355 GLint WebGLContext::GetFragDataLocation(const WebGLProgram& prog,
2356 const std::string& userName) const {
2357 const auto err = CheckGLSLVariableName(IsWebGL2(), userName);
2358 if (err) {
2359 GenerateError(err->type, "%s", err->info.c_str());
2360 return -1;
2363 const auto& info = prog.LinkInfo();
2364 if (!info) return -1;
2365 const auto& nameMap = info->nameMap;
2367 const auto parts = ExplodeName(userName);
2369 std::ostringstream ret;
2370 for (const auto& part : parts) {
2371 const auto maybe = MaybeFind(nameMap, part);
2372 if (maybe) {
2373 ret << *maybe;
2374 } else {
2375 ret << part;
2378 const auto mappedName = ret.str();
2380 if (gl->WorkAroundDriverBugs() && gl->IsMesa()) {
2381 // Mesa incorrectly generates INVALID_OPERATION for gl_ prefixes here.
2382 if (mappedName.find("gl_") == 0) {
2383 return -1;
2387 return gl->fGetFragDataLocation(prog.mGLName, mappedName.c_str());
2390 // -
2392 WebGLContextBoundObject::WebGLContextBoundObject(WebGLContext* webgl)
2393 : mContext(webgl) {}
2395 // -
2397 Result<webgl::ExplicitPixelPackingState, std::string>
2398 webgl::ExplicitPixelPackingState::ForUseWith(
2399 const webgl::PixelPackingState& stateOrZero, const GLenum target,
2400 const uvec3& subrectSize, const webgl::PackingInfo& pi,
2401 const Maybe<size_t> bytesPerRowStrideOverride) {
2402 auto state = stateOrZero;
2404 if (!IsTexTarget3D(target)) {
2405 state.skipImages = 0;
2406 state.imageHeight = 0;
2408 if (!state.rowLength) {
2409 state.rowLength = subrectSize.x;
2411 if (!state.imageHeight) {
2412 state.imageHeight = subrectSize.y;
2415 // -
2417 const auto mpii = PackingInfoInfo::For(pi);
2418 if (!mpii) {
2419 const auto text =
2420 nsPrintfCString("Invalid pi: { 0x%x, 0x%x}", pi.format, pi.type);
2421 return Err(mozilla::ToString(text));
2423 const auto pii = *mpii;
2424 const auto bytesPerPixel = pii.BytesPerPixel();
2426 const auto ElemsPerRowStride = [&]() {
2427 // GLES 3.0.6 p116:
2428 // p: `Elem*` pointer to the first element of the first row
2429 // N: row number, starting at 0
2430 // l: groups (pixels) per row
2431 // n: elements per group (pixel) in [1,2,3,4]
2432 // s: bytes per element in [1,2,4,8]
2433 // a: UNPACK_ALIGNMENT in [1,2,4,8]
2434 // Pointer to first element of Nth row: p + N*k
2435 // k(s>=a): n*l
2436 // k(s<a): a/s * ceil(s*n*l/a)
2437 const auto n__elemsPerPixel = pii.elementsPerPixel;
2438 const auto l__pixelsPerRow = state.rowLength;
2439 const auto a__alignment = state.alignmentInTypeElems;
2440 const auto s__bytesPerElem = pii.bytesPerElement;
2442 const auto nl = CheckedInt<size_t>(n__elemsPerPixel) * l__pixelsPerRow;
2443 auto k__elemsPerRowStride = nl;
2444 if (s__bytesPerElem < a__alignment) {
2445 // k = a/s * ceil(s*n*l/a)
2446 k__elemsPerRowStride =
2447 a__alignment / s__bytesPerElem *
2448 ((nl * s__bytesPerElem + a__alignment - 1) / a__alignment);
2450 return k__elemsPerRowStride;
2453 // -
2455 if (bytesPerRowStrideOverride) { // E.g. HTMLImageElement
2456 const size_t bytesPerRowStrideRequired = *bytesPerRowStrideOverride;
2457 // We have to reverse-engineer an ALIGNMENT and ROW_LENGTH for this.
2459 // GL does this in elems not bytes, so we should too.
2460 MOZ_RELEASE_ASSERT(bytesPerRowStrideRequired % pii.bytesPerElement == 0);
2461 const auto elemsPerRowStrideRequired =
2462 bytesPerRowStrideRequired / pii.bytesPerElement;
2464 state.rowLength = bytesPerRowStrideRequired / bytesPerPixel;
2465 state.alignmentInTypeElems = 8;
2466 while (true) {
2467 const auto elemPerRowStride = ElemsPerRowStride();
2468 if (elemPerRowStride.isValid() &&
2469 elemPerRowStride.value() == elemsPerRowStrideRequired) {
2470 break;
2472 state.alignmentInTypeElems /= 2;
2473 if (!state.alignmentInTypeElems) {
2474 const auto text = nsPrintfCString(
2475 "No valid alignment found: pi: { 0x%x, 0x%x},"
2476 " bytesPerRowStrideRequired: %zu",
2477 pi.format, pi.type, bytesPerRowStrideRequired);
2478 return Err(mozilla::ToString(text));
2483 // -
2485 const auto usedPixelsPerRow =
2486 CheckedInt<size_t>(state.skipPixels) + subrectSize.x;
2487 if (!usedPixelsPerRow.isValid() ||
2488 usedPixelsPerRow.value() > state.rowLength) {
2489 return Err("UNPACK_SKIP_PIXELS + width > UNPACK_ROW_LENGTH.");
2492 if (subrectSize.y > state.imageHeight) {
2493 return Err("height > UNPACK_IMAGE_HEIGHT.");
2495 // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
2497 // -
2499 auto metrics = Metrics{};
2501 metrics.usedSize = subrectSize;
2502 metrics.bytesPerPixel = BytesPerPixel(pi);
2504 // -
2506 const auto elemsPerRowStride = ElemsPerRowStride();
2507 const auto bytesPerRowStride = pii.bytesPerElement * elemsPerRowStride;
2508 if (!bytesPerRowStride.isValid()) {
2509 return Err("ROW_LENGTH or width too large for packing.");
2511 metrics.bytesPerRowStride = bytesPerRowStride.value();
2513 // -
2515 const auto firstImageTotalRows =
2516 CheckedInt<size_t>(state.skipRows) + metrics.usedSize.y;
2517 const auto totalImages =
2518 CheckedInt<size_t>(state.skipImages) + metrics.usedSize.z;
2519 auto totalRows = CheckedInt<size_t>(0);
2520 if (metrics.usedSize.y && metrics.usedSize.z) {
2521 totalRows = firstImageTotalRows + state.imageHeight * (totalImages - 1);
2523 if (!totalRows.isValid()) {
2524 return Err(
2525 "SKIP_ROWS, height, IMAGE_HEIGHT, SKIP_IMAGES, or depth too large for "
2526 "packing.");
2528 metrics.totalRows = totalRows.value();
2530 // -
2532 const auto totalBytesStrided = totalRows * metrics.bytesPerRowStride;
2533 if (!totalBytesStrided.isValid()) {
2534 return Err("Total byte count too large for packing.");
2536 metrics.totalBytesStrided = totalBytesStrided.value();
2538 metrics.totalBytesUsed = metrics.totalBytesStrided;
2539 if (metrics.usedSize.x && metrics.usedSize.y && metrics.usedSize.z) {
2540 const auto usedBytesPerRow =
2541 usedPixelsPerRow.value() * metrics.bytesPerPixel;
2542 metrics.totalBytesUsed -= metrics.bytesPerRowStride;
2543 metrics.totalBytesUsed += usedBytesPerRow;
2546 // -
2548 return {{state, metrics}};
2551 } // namespace mozilla