no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / canvas / WebGLContext.cpp
bloba56135a8bc4a6246c59c0b9c5e89423548392bea
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 XP_WIN
90 # include "WGLLibrary.h"
91 #endif
93 // Generated
94 #include "mozilla/dom/WebGLRenderingContextBinding.h"
96 namespace mozilla {
98 WebGLContextOptions::WebGLContextOptions() {
99 // Set default alpha state based on preference.
100 alpha = !StaticPrefs::webgl_default_no_alpha();
101 antialias = StaticPrefs::webgl_default_antialias();
104 StaticMutex WebGLContext::sLruMutex;
105 std::list<WebGLContext*> WebGLContext::sLru;
107 WebGLContext::LruPosition::LruPosition() {
108 StaticMutexAutoLock lock(sLruMutex);
109 mItr = sLru.end();
110 } // NOLINT
112 WebGLContext::LruPosition::LruPosition(WebGLContext& context) {
113 StaticMutexAutoLock lock(sLruMutex);
114 mItr = sLru.insert(sLru.end(), &context);
117 void WebGLContext::LruPosition::AssignLocked(WebGLContext& aContext) {
118 ResetLocked();
119 mItr = sLru.insert(sLru.end(), &aContext);
122 void WebGLContext::LruPosition::ResetLocked() {
123 const auto end = sLru.end();
124 if (mItr != end) {
125 sLru.erase(mItr);
126 mItr = end;
130 void WebGLContext::LruPosition::Reset() {
131 StaticMutexAutoLock lock(sLruMutex);
132 ResetLocked();
135 bool WebGLContext::LruPosition::IsInsertedLocked() const {
136 return mItr != sLru.end();
139 WebGLContext::WebGLContext(HostWebGLContext& host,
140 const webgl::InitContextDesc& desc)
141 : gl(mGL_OnlyClearInDestroyResourcesAndContext), // const reference
142 mHost(&host),
143 mResistFingerprinting(desc.resistFingerprinting),
144 mOptions(desc.options),
145 mPrincipalKey(desc.principalKey),
146 mContextLossHandler(this),
147 mRequestedSize(desc.size) {
148 host.mContext = this;
149 const FuncScope funcScope(*this, "<Create>");
152 WebGLContext::~WebGLContext() { DestroyResourcesAndContext(); }
154 void WebGLContext::DestroyResourcesAndContext() {
155 if (mRemoteTextureOwner) {
156 // Clean up any remote textures registered for framebuffer swap chains.
157 mRemoteTextureOwner->UnregisterAllTextureOwners();
158 mRemoteTextureOwner = nullptr;
161 if (!gl) return;
163 mDefaultFB = nullptr;
164 mResolvedDefaultFB = nullptr;
166 mBound2DTextures.Clear();
167 mBoundCubeMapTextures.Clear();
168 mBound3DTextures.Clear();
169 mBound2DArrayTextures.Clear();
170 mBoundSamplers.Clear();
171 mBoundArrayBuffer = nullptr;
172 mBoundCopyReadBuffer = nullptr;
173 mBoundCopyWriteBuffer = nullptr;
174 mBoundPixelPackBuffer = nullptr;
175 mBoundPixelUnpackBuffer = nullptr;
176 mBoundTransformFeedbackBuffer = nullptr;
177 mBoundUniformBuffer = nullptr;
178 mCurrentProgram = nullptr;
179 mActiveProgramLinkInfo = nullptr;
180 mBoundDrawFramebuffer = nullptr;
181 mBoundReadFramebuffer = nullptr;
182 mBoundVertexArray = nullptr;
183 mDefaultVertexArray = nullptr;
184 mBoundTransformFeedback = nullptr;
185 mDefaultTransformFeedback = nullptr;
187 mQuerySlot_SamplesPassed = nullptr;
188 mQuerySlot_TFPrimsWritten = nullptr;
189 mQuerySlot_TimeElapsed = nullptr;
191 mIndexedUniformBufferBindings.clear();
193 //////
195 if (mEmptyTFO) {
196 gl->fDeleteTransformFeedbacks(1, &mEmptyTFO);
197 mEmptyTFO = 0;
200 //////
202 if (mFakeVertexAttrib0BufferObject) {
203 gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject);
204 mFakeVertexAttrib0BufferObject = 0;
207 // disable all extensions except "WEBGL_lose_context". see bug #927969
208 // spec: http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
209 for (size_t i = 0; i < size_t(WebGLExtensionID::Max); ++i) {
210 WebGLExtensionID extension = WebGLExtensionID(i);
211 if (extension == WebGLExtensionID::WEBGL_lose_context) continue;
212 mExtensions[extension] = nullptr;
215 // We just got rid of everything, so the context had better
216 // have been going away.
217 if (gl::GLContext::ShouldSpew()) {
218 printf_stderr("--- WebGL context destroyed: %p\n", gl.get());
221 MOZ_ASSERT(gl);
222 gl->MarkDestroyed();
223 mGL_OnlyClearInDestroyResourcesAndContext = nullptr;
224 MOZ_ASSERT(!gl);
227 void ClientWebGLContext::MarkCanvasDirty() {
228 if (!mCanvasElement && !mOffscreenCanvas) return;
230 mFrameCaptureState = FrameCaptureState::DIRTY;
232 if (mIsCanvasDirty) return;
233 mIsCanvasDirty = true;
235 if (mCanvasElement) {
236 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
237 mCanvasElement->InvalidateCanvasContent(nullptr);
238 } else if (mOffscreenCanvas) {
239 mOffscreenCanvas->QueueCommitToCompositor();
243 void WebGLContext::OnMemoryPressure() {
244 bool shouldLoseContext = mLoseContextOnMemoryPressure;
246 if (!mCanLoseContextInForeground &&
247 ProcessPriorityManager::CurrentProcessIsForeground()) {
248 shouldLoseContext = false;
251 if (shouldLoseContext) LoseContext();
254 // --
256 bool WebGLContext::CreateAndInitGL(
257 bool forceEnabled, std::vector<FailureReason>* const out_failReasons) {
258 const FuncScope funcScope(*this, "<Create>");
260 // WebGL2 is separately blocked:
261 if (IsWebGL2() && !forceEnabled) {
262 FailureReason reason;
263 if (!gfx::gfxVars::AllowWebgl2()) {
264 reason.info =
265 "AllowWebgl2:false restricts context creation on this system.";
266 out_failReasons->push_back(reason);
267 GenerateWarning("%s", reason.info.BeginReading());
268 return false;
272 auto flags = gl::CreateContextFlags::PREFER_ROBUSTNESS;
274 if (StaticPrefs::webgl_gl_khr_no_error()) {
275 flags |= gl::CreateContextFlags::NO_VALIDATION;
278 // -
280 if (StaticPrefs::webgl_forbid_hardware()) {
281 flags |= gl::CreateContextFlags::FORBID_HARDWARE;
283 if (StaticPrefs::webgl_forbid_software()) {
284 flags |= gl::CreateContextFlags::FORBID_SOFTWARE;
287 if (forceEnabled) {
288 flags &= ~gl::CreateContextFlags::FORBID_HARDWARE;
289 flags &= ~gl::CreateContextFlags::FORBID_SOFTWARE;
292 if ((flags & gl::CreateContextFlags::FORBID_HARDWARE) &&
293 (flags & gl::CreateContextFlags::FORBID_SOFTWARE)) {
294 FailureReason reason;
295 reason.info = "Both hardware and software were forbidden by config.";
296 out_failReasons->push_back(reason);
297 GenerateWarning("%s", reason.info.BeginReading());
298 return false;
301 // -
303 if (StaticPrefs::webgl_cgl_multithreaded()) {
304 flags |= gl::CreateContextFlags::PREFER_MULTITHREADED;
307 if (IsWebGL2()) {
308 flags |= gl::CreateContextFlags::PREFER_ES3;
309 } else {
310 // Request and prefer ES2 context for WebGL1.
311 flags |= gl::CreateContextFlags::PREFER_EXACT_VERSION;
313 if (!StaticPrefs::webgl_1_allow_core_profiles()) {
314 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
319 auto powerPref = mOptions.powerPreference;
321 // If "Use hardware acceleration when available" option is disabled:
322 if (!gfx::gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING)) {
323 powerPref = dom::WebGLPowerPreference::Low_power;
326 const auto overrideVal = StaticPrefs::webgl_power_preference_override();
327 if (overrideVal > 0) {
328 powerPref = dom::WebGLPowerPreference::High_performance;
329 } else if (overrideVal < 0) {
330 powerPref = dom::WebGLPowerPreference::Low_power;
333 if (powerPref == dom::WebGLPowerPreference::High_performance) {
334 flags |= gl::CreateContextFlags::HIGH_POWER;
338 if (!gfx::gfxVars::WebglAllowCoreProfile()) {
339 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
342 // --
344 const bool useEGL = PR_GetEnv("MOZ_WEBGL_FORCE_EGL");
346 bool tryNativeGL = true;
347 bool tryANGLE = false;
349 #ifdef XP_WIN
350 tryNativeGL = false;
351 tryANGLE = true;
353 if (StaticPrefs::webgl_disable_wgl()) {
354 tryNativeGL = false;
357 if (StaticPrefs::webgl_disable_angle() ||
358 PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL") || useEGL) {
359 tryNativeGL = true;
360 tryANGLE = false;
362 #endif
364 if (tryNativeGL && !forceEnabled) {
365 FailureReason reason;
366 if (!gfx::gfxVars::WebglAllowWindowsNativeGl()) {
367 reason.info =
368 "WebglAllowWindowsNativeGl:false restricts context creation on this "
369 "system.";
371 out_failReasons->push_back(reason);
373 GenerateWarning("%s", reason.info.BeginReading());
374 tryNativeGL = false;
378 // --
380 using fnCreateT = decltype(gl::GLContextProviderEGL::CreateHeadless);
381 const auto fnCreate = [&](fnCreateT* const pfnCreate,
382 const char* const info) {
383 nsCString failureId;
384 const RefPtr<gl::GLContext> gl = pfnCreate({flags}, &failureId);
385 if (!gl) {
386 out_failReasons->push_back(WebGLContext::FailureReason(failureId, info));
388 return gl;
391 const auto newGL = [&]() -> RefPtr<gl::GLContext> {
392 if (tryNativeGL) {
393 if (useEGL)
394 return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "useEGL");
396 const auto ret =
397 fnCreate(&gl::GLContextProvider::CreateHeadless, "tryNativeGL");
398 if (ret) return ret;
401 if (tryANGLE) {
402 return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "tryANGLE");
404 return nullptr;
405 }();
407 if (!newGL) {
408 out_failReasons->push_back(
409 FailureReason("FEATURE_FAILURE_WEBGL_EXHAUSTED_DRIVERS",
410 "Exhausted GL driver options."));
411 return false;
414 // --
416 FailureReason reason;
418 mGL_OnlyClearInDestroyResourcesAndContext = newGL;
419 MOZ_RELEASE_ASSERT(gl);
420 if (!InitAndValidateGL(&reason)) {
421 DestroyResourcesAndContext();
422 MOZ_RELEASE_ASSERT(!gl);
424 // The fail reason here should be specific enough for now.
425 out_failReasons->push_back(reason);
426 return false;
429 const auto val = StaticPrefs::webgl_debug_incomplete_tex_color();
430 if (val) {
431 mIncompleteTexOverride.reset(new gl::Texture(*gl));
432 const gl::ScopedBindTexture autoBind(gl, mIncompleteTexOverride->name);
433 const auto heapVal = std::make_unique<uint32_t>(val);
434 gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 1, 1, 0,
435 LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, heapVal.get());
438 return true;
441 // Fallback for resizes:
443 bool WebGLContext::EnsureDefaultFB() {
444 if (mDefaultFB) {
445 MOZ_ASSERT(*uvec2::FromSize(mDefaultFB->mSize) == mRequestedSize);
446 return true;
449 const bool depthStencil = mOptions.depth || mOptions.stencil;
450 auto attemptSize = gfx::IntSize{mRequestedSize.x, mRequestedSize.y};
452 while (attemptSize.width || attemptSize.height) {
453 attemptSize.width = std::max(attemptSize.width, 1);
454 attemptSize.height = std::max(attemptSize.height, 1);
456 [&]() {
457 if (mOptions.antialias) {
458 MOZ_ASSERT(!mDefaultFB);
459 mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, mMsaaSamples,
460 depthStencil);
461 if (mDefaultFB) return;
462 if (mOptionsFrozen) return;
465 MOZ_ASSERT(!mDefaultFB);
466 mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, 0, depthStencil);
467 }();
469 if (mDefaultFB) break;
471 attemptSize.width /= 2;
472 attemptSize.height /= 2;
475 if (!mDefaultFB) {
476 GenerateWarning("Backbuffer resize failed. Losing context.");
477 LoseContext();
478 return false;
481 mDefaultFB_IsInvalid = true;
483 const auto actualSize = *uvec2::FromSize(mDefaultFB->mSize);
484 if (actualSize != mRequestedSize) {
485 GenerateWarning(
486 "Requested size %ux%u was too large, but resize"
487 " to %ux%u succeeded.",
488 mRequestedSize.x, mRequestedSize.y, actualSize.x, actualSize.y);
490 mRequestedSize = actualSize;
491 return true;
494 void WebGLContext::Resize(uvec2 requestedSize) {
495 // Zero-sized surfaces can cause problems.
496 if (!requestedSize.x) {
497 requestedSize.x = 1;
499 if (!requestedSize.y) {
500 requestedSize.y = 1;
503 // Kill our current default fb(s), for later lazy allocation.
504 mRequestedSize = requestedSize;
505 mDefaultFB = nullptr;
506 mResetLayer = true; // New size means new Layer.
509 UniquePtr<webgl::FormatUsageAuthority> WebGLContext::CreateFormatUsage(
510 gl::GLContext* gl) const {
511 return webgl::FormatUsageAuthority::CreateForWebGL1(gl);
514 /*static*/
515 RefPtr<WebGLContext> WebGLContext::Create(HostWebGLContext& host,
516 const webgl::InitContextDesc& desc,
517 webgl::InitContextResult* const out) {
518 AUTO_PROFILER_LABEL("WebGLContext::Create", GRAPHICS);
519 nsCString failureId = "FEATURE_FAILURE_WEBGL_UNKOWN"_ns;
520 const bool forceEnabled = StaticPrefs::webgl_force_enabled();
521 ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
523 auto res = [&]() -> Result<RefPtr<WebGLContext>, std::string> {
524 bool disabled = StaticPrefs::webgl_disabled();
526 // TODO: When we have software webgl support we should use that instead.
527 disabled |= gfxPlatform::InSafeMode();
529 if (disabled) {
530 if (gfxPlatform::InSafeMode()) {
531 failureId = "FEATURE_FAILURE_WEBGL_SAFEMODE"_ns;
532 } else {
533 failureId = "FEATURE_FAILURE_WEBGL_DISABLED"_ns;
535 return Err("WebGL is currently disabled.");
538 // Alright, now let's start trying.
540 RefPtr<WebGLContext> webgl;
541 if (desc.isWebgl2) {
542 webgl = new WebGL2Context(host, desc);
543 } else {
544 webgl = new WebGLContext(host, desc);
547 MOZ_ASSERT(!webgl->gl);
548 std::vector<FailureReason> failReasons;
549 if (!webgl->CreateAndInitGL(forceEnabled, &failReasons)) {
550 nsCString text("WebGL creation failed: ");
551 for (const auto& cur : failReasons) {
552 // Don't try to accumulate using an empty key if |cur.key| is empty.
553 if (cur.key.IsEmpty()) {
554 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID,
555 "FEATURE_FAILURE_REASON_UNKNOWN"_ns);
556 } else {
557 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, cur.key);
560 const auto str = nsPrintfCString("\n* %s (%s)", cur.info.BeginReading(),
561 cur.key.BeginReading());
562 text.Append(str);
564 failureId = "FEATURE_FAILURE_REASON"_ns;
565 return Err(text.BeginReading());
567 MOZ_ASSERT(webgl->gl);
569 if (desc.options.failIfMajorPerformanceCaveat) {
570 if (webgl->gl->IsWARP()) {
571 failureId = "FEATURE_FAILURE_WEBGL_PERF_WARP"_ns;
572 return Err(
573 "failIfMajorPerformanceCaveat: Driver is not"
574 " hardware-accelerated.");
577 #ifdef XP_WIN
578 if (webgl->gl->GetContextType() == gl::GLContextType::WGL &&
579 !gl::sWGLLib.HasDXInterop2()) {
580 failureId = "FEATURE_FAILURE_WEBGL_DXGL_INTEROP2"_ns;
581 return Err("failIfMajorPerformanceCaveat: WGL without DXGLInterop2.");
583 #endif
586 const FuncScope funcScope(*webgl, "getContext/restoreContext");
588 MOZ_ASSERT(!webgl->mDefaultFB);
589 if (!webgl->EnsureDefaultFB()) {
590 MOZ_ASSERT(!webgl->mDefaultFB);
591 MOZ_ASSERT(webgl->IsContextLost());
592 failureId = "FEATURE_FAILURE_WEBGL_BACKBUFFER"_ns;
593 return Err("Initializing WebGL backbuffer failed.");
596 return webgl;
597 }();
598 if (res.isOk()) {
599 failureId = "SUCCESS"_ns;
601 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, failureId);
603 if (!res.isOk()) {
604 out->error = res.unwrapErr();
605 return nullptr;
607 const auto webgl = res.unwrap();
609 // Update our internal stuff:
610 webgl->FinishInit();
612 reporter.SetSuccessful();
613 if (gl::GLContext::ShouldSpew()) {
614 printf_stderr("--- WebGL context created: %p\n", webgl.get());
617 // -
619 const auto UploadableSdTypes = [&]() {
620 webgl::EnumMask<layers::SurfaceDescriptor::Type> types;
621 types[layers::SurfaceDescriptor::TSurfaceDescriptorBuffer] = true;
622 if (webgl->gl->IsANGLE()) {
623 types[layers::SurfaceDescriptor::TSurfaceDescriptorD3D10] = true;
624 types[layers::SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr] = true;
626 if (kIsMacOS) {
627 types[layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface] = true;
629 if (kIsAndroid) {
630 types[layers::SurfaceDescriptor::TSurfaceTextureDescriptor] = true;
632 if (kIsLinux) {
633 types[layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf] = true;
635 return types;
638 // -
640 out->options = webgl->mOptions;
641 out->limits = *webgl->mLimits;
642 out->uploadableSdTypes = UploadableSdTypes();
643 out->vendor = webgl->gl->Vendor();
645 return webgl;
648 void WebGLContext::FinishInit() {
649 mOptions.antialias &= bool(mDefaultFB->mSamples);
651 if (!mOptions.alpha) {
652 // We always have alpha.
653 mNeedsFakeNoAlpha = true;
656 if (mOptions.depth || mOptions.stencil) {
657 // We always have depth+stencil if we have either.
658 if (!mOptions.depth) {
659 mNeedsFakeNoDepth = true;
661 if (!mOptions.stencil) {
662 mNeedsFakeNoStencil = true;
666 mResetLayer = true;
667 mOptionsFrozen = true;
669 //////
670 // Initial setup.
672 gl->mImplicitMakeCurrent = true;
673 gl->mElideDuplicateBindFramebuffers = true;
675 const auto& size = mDefaultFB->mSize;
677 mViewportX = mViewportY = 0;
678 mViewportWidth = size.width;
679 mViewportHeight = size.height;
680 gl->fViewport(mViewportX, mViewportY, mViewportWidth, mViewportHeight);
682 mScissorRect = {0, 0, size.width, size.height};
683 mScissorRect.Apply(*gl);
685 //////
686 // Check everything
688 AssertCachedBindings();
689 AssertCachedGlobalState();
691 mShouldPresent = true;
693 //////
695 gl->ResetSyncCallCount("WebGLContext Initialization");
696 LoseLruContextIfLimitExceeded();
699 void WebGLContext::SetCompositableHost(
700 RefPtr<layers::CompositableHost>& aCompositableHost) {
701 mCompositableHost = aCompositableHost;
704 void WebGLContext::BumpLruLocked() {
705 if (!mIsContextLost && !mPendingContextLoss) {
706 mLruPosition.AssignLocked(*this);
707 } else {
708 MOZ_ASSERT(!mLruPosition.IsInsertedLocked());
712 void WebGLContext::BumpLru() {
713 StaticMutexAutoLock lock(sLruMutex);
714 BumpLruLocked();
717 void WebGLContext::LoseLruContextIfLimitExceeded() {
718 StaticMutexAutoLock lock(sLruMutex);
720 const auto maxContexts = std::max(1u, StaticPrefs::webgl_max_contexts());
721 const auto maxContextsPerPrincipal =
722 std::max(1u, StaticPrefs::webgl_max_contexts_per_principal());
724 // it's important to update the index on a new context before losing old
725 // contexts, otherwise new unused contexts would all have index 0 and we
726 // couldn't distinguish older ones when choosing which one to lose first.
727 BumpLruLocked();
730 size_t forPrincipal = 0;
731 for (const auto& context : sLru) {
732 if (context->mPrincipalKey == mPrincipalKey) {
733 forPrincipal += 1;
737 while (forPrincipal > maxContextsPerPrincipal) {
738 const auto text = nsPrintfCString(
739 "Exceeded %u live WebGL contexts for this principal, losing the "
740 "least recently used one.",
741 maxContextsPerPrincipal);
742 mHost->JsWarning(ToString(text));
744 for (const auto& context : sLru) {
745 if (context->mPrincipalKey == mPrincipalKey) {
746 MOZ_ASSERT(context != this);
747 context->LoseContextLruLocked(webgl::ContextLossReason::None);
748 forPrincipal -= 1;
749 break;
755 auto total = sLru.size();
756 while (total > maxContexts) {
757 const auto text = nsPrintfCString(
758 "Exceeded %u live WebGL contexts, losing the least "
759 "recently used one.",
760 maxContexts);
761 mHost->JsWarning(ToString(text));
763 const auto& context = sLru.front();
764 MOZ_ASSERT(context != this);
765 context->LoseContextLruLocked(webgl::ContextLossReason::None);
766 total -= 1;
770 // -
772 namespace webgl {
774 ScopedPrepForResourceClear::ScopedPrepForResourceClear(
775 const WebGLContext& webgl_)
776 : webgl(webgl_) {
777 const auto& gl = webgl.gl;
779 if (webgl.mScissorTestEnabled) {
780 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
782 if (webgl.mRasterizerDiscardEnabled) {
783 gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD);
786 // "The clear operation always uses the front stencil write mask
787 // when clearing the stencil buffer."
788 webgl.DoColorMask(Some(0), 0b1111);
789 gl->fDepthMask(true);
790 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff);
792 gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
793 gl->fClearDepth(1.0f); // Depth formats are always cleared to 1.0f, not 0.0f.
794 gl->fClearStencil(0);
797 ScopedPrepForResourceClear::~ScopedPrepForResourceClear() {
798 const auto& gl = webgl.gl;
800 if (webgl.mScissorTestEnabled) {
801 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
803 if (webgl.mRasterizerDiscardEnabled) {
804 gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
807 webgl.DoColorMask(Some(0), webgl.mColorWriteMask0);
808 gl->fDepthMask(webgl.mDepthWriteMask);
809 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, webgl.mStencilWriteMaskFront);
811 gl->fClearColor(webgl.mColorClearValue[0], webgl.mColorClearValue[1],
812 webgl.mColorClearValue[2], webgl.mColorClearValue[3]);
813 gl->fClearDepth(webgl.mDepthClearValue);
814 gl->fClearStencil(webgl.mStencilClearValue);
817 } // namespace webgl
819 // -
821 void WebGLContext::OnEndOfFrame() {
822 if (StaticPrefs::webgl_perf_spew_frame_allocs()) {
823 GeneratePerfWarning("[webgl.perf.spew-frame-allocs] %" PRIu64
824 " data allocations this frame.",
825 mDataAllocGLCallCount);
827 mDataAllocGLCallCount = 0;
828 gl->ResetSyncCallCount("WebGLContext PresentScreenBuffer");
830 mDrawCallsSinceLastFlush = 0;
832 BumpLru();
835 void WebGLContext::BlitBackbufferToCurDriverFB(
836 WebGLFramebuffer* const srcAsWebglFb,
837 const gl::MozFramebuffer* const srcAsMozFb, bool srcIsBGRA) const {
838 // BlitFramebuffer ignores ColorMask().
840 if (mScissorTestEnabled) {
841 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
844 [&]() {
845 // If a MozFramebuffer is supplied, ensure that a WebGLFramebuffer is not
846 // used since it might not have completeness info, while the MozFramebuffer
847 // can still supply the needed information.
848 MOZ_ASSERT(!(srcAsMozFb && srcAsWebglFb));
849 const auto* mozFb = srcAsMozFb ? srcAsMozFb : mDefaultFB.get();
850 GLuint fbo = 0;
851 gfx::IntSize size;
852 if (srcAsWebglFb) {
853 fbo = srcAsWebglFb->mGLName;
854 const auto* info = srcAsWebglFb->GetCompletenessInfo();
855 MOZ_ASSERT(info);
856 size = gfx::IntSize(info->width, info->height);
857 } else {
858 fbo = mozFb->mFB;
859 size = mozFb->mSize;
862 // If no format conversion is necessary, then attempt to directly blit
863 // between framebuffers. Otherwise, if we need to convert to RGBA from
864 // the source format, then we will need to use the texture blit path
865 // below.
866 if (!srcIsBGRA) {
867 if (gl->IsSupported(gl::GLFeature::framebuffer_blit)) {
868 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
869 gl->fBlitFramebuffer(0, 0, size.width, size.height, 0, 0, size.width,
870 size.height, LOCAL_GL_COLOR_BUFFER_BIT,
871 LOCAL_GL_NEAREST);
872 return;
874 if (mDefaultFB->mSamples &&
875 gl->IsExtensionSupported(
876 gl::GLContext::APPLE_framebuffer_multisample)) {
877 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
878 gl->fResolveMultisampleFramebufferAPPLE();
879 return;
883 GLuint colorTex = 0;
884 if (srcAsWebglFb) {
885 const auto& attach = srcAsWebglFb->ColorAttachment0();
886 MOZ_ASSERT(attach.Texture());
887 colorTex = attach.Texture()->mGLName;
888 } else {
889 colorTex = mozFb->ColorTex();
892 // DrawBlit handles ColorMask itself.
893 gl->BlitHelper()->DrawBlitTextureToFramebuffer(
894 colorTex, size, size, LOCAL_GL_TEXTURE_2D, srcIsBGRA);
895 }();
897 if (mScissorTestEnabled) {
898 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
902 // -
904 template <typename T, typename... Args>
905 constexpr auto MakeArray(Args... args) -> std::array<T, sizeof...(Args)> {
906 return {{static_cast<T>(args)...}};
909 inline gfx::ColorSpace2 ToColorSpace2(const WebGLContextOptions& options) {
910 auto ret = gfx::ColorSpace2::UNKNOWN;
911 if (true) {
912 ret = gfx::ColorSpace2::SRGB;
914 if (!options.ignoreColorSpace) {
915 ret = gfx::ToColorSpace2(options.colorSpace);
917 return ret;
920 // -
922 // For an overview of how WebGL compositing works, see:
923 // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
924 bool WebGLContext::PresentInto(gl::SwapChain& swapChain) {
925 OnEndOfFrame();
927 if (!ValidateAndInitFB(nullptr)) return false;
930 const auto colorSpace = ToColorSpace2(mOptions);
931 auto presenter = swapChain.Acquire(mDefaultFB->mSize, colorSpace);
932 if (!presenter) {
933 GenerateWarning("Swap chain surface creation failed.");
934 LoseContext();
935 return false;
938 const auto destFb = presenter->Fb();
939 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
941 BlitBackbufferToCurDriverFB();
943 if (!mOptions.preserveDrawingBuffer) {
944 if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
945 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB);
946 constexpr auto attachments = MakeArray<GLenum>(
947 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
948 gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
949 attachments.size(), attachments.data());
951 mDefaultFB_IsInvalid = true;
954 #ifdef DEBUG
955 if (!mOptions.alpha) {
956 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
957 gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
958 if (IsWebGL2()) {
959 gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, 0);
960 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
961 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
963 uint32_t pixel = 0xffbadbad;
964 gl->fReadPixels(0, 0, 1, 1, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
965 &pixel);
966 MOZ_ASSERT((pixel & 0xff000000) == 0xff000000);
968 #endif
971 return true;
974 bool WebGLContext::PresentIntoXR(gl::SwapChain& swapChain,
975 const gl::MozFramebuffer& fb) {
976 OnEndOfFrame();
978 const auto colorSpace = ToColorSpace2(mOptions);
979 auto presenter = swapChain.Acquire(fb.mSize, colorSpace);
980 if (!presenter) {
981 GenerateWarning("Swap chain surface creation failed.");
982 LoseContext();
983 return false;
986 const auto destFb = presenter->Fb();
987 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
989 BlitBackbufferToCurDriverFB(nullptr, &fb);
991 // https://immersive-web.github.io/webxr/#opaque-framebuffer
992 // Opaque framebuffers will always be cleared regardless of the
993 // associated WebGL context’s preserveDrawingBuffer value.
994 if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
995 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb.mFB);
996 constexpr auto attachments = MakeArray<GLenum>(
997 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
998 gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, attachments.size(),
999 attachments.data());
1002 return true;
1005 // Initialize a swap chain's surface factory given the desired surface type.
1006 void InitSwapChain(gl::GLContext& gl, gl::SwapChain& swapChain,
1007 const layers::TextureType consumerType) {
1008 if (!swapChain.mFactory) {
1009 auto typedFactory = gl::SurfaceFactory::Create(&gl, consumerType);
1010 if (typedFactory) {
1011 swapChain.mFactory = std::move(typedFactory);
1014 if (!swapChain.mFactory) {
1015 NS_WARNING("Failed to make an ideal SurfaceFactory.");
1016 swapChain.mFactory = MakeUnique<gl::SurfaceFactory_Basic>(gl);
1018 MOZ_ASSERT(swapChain.mFactory);
1021 void WebGLContext::Present(WebGLFramebuffer* const xrFb,
1022 const layers::TextureType consumerType,
1023 const bool webvr,
1024 const webgl::SwapChainOptions& options) {
1025 const FuncScope funcScope(*this, "<Present>");
1026 if (IsContextLost()) return;
1028 auto swapChain = GetSwapChain(xrFb, webvr);
1029 const gl::MozFramebuffer* maybeFB = nullptr;
1030 if (xrFb) {
1031 maybeFB = xrFb->mOpaque.get();
1032 } else {
1033 mResolvedDefaultFB = nullptr;
1036 InitSwapChain(*gl, *swapChain, consumerType);
1038 bool valid =
1039 maybeFB ? PresentIntoXR(*swapChain, *maybeFB) : PresentInto(*swapChain);
1040 if (!valid) {
1041 return;
1044 bool useAsync = options.remoteTextureOwnerId.IsValid() &&
1045 options.remoteTextureId.IsValid();
1046 if (useAsync) {
1047 PushRemoteTexture(nullptr, *swapChain, swapChain->FrontBuffer(), options);
1051 void WebGLContext::CopyToSwapChain(WebGLFramebuffer* const srcFb,
1052 const layers::TextureType consumerType,
1053 const webgl::SwapChainOptions& options) {
1054 const FuncScope funcScope(*this, "<CopyToSwapChain>");
1055 if (IsContextLost()) return;
1057 OnEndOfFrame();
1059 if (!srcFb) return;
1060 const auto* info = srcFb->GetCompletenessInfo();
1061 if (!info) {
1062 return;
1064 gfx::IntSize size(info->width, info->height);
1066 InitSwapChain(*gl, srcFb->mSwapChain, consumerType);
1068 bool useAsync = options.remoteTextureOwnerId.IsValid() &&
1069 options.remoteTextureId.IsValid();
1070 // If we're using async present and if there is no way to serialize surfaces,
1071 // then a readback is required to do the copy. In this case, there's no reason
1072 // to copy into a separate shared surface for the front buffer. Just directly
1073 // read back the WebGL framebuffer into and push it as a remote texture.
1074 if (useAsync && srcFb->mSwapChain.mFactory->GetConsumerType() ==
1075 layers::TextureType::Unknown) {
1076 PushRemoteTexture(srcFb, srcFb->mSwapChain, nullptr, options);
1077 return;
1081 // ColorSpace will need to be part of SwapChainOptions for DTWebgl.
1082 const auto colorSpace = ToColorSpace2(mOptions);
1083 auto presenter = srcFb->mSwapChain.Acquire(size, colorSpace);
1084 if (!presenter) {
1085 GenerateWarning("Swap chain surface creation failed.");
1086 LoseContext();
1087 return;
1090 const ScopedFBRebinder saveFB(this);
1092 const auto destFb = presenter->Fb();
1093 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
1095 BlitBackbufferToCurDriverFB(srcFb, nullptr, options.bgra);
1098 if (useAsync) {
1099 PushRemoteTexture(srcFb, srcFb->mSwapChain, srcFb->mSwapChain.FrontBuffer(),
1100 options);
1104 bool WebGLContext::PushRemoteTexture(WebGLFramebuffer* fb,
1105 gl::SwapChain& swapChain,
1106 std::shared_ptr<gl::SharedSurface> surf,
1107 const webgl::SwapChainOptions& options) {
1108 const auto onFailure = [&]() -> bool {
1109 GenerateWarning("Remote texture creation failed.");
1110 LoseContext();
1111 if (mRemoteTextureOwner) {
1112 mRemoteTextureOwner->PushDummyTexture(options.remoteTextureId,
1113 options.remoteTextureOwnerId);
1115 return false;
1118 if (!mRemoteTextureOwner) {
1119 // Ensure we have a remote texture owner client for WebGLParent.
1120 const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr;
1121 if (!outOfProcess) {
1122 return onFailure();
1124 mRemoteTextureOwner =
1125 MakeRefPtr<layers::RemoteTextureOwnerClient>(outOfProcess->OtherPid());
1128 layers::RemoteTextureOwnerId ownerId = options.remoteTextureOwnerId;
1129 layers::RemoteTextureId textureId = options.remoteTextureId;
1131 if (!mRemoteTextureOwner->IsRegistered(ownerId)) {
1132 // Register a texture owner to represent the swap chain.
1133 RefPtr<layers::RemoteTextureOwnerClient> textureOwner = mRemoteTextureOwner;
1134 auto destroyedCallback = [textureOwner, ownerId]() {
1135 textureOwner->UnregisterTextureOwner(ownerId);
1138 swapChain.SetDestroyedCallback(destroyedCallback);
1139 mRemoteTextureOwner->RegisterTextureOwner(
1140 ownerId,
1141 /* aIsSyncMode */ gfx::gfxVars::WebglOopAsyncPresentForceSync());
1144 MOZ_ASSERT(fb || surf);
1145 gfx::IntSize size;
1146 if (surf) {
1147 size = surf->mDesc.size;
1148 } else {
1149 const auto* info = fb->GetCompletenessInfo();
1150 MOZ_ASSERT(info);
1151 size = gfx::IntSize(info->width, info->height);
1154 const auto surfaceFormat = mOptions.alpha ? gfx::SurfaceFormat::B8G8R8A8
1155 : gfx::SurfaceFormat::B8G8R8X8;
1156 Maybe<layers::SurfaceDescriptor> desc;
1157 if (surf) {
1158 desc = surf->ToSurfaceDescriptor();
1160 if (!desc) {
1161 if (surf && surf->mDesc.type != gl::SharedSurfaceType::Basic) {
1162 return onFailure();
1164 // If we can't serialize to a surface descriptor, then we need to create
1165 // a buffer to read back into that will become the remote texture.
1166 auto data = mRemoteTextureOwner->CreateOrRecycleBufferTextureData(
1167 ownerId, size, surfaceFormat);
1168 if (!data) {
1169 gfxCriticalNoteOnce << "Failed to allocate BufferTextureData";
1170 return onFailure();
1173 layers::MappedTextureData mappedData;
1174 if (!data->BorrowMappedData(mappedData)) {
1175 return onFailure();
1178 Range<uint8_t> range = {mappedData.data,
1179 data->AsBufferTextureData()->GetBufferSize()};
1181 // If we have a surface representing the front buffer, then try to snapshot
1182 // that. Otherwise, when there is no surface, we read back directly from the
1183 // WebGL framebuffer.
1184 auto valid =
1185 surf ? FrontBufferSnapshotInto(surf, Some(range),
1186 Some(mappedData.stride))
1187 : SnapshotInto(fb->mGLName, size, range, Some(mappedData.stride));
1188 if (!valid) {
1189 return onFailure();
1192 if (!options.bgra) {
1193 // If the buffer is already BGRA, we don't need to swizzle. However, if it
1194 // is RGBA, then a swizzle to BGRA is required.
1195 bool rv = gfx::SwizzleData(mappedData.data, mappedData.stride,
1196 gfx::SurfaceFormat::R8G8B8A8, mappedData.data,
1197 mappedData.stride,
1198 gfx::SurfaceFormat::B8G8R8A8, mappedData.size);
1199 MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
1202 mRemoteTextureOwner->PushTexture(textureId, ownerId, std::move(data));
1203 return true;
1206 // SharedSurfaces of SurfaceDescriptorD3D10 and SurfaceDescriptorMacIOSurface
1207 // need to be kept alive. They will be recycled by
1208 // RemoteTextureOwnerClient::GetRecycledSharedSurface() when their usages are
1209 // ended.
1210 std::shared_ptr<gl::SharedSurface> keepAlive;
1211 switch (desc->type()) {
1212 case layers::SurfaceDescriptor::TSurfaceDescriptorD3D10:
1213 case layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface:
1214 case layers::SurfaceDescriptor::TSurfaceTextureDescriptor:
1215 case layers::SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer:
1216 case layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf:
1217 keepAlive = surf;
1218 break;
1219 default:
1220 break;
1223 auto data =
1224 MakeUnique<layers::SharedSurfaceTextureData>(*desc, surfaceFormat, size);
1225 mRemoteTextureOwner->PushTexture(textureId, ownerId, std::move(data),
1226 keepAlive);
1227 auto recycledSurface = mRemoteTextureOwner->GetRecycledSharedSurface(ownerId);
1228 if (recycledSurface) {
1229 swapChain.StoreRecycledSurface(recycledSurface);
1231 return true;
1234 void WebGLContext::EndOfFrame() {
1235 const FuncScope funcScope(*this, "<EndOfFrame>");
1236 if (IsContextLost()) return;
1238 OnEndOfFrame();
1241 gl::SwapChain* WebGLContext::GetSwapChain(WebGLFramebuffer* const xrFb,
1242 const bool webvr) {
1243 auto swapChain = webvr ? &mWebVRSwapChain : &mSwapChain;
1244 if (xrFb) {
1245 swapChain = &xrFb->mSwapChain;
1247 return swapChain;
1250 Maybe<layers::SurfaceDescriptor> WebGLContext::GetFrontBuffer(
1251 WebGLFramebuffer* const xrFb, const bool webvr) {
1252 auto* swapChain = GetSwapChain(xrFb, webvr);
1253 if (!swapChain) return {};
1254 const auto& front = swapChain->FrontBuffer();
1255 if (!front) return {};
1257 return front->ToSurfaceDescriptor();
1260 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
1261 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
1262 const auto& front = mSwapChain.FrontBuffer();
1263 if (!front) return {};
1264 return FrontBufferSnapshotInto(front, maybeDest, destStride);
1267 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
1268 const std::shared_ptr<gl::SharedSurface>& front,
1269 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
1270 const auto& size = front->mDesc.size;
1271 if (!maybeDest) return Some(*uvec2::FromSize(size));
1273 // -
1275 front->WaitForBufferOwnership();
1276 front->LockProd();
1277 front->ProducerReadAcquire();
1278 auto reset = MakeScopeExit([&] {
1279 front->ProducerReadRelease();
1280 front->UnlockProd();
1283 // -
1285 return SnapshotInto(front->mFb ? front->mFb->mFB : 0, size, *maybeDest,
1286 destStride);
1289 Maybe<uvec2> WebGLContext::SnapshotInto(GLuint srcFb, const gfx::IntSize& size,
1290 const Range<uint8_t>& dest,
1291 const Maybe<size_t> destStride) {
1292 const auto minStride = CheckedInt<size_t>(size.width) * 4;
1293 if (!minStride.isValid()) {
1294 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width;
1295 return {};
1297 size_t stride = destStride.valueOr(minStride.value());
1298 if (stride < minStride.value() || (stride % 4) != 0) {
1299 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width
1300 << ", stride:" << stride;
1301 return {};
1304 gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
1305 if (IsWebGL2()) {
1306 gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH,
1307 stride > minStride.value() ? stride / 4 : 0);
1308 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
1309 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
1312 // -
1314 const auto readFbWas = mBoundReadFramebuffer;
1315 const auto pboWas = mBoundPixelPackBuffer;
1317 GLenum fbTarget = LOCAL_GL_READ_FRAMEBUFFER;
1318 if (!IsWebGL2()) {
1319 fbTarget = LOCAL_GL_FRAMEBUFFER;
1321 auto reset2 = MakeScopeExit([&] {
1322 DoBindFB(readFbWas, fbTarget);
1323 if (pboWas) {
1324 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
1328 gl->fBindFramebuffer(fbTarget, srcFb);
1329 if (pboWas) {
1330 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
1333 // -
1335 const auto srcByteCount = CheckedInt<size_t>(stride) * size.height;
1336 if (!srcByteCount.isValid()) {
1337 gfxCriticalError() << "SnapshotInto: invalid srcByteCount, width:"
1338 << size.width << ", height:" << size.height;
1339 return {};
1341 const auto dstByteCount = dest.length();
1342 if (srcByteCount.value() > dstByteCount) {
1343 gfxCriticalError() << "SnapshotInto: srcByteCount:" << srcByteCount.value()
1344 << " > dstByteCount:" << dstByteCount;
1345 return {};
1347 uint8_t* dstPtr = dest.begin().get();
1348 gl->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA,
1349 LOCAL_GL_UNSIGNED_BYTE, dstPtr);
1351 if (!IsWebGL2() && stride > minStride.value() && size.height > 1) {
1352 // WebGL 1 doesn't support PACK_ROW_LENGTH. Instead, we read the data tight
1353 // into the front of the buffer, and use memmove (since the source and dest
1354 // may overlap) starting from the back to move it to the correct stride
1355 // offsets. We don't move the first row as it is already in the right place.
1356 uint8_t* destRow = dstPtr + stride * (size.height - 1);
1357 const uint8_t* srcRow = dstPtr + minStride.value() * (size.height - 1);
1358 while (destRow > dstPtr) {
1359 memmove(destRow, srcRow, minStride.value());
1360 destRow -= stride;
1361 srcRow -= minStride.value();
1365 return Some(*uvec2::FromSize(size));
1368 void WebGLContext::ClearVRSwapChain() { mWebVRSwapChain.ClearPool(); }
1370 // ------------------------
1372 RefPtr<gfx::DataSourceSurface> GetTempSurface(const gfx::IntSize& aSize,
1373 gfx::SurfaceFormat& aFormat) {
1374 uint32_t stride =
1375 gfx::GetAlignedStride<8>(aSize.width, BytesPerPixel(aFormat));
1376 return gfx::Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat,
1377 stride);
1380 void WebGLContext::DummyReadFramebufferOperation() {
1381 if (!mBoundReadFramebuffer) return; // Infallible.
1383 const auto status = mBoundReadFramebuffer->CheckFramebufferStatus();
1384 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
1385 ErrorInvalidFramebufferOperation("Framebuffer must be complete.");
1389 bool WebGLContext::Has64BitTimestamps() const {
1390 // 'sync' provides glGetInteger64v either by supporting ARB_sync, GL3+, or
1391 // GLES3+.
1392 return gl->IsSupported(gl::GLFeature::sync);
1395 static bool CheckContextLost(gl::GLContext* gl, bool* const out_isGuilty) {
1396 MOZ_ASSERT(gl);
1398 const auto resetStatus = gl->fGetGraphicsResetStatus();
1399 if (resetStatus == LOCAL_GL_NO_ERROR) {
1400 *out_isGuilty = false;
1401 return false;
1404 // Assume guilty unless we find otherwise!
1405 bool isGuilty = true;
1406 switch (resetStatus) {
1407 case LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB:
1408 case LOCAL_GL_PURGED_CONTEXT_RESET_NV:
1409 // Either nothing wrong, or not our fault.
1410 isGuilty = false;
1411 break;
1412 case LOCAL_GL_GUILTY_CONTEXT_RESET_ARB:
1413 NS_WARNING(
1414 "WebGL content on the page definitely caused the graphics"
1415 " card to reset.");
1416 break;
1417 case LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB:
1418 NS_WARNING(
1419 "WebGL content on the page might have caused the graphics"
1420 " card to reset");
1421 // If we can't tell, assume not-guilty.
1422 // Todo: Implement max number of "unknown" resets per document or time.
1423 isGuilty = false;
1424 break;
1425 default:
1426 gfxCriticalError() << "Unexpected glGetGraphicsResetStatus: "
1427 << gfx::hexa(resetStatus);
1428 break;
1431 if (isGuilty) {
1432 NS_WARNING(
1433 "WebGL context on this page is considered guilty, and will"
1434 " not be restored.");
1437 *out_isGuilty = isGuilty;
1438 return true;
1441 void WebGLContext::RunContextLossTimer() { mContextLossHandler.RunTimer(); }
1443 // We use this timer for many things. Here are the things that it is activated
1444 // for:
1445 // 1) If a script is using the MOZ_WEBGL_lose_context extension.
1446 // 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the
1447 // CONTEXT_LOST_WEBGL error has been triggered.
1448 // 3) If we are using ANGLE, or anything that supports ARB_robustness, query the
1449 // GPU periodically to see if the reset status bit has been set.
1450 // In all of these situations, we use this timer to send the script context lost
1451 // and restored events asynchronously. For example, if it triggers a context
1452 // loss, the webglcontextlost event will be sent to it the next time the
1453 // robustness timer fires.
1454 // Note that this timer mechanism is not used unless one of these 3 criteria are
1455 // met.
1456 // At a bare minimum, from context lost to context restores, it would take 3
1457 // full timer iterations: detection, webglcontextlost, webglcontextrestored.
1458 void WebGLContext::CheckForContextLoss() {
1459 bool isGuilty = true;
1460 const auto isContextLost = CheckContextLost(gl, &isGuilty);
1461 if (!isContextLost) return;
1463 mWebGLError = LOCAL_GL_CONTEXT_LOST;
1465 auto reason = webgl::ContextLossReason::None;
1466 if (isGuilty) {
1467 reason = webgl::ContextLossReason::Guilty;
1469 LoseContext(reason);
1472 void WebGLContext::HandlePendingContextLoss() {
1473 mIsContextLost = true;
1474 mHost->OnContextLoss(mPendingContextLossReason);
1477 void WebGLContext::LoseContextLruLocked(const webgl::ContextLossReason reason) {
1478 printf_stderr("WebGL(%p)::LoseContext(%u)\n", this,
1479 static_cast<uint32_t>(reason));
1480 mLruPosition.ResetLocked();
1481 mPendingContextLossReason = reason;
1482 mPendingContextLoss = true;
1485 void WebGLContext::LoseContext(const webgl::ContextLossReason reason) {
1486 StaticMutexAutoLock lock(sLruMutex);
1487 LoseContextLruLocked(reason);
1488 HandlePendingContextLoss();
1489 if (mRemoteTextureOwner) {
1490 mRemoteTextureOwner->NotifyContextLost();
1494 void WebGLContext::DidRefresh() {
1495 if (gl) {
1496 gl->FlushIfHeavyGLCallsSinceLastFlush();
1500 ////////////////////////////////////////////////////////////////////////////////
1502 uvec2 WebGLContext::DrawingBufferSize() {
1503 const FuncScope funcScope(*this, "width/height");
1504 if (IsContextLost()) return {};
1506 if (!EnsureDefaultFB()) return {};
1508 return *uvec2::FromSize(mDefaultFB->mSize);
1511 bool WebGLContext::ValidateAndInitFB(const WebGLFramebuffer* const fb,
1512 const GLenum incompleteFbError) {
1513 if (fb) return fb->ValidateAndInitAttachments(incompleteFbError);
1515 if (!EnsureDefaultFB()) return false;
1517 if (mDefaultFB_IsInvalid) {
1518 // Clear it!
1519 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
1520 const webgl::ScopedPrepForResourceClear scopedPrep(*this);
1521 if (!mOptions.alpha) {
1522 gl->fClearColor(0, 0, 0, 1);
1524 const GLbitfield bits = LOCAL_GL_COLOR_BUFFER_BIT |
1525 LOCAL_GL_DEPTH_BUFFER_BIT |
1526 LOCAL_GL_STENCIL_BUFFER_BIT;
1527 gl->fClear(bits);
1529 mDefaultFB_IsInvalid = false;
1531 return true;
1534 void WebGLContext::DoBindFB(const WebGLFramebuffer* const fb,
1535 const GLenum target) const {
1536 const GLenum driverFB = fb ? fb->mGLName : mDefaultFB->mFB;
1537 gl->fBindFramebuffer(target, driverFB);
1540 bool WebGLContext::BindCurFBForDraw() {
1541 const auto& fb = mBoundDrawFramebuffer;
1542 if (!ValidateAndInitFB(fb)) return false;
1544 DoBindFB(fb);
1545 return true;
1548 bool WebGLContext::BindCurFBForColorRead(
1549 const webgl::FormatUsageInfo** const out_format, uint32_t* const out_width,
1550 uint32_t* const out_height, const GLenum incompleteFbError) {
1551 const auto& fb = mBoundReadFramebuffer;
1553 if (fb) {
1554 if (!ValidateAndInitFB(fb, incompleteFbError)) return false;
1555 if (!fb->ValidateForColorRead(out_format, out_width, out_height))
1556 return false;
1558 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb->mGLName);
1559 return true;
1562 if (!BindDefaultFBForRead()) return false;
1564 if (mDefaultFB_ReadBuffer == LOCAL_GL_NONE) {
1565 ErrorInvalidOperation(
1566 "Can't read from backbuffer when readBuffer mode is NONE.");
1567 return false;
1570 auto effFormat = mOptions.alpha ? webgl::EffectiveFormat::RGBA8
1571 : webgl::EffectiveFormat::RGB8;
1573 *out_format = mFormatUsage->GetUsage(effFormat);
1574 MOZ_ASSERT(*out_format);
1576 *out_width = mDefaultFB->mSize.width;
1577 *out_height = mDefaultFB->mSize.height;
1578 return true;
1581 bool WebGLContext::BindDefaultFBForRead() {
1582 if (!ValidateAndInitFB(nullptr)) return false;
1584 if (!mDefaultFB->mSamples) {
1585 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
1586 return true;
1589 if (!mResolvedDefaultFB) {
1590 mResolvedDefaultFB =
1591 gl::MozFramebuffer::Create(gl, mDefaultFB->mSize, 0, false);
1592 if (!mResolvedDefaultFB) {
1593 gfxCriticalNote << FuncName() << ": Failed to create mResolvedDefaultFB.";
1594 return false;
1598 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
1599 BlitBackbufferToCurDriverFB();
1601 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
1602 return true;
1605 void WebGLContext::DoColorMask(Maybe<GLuint> i, const uint8_t bitmask) const {
1606 if (!IsExtensionEnabled(WebGLExtensionID::OES_draw_buffers_indexed)) {
1607 i = Nothing();
1609 const auto bs = std::bitset<4>(bitmask);
1610 if (i) {
1611 gl->fColorMaski(*i, bs[0], bs[1], bs[2], bs[3]);
1612 } else {
1613 gl->fColorMask(bs[0], bs[1], bs[2], bs[3]);
1617 ////////////////////////////////////////////////////////////////////////////////
1619 ScopedDrawCallWrapper::ScopedDrawCallWrapper(WebGLContext& webgl)
1620 : mWebGL(webgl) {
1621 uint8_t driverColorMask0 = mWebGL.mColorWriteMask0;
1622 bool driverDepthTest = mWebGL.mDepthTestEnabled;
1623 bool driverStencilTest = mWebGL.mStencilTestEnabled;
1624 const auto& fb = mWebGL.mBoundDrawFramebuffer;
1625 if (!fb) {
1626 if (mWebGL.mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE) {
1627 driverColorMask0 = 0; // Is this well-optimized enough for depth-first
1628 // rendering?
1629 } else {
1630 driverColorMask0 &= ~(uint8_t(mWebGL.mNeedsFakeNoAlpha) << 3);
1632 driverDepthTest &= !mWebGL.mNeedsFakeNoDepth;
1633 driverStencilTest &= !mWebGL.mNeedsFakeNoStencil;
1636 const auto& gl = mWebGL.gl;
1637 mWebGL.DoColorMask(Some(0), driverColorMask0);
1638 if (mWebGL.mDriverDepthTest != driverDepthTest) {
1639 // "When disabled, the depth comparison and subsequent possible updates to
1640 // the
1641 // depth buffer value are bypassed and the fragment is passed to the next
1642 // operation." [GLES 3.0.5, p177]
1643 mWebGL.mDriverDepthTest = driverDepthTest;
1644 gl->SetEnabled(LOCAL_GL_DEPTH_TEST, mWebGL.mDriverDepthTest);
1646 if (mWebGL.mDriverStencilTest != driverStencilTest) {
1647 // "When disabled, the stencil test and associated modifications are not
1648 // made, and
1649 // the fragment is always passed." [GLES 3.0.5, p175]
1650 mWebGL.mDriverStencilTest = driverStencilTest;
1651 gl->SetEnabled(LOCAL_GL_STENCIL_TEST, mWebGL.mDriverStencilTest);
1655 ScopedDrawCallWrapper::~ScopedDrawCallWrapper() {
1656 if (mWebGL.mBoundDrawFramebuffer) return;
1658 mWebGL.mResolvedDefaultFB = nullptr;
1659 mWebGL.mShouldPresent = true;
1662 // -
1664 void WebGLContext::ScissorRect::Apply(gl::GLContext& gl) const {
1665 gl.fScissor(x, y, w, h);
1668 ////////////////////////////////////////
1670 IndexedBufferBinding::IndexedBufferBinding() = default;
1671 IndexedBufferBinding::~IndexedBufferBinding() = default;
1673 uint64_t IndexedBufferBinding::ByteCount() const {
1674 if (!mBufferBinding) return 0;
1676 uint64_t bufferSize = mBufferBinding->ByteLength();
1677 if (!mRangeSize) // BindBufferBase
1678 return bufferSize;
1680 if (mRangeStart >= bufferSize) return 0;
1681 bufferSize -= mRangeStart;
1683 return std::min(bufferSize, mRangeSize);
1686 ////////////////////////////////////////
1688 ScopedFBRebinder::~ScopedFBRebinder() {
1689 const auto fnName = [&](WebGLFramebuffer* fb) {
1690 return fb ? fb->mGLName : 0;
1693 const auto& gl = mWebGL->gl;
1694 if (mWebGL->IsWebGL2()) {
1695 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
1696 fnName(mWebGL->mBoundDrawFramebuffer));
1697 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
1698 fnName(mWebGL->mBoundReadFramebuffer));
1699 } else {
1700 MOZ_ASSERT(mWebGL->mBoundDrawFramebuffer == mWebGL->mBoundReadFramebuffer);
1701 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER,
1702 fnName(mWebGL->mBoundDrawFramebuffer));
1706 ////////////////////
1708 void DoBindBuffer(gl::GLContext& gl, const GLenum target,
1709 const WebGLBuffer* const buffer) {
1710 gl.fBindBuffer(target, buffer ? buffer->mGLName : 0);
1713 ////////////////////////////////////////
1715 bool Intersect(const int32_t srcSize, const int32_t read0,
1716 const int32_t readSize, int32_t* const out_intRead0,
1717 int32_t* const out_intWrite0, int32_t* const out_intSize) {
1718 MOZ_ASSERT(srcSize >= 0);
1719 MOZ_ASSERT(readSize >= 0);
1720 const auto read1 = int64_t(read0) + readSize;
1722 int32_t intRead0 = read0; // Clearly doesn't need validation.
1723 int64_t intWrite0 = 0;
1724 int64_t intSize = readSize;
1726 if (read1 <= 0 || read0 >= srcSize) {
1727 // Disjoint ranges.
1728 intSize = 0;
1729 } else {
1730 if (read0 < 0) {
1731 const auto diff = int64_t(0) - read0;
1732 MOZ_ASSERT(diff >= 0);
1733 intRead0 = 0;
1734 intWrite0 = diff;
1735 intSize -= diff;
1737 if (read1 > srcSize) {
1738 const auto diff = int64_t(read1) - srcSize;
1739 MOZ_ASSERT(diff >= 0);
1740 intSize -= diff;
1743 if (!CheckedInt<int32_t>(intWrite0).isValid() ||
1744 !CheckedInt<int32_t>(intSize).isValid()) {
1745 return false;
1749 *out_intRead0 = intRead0;
1750 *out_intWrite0 = intWrite0;
1751 *out_intSize = intSize;
1752 return true;
1755 // --
1757 uint64_t AvailGroups(const uint64_t totalAvailItems,
1758 const uint64_t firstItemOffset, const uint32_t groupSize,
1759 const uint32_t groupStride) {
1760 MOZ_ASSERT(groupSize && groupStride);
1761 MOZ_ASSERT(groupSize <= groupStride);
1763 if (totalAvailItems <= firstItemOffset) return 0;
1764 const size_t availItems = totalAvailItems - firstItemOffset;
1766 size_t availGroups = availItems / groupStride;
1767 const size_t tailItems = availItems % groupStride;
1768 if (tailItems >= groupSize) {
1769 availGroups += 1;
1771 return availGroups;
1774 ////////////////////////////////////////////////////////////////////////////////
1776 const char* WebGLContext::FuncName() const {
1777 const char* ret;
1778 if (MOZ_LIKELY(mFuncScope)) {
1779 ret = mFuncScope->mFuncName;
1780 } else {
1781 NS_WARNING("FuncScope not on stack!");
1782 ret = "<unknown function>";
1784 return ret;
1787 // -
1789 WebGLContext::FuncScope::FuncScope(const WebGLContext& webgl,
1790 const char* const funcName)
1791 : mWebGL(webgl), mFuncName(bool(mWebGL.mFuncScope) ? nullptr : funcName) {
1792 if (!mFuncName) return;
1793 mWebGL.mFuncScope = this;
1796 WebGLContext::FuncScope::~FuncScope() {
1797 if (mBindFailureGuard) {
1798 gfxCriticalError() << "mBindFailureGuard failure: Early exit from "
1799 << mWebGL.FuncName();
1802 if (!mFuncName) return;
1803 mWebGL.mFuncScope = nullptr;
1806 // --
1808 bool ClientWebGLContext::IsXRCompatible() const { return mXRCompatible; }
1810 already_AddRefed<dom::Promise> ClientWebGLContext::MakeXRCompatible(
1811 ErrorResult& aRv) {
1812 const FuncScope funcScope(*this, "MakeXRCompatible");
1813 nsCOMPtr<nsIGlobalObject> global = GetParentObject();
1814 if (!global) {
1815 aRv.ThrowInvalidAccessError(
1816 "Using a WebGL context that is not attached to either a canvas or an "
1817 "OffscreenCanvas");
1818 return nullptr;
1820 RefPtr<dom::Promise> promise = dom::Promise::Create(global, aRv);
1821 NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
1823 if (IsContextLost()) {
1824 promise->MaybeRejectWithInvalidStateError(
1825 "Can not make context XR compatible when context is already lost.");
1826 return promise.forget();
1829 // TODO: Bug 1580258 - WebGLContext.MakeXRCompatible needs to switch to
1830 // the device connected to the XR hardware
1831 // This should update `options` and lose+restore the context.
1832 mXRCompatible = true;
1833 promise->MaybeResolveWithUndefined();
1834 return promise.forget();
1837 // --
1839 webgl::AvailabilityRunnable& ClientWebGLContext::EnsureAvailabilityRunnable()
1840 const {
1841 if (!mAvailabilityRunnable) {
1842 mAvailabilityRunnable = new webgl::AvailabilityRunnable(this);
1843 auto forgettable = mAvailabilityRunnable;
1844 NS_DispatchToCurrentThread(forgettable.forget());
1846 return *mAvailabilityRunnable;
1849 webgl::AvailabilityRunnable::AvailabilityRunnable(
1850 const ClientWebGLContext* const webgl)
1851 : DiscardableRunnable("webgl::AvailabilityRunnable"), mWebGL(webgl) {}
1853 webgl::AvailabilityRunnable::~AvailabilityRunnable() {
1854 MOZ_ASSERT(mQueries.empty());
1855 MOZ_ASSERT(mSyncs.empty());
1858 nsresult webgl::AvailabilityRunnable::Run() {
1859 for (const auto& cur : mQueries) {
1860 if (!cur) continue;
1861 cur->mCanBeAvailable = true;
1863 mQueries.clear();
1865 for (const auto& cur : mSyncs) {
1866 if (!cur) continue;
1867 cur->mCanBeAvailable = true;
1869 mSyncs.clear();
1871 if (mWebGL) {
1872 mWebGL->mAvailabilityRunnable = nullptr;
1874 return NS_OK;
1877 // -
1879 void WebGLContext::GenerateErrorImpl(const GLenum errOrWarning,
1880 const std::string& text) const {
1881 auto err = errOrWarning;
1882 bool isPerfWarning = false;
1883 if (err == webgl::kErrorPerfWarning) {
1884 err = 0;
1885 isPerfWarning = true;
1888 if (err && mFuncScope && mFuncScope->mBindFailureGuard) {
1889 gfxCriticalError() << "mBindFailureGuard failure: Generating error "
1890 << EnumString(err) << ": " << text;
1893 /* ES2 section 2.5 "GL Errors" states that implementations can have
1894 * multiple 'flags', as errors might be caught in different parts of
1895 * a distributed implementation.
1896 * We're signing up as a distributed implementation here, with
1897 * separate flags for WebGL and the underlying GLContext.
1899 if (!mWebGLError) mWebGLError = err;
1901 if (!mHost) return; // Impossible?
1903 // -
1905 const auto ShouldWarn = [&]() {
1906 if (isPerfWarning) {
1907 return ShouldGeneratePerfWarnings();
1909 return ShouldGenerateWarnings();
1911 if (!ShouldWarn()) return;
1913 // -
1915 auto* pNumWarnings = &mWarningCount;
1916 const char* warningsType = "warnings";
1917 if (isPerfWarning) {
1918 pNumWarnings = &mNumPerfWarnings;
1919 warningsType = "perf warnings";
1922 if (isPerfWarning) {
1923 const auto perfText = std::string("WebGL perf warning: ") + text;
1924 mHost->JsWarning(perfText);
1925 } else {
1926 mHost->JsWarning(text);
1928 *pNumWarnings += 1;
1930 if (!ShouldWarn()) {
1931 const auto& msg = nsPrintfCString(
1932 "After reporting %i, no further %s will be reported for this WebGL "
1933 "context.",
1934 int(*pNumWarnings), warningsType);
1935 mHost->JsWarning(ToString(msg));
1939 // -
1941 Maybe<std::string> WebGLContext::GetString(const GLenum pname) const {
1942 const WebGLContext::FuncScope funcScope(*this, "getParameter");
1943 if (IsContextLost()) return {};
1945 const auto FromRaw = [](const char* const raw) -> Maybe<std::string> {
1946 if (!raw) return {};
1947 return Some(std::string(raw));
1950 switch (pname) {
1951 case LOCAL_GL_EXTENSIONS: {
1952 if (!gl->IsCoreProfile()) {
1953 const auto rawExt = (const char*)gl->fGetString(LOCAL_GL_EXTENSIONS);
1954 return FromRaw(rawExt);
1956 std::string ret;
1957 const auto& numExts = gl->GetIntAs<GLuint>(LOCAL_GL_NUM_EXTENSIONS);
1958 for (GLuint i = 0; i < numExts; i++) {
1959 const auto rawExt =
1960 (const char*)gl->fGetStringi(LOCAL_GL_EXTENSIONS, i);
1961 if (!rawExt) continue;
1963 if (i > 0) {
1964 ret += " ";
1966 ret += rawExt;
1968 return Some(std::move(ret));
1971 case LOCAL_GL_RENDERER:
1972 case LOCAL_GL_VENDOR:
1973 case LOCAL_GL_VERSION: {
1974 const auto raw = (const char*)gl->fGetString(pname);
1975 return FromRaw(raw);
1978 case dom::MOZ_debug_Binding::WSI_INFO: {
1979 nsCString info;
1980 gl->GetWSIInfo(&info);
1981 return Some(std::string(info.BeginReading()));
1984 default:
1985 ErrorInvalidEnumArg("pname", pname);
1986 return {};
1990 // ---------------------------------
1992 Maybe<webgl::IndexedName> webgl::ParseIndexed(const std::string& str) {
1993 static const std::regex kRegex("(.*)\\[([0-9]+)\\]");
1995 std::smatch match;
1996 if (!std::regex_match(str, match, kRegex)) return {};
1998 const auto index = std::stoull(match[2]);
1999 return Some(webgl::IndexedName{match[1], index});
2002 // ExplodeName("foo.bar[3].x") -> ["foo", ".", "bar", "[", "3", "]", ".", "x"]
2003 static std::vector<std::string> ExplodeName(const std::string& str) {
2004 std::vector<std::string> ret;
2006 static const std::regex kSep("[.[\\]]");
2008 auto itr = std::regex_token_iterator<decltype(str.begin())>(
2009 str.begin(), str.end(), kSep, {-1, 0});
2010 const auto end = decltype(itr)();
2012 for (; itr != end; ++itr) {
2013 const auto& part = itr->str();
2014 if (part.size()) {
2015 ret.push_back(part);
2018 return ret;
2023 // #define DUMP_MakeLinkResult
2025 webgl::LinkActiveInfo GetLinkActiveInfo(
2026 gl::GLContext& gl, const GLuint prog, const bool webgl2,
2027 const std::unordered_map<std::string, std::string>& nameUnmap) {
2028 webgl::LinkActiveInfo ret;
2029 [&]() {
2030 const auto fnGetProgramui = [&](const GLenum pname) {
2031 GLint ret = 0;
2032 gl.fGetProgramiv(prog, pname, &ret);
2033 return static_cast<uint32_t>(ret);
2036 std::vector<char> stringBuffer(1);
2037 const auto fnEnsureCapacity = [&](const GLenum pname) {
2038 const auto maxWithNull = fnGetProgramui(pname);
2039 if (maxWithNull > stringBuffer.size()) {
2040 stringBuffer.resize(maxWithNull);
2044 fnEnsureCapacity(LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH);
2045 fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH);
2046 if (webgl2) {
2047 fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH);
2048 fnEnsureCapacity(LOCAL_GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH);
2051 // -
2053 const auto fnUnmapName = [&](const std::string& mappedName) {
2054 const auto parts = ExplodeName(mappedName);
2056 std::ostringstream ret;
2057 for (const auto& part : parts) {
2058 const auto maybe = MaybeFind(nameUnmap, part);
2059 if (maybe) {
2060 ret << *maybe;
2061 } else {
2062 ret << part;
2065 return ret.str();
2068 // -
2071 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_ATTRIBUTES);
2072 ret.activeAttribs.reserve(count);
2073 for (const auto i : IntegerRange(count)) {
2074 GLsizei lengthWithoutNull = 0;
2075 GLint elemCount = 0; // `size`
2076 GLenum elemType = 0; // `type`
2077 gl.fGetActiveAttrib(prog, i, stringBuffer.size(), &lengthWithoutNull,
2078 &elemCount, &elemType, stringBuffer.data());
2079 if (!elemType) {
2080 const auto error = gl.fGetError();
2081 if (error != LOCAL_GL_CONTEXT_LOST) {
2082 gfxCriticalError() << "Failed to do glGetActiveAttrib: " << error;
2084 return;
2086 const auto mappedName =
2087 std::string(stringBuffer.data(), lengthWithoutNull);
2088 const auto userName = fnUnmapName(mappedName);
2090 auto loc = gl.fGetAttribLocation(prog, mappedName.c_str());
2091 if (mappedName.find("gl_") == 0) {
2092 // Bug 1328559: Appears problematic on ANGLE and OSX, but not Linux or
2093 // Win+GL.
2094 loc = -1;
2097 #ifdef DUMP_MakeLinkResult
2098 printf_stderr("[attrib %u/%u] @%i %s->%s\n", i, count, loc,
2099 userName.c_str(), mappedName.c_str());
2100 #endif
2101 webgl::ActiveAttribInfo info;
2102 info.elemType = elemType;
2103 info.elemCount = elemCount;
2104 info.name = userName;
2105 info.location = loc;
2106 info.baseType = webgl::ToAttribBaseType(info.elemType);
2107 ret.activeAttribs.push_back(std::move(info));
2111 // -
2114 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORMS);
2115 ret.activeUniforms.reserve(count);
2117 std::vector<GLint> blockIndexList(count, -1);
2118 std::vector<GLint> blockOffsetList(count, -1);
2119 std::vector<GLint> blockArrayStrideList(count, -1);
2120 std::vector<GLint> blockMatrixStrideList(count, -1);
2121 std::vector<GLint> blockIsRowMajorList(count, 0);
2123 if (webgl2 && count) {
2124 std::vector<GLuint> activeIndices;
2125 activeIndices.reserve(count);
2126 for (const auto i : IntegerRange(count)) {
2127 activeIndices.push_back(i);
2130 gl.fGetActiveUniformsiv(
2131 prog, activeIndices.size(), activeIndices.data(),
2132 LOCAL_GL_UNIFORM_BLOCK_INDEX, blockIndexList.data());
2134 gl.fGetActiveUniformsiv(prog, activeIndices.size(),
2135 activeIndices.data(), LOCAL_GL_UNIFORM_OFFSET,
2136 blockOffsetList.data());
2138 gl.fGetActiveUniformsiv(
2139 prog, activeIndices.size(), activeIndices.data(),
2140 LOCAL_GL_UNIFORM_ARRAY_STRIDE, blockArrayStrideList.data());
2142 gl.fGetActiveUniformsiv(
2143 prog, activeIndices.size(), activeIndices.data(),
2144 LOCAL_GL_UNIFORM_MATRIX_STRIDE, blockMatrixStrideList.data());
2146 gl.fGetActiveUniformsiv(
2147 prog, activeIndices.size(), activeIndices.data(),
2148 LOCAL_GL_UNIFORM_IS_ROW_MAJOR, blockIsRowMajorList.data());
2151 for (const auto i : IntegerRange(count)) {
2152 GLsizei lengthWithoutNull = 0;
2153 GLint elemCount = 0; // `size`
2154 GLenum elemType = 0; // `type`
2155 gl.fGetActiveUniform(prog, i, stringBuffer.size(), &lengthWithoutNull,
2156 &elemCount, &elemType, stringBuffer.data());
2157 if (!elemType) {
2158 const auto error = gl.fGetError();
2159 if (error != LOCAL_GL_CONTEXT_LOST) {
2160 gfxCriticalError() << "Failed to do glGetActiveUniform: " << error;
2162 return;
2164 auto mappedName = std::string(stringBuffer.data(), lengthWithoutNull);
2166 // Get true name
2168 auto baseMappedName = mappedName;
2170 const bool isArray = [&]() {
2171 const auto maybe = webgl::ParseIndexed(mappedName);
2172 if (maybe) {
2173 MOZ_ASSERT(maybe->index == 0);
2174 baseMappedName = std::move(maybe->name);
2175 return true;
2177 return false;
2178 }();
2180 const auto userName = fnUnmapName(mappedName);
2181 if (StartsWith(userName, "webgl_")) continue;
2183 // -
2185 webgl::ActiveUniformInfo info;
2186 info.elemType = elemType;
2187 info.elemCount = static_cast<uint32_t>(elemCount);
2188 info.name = userName;
2189 info.block_index = blockIndexList[i];
2190 info.block_offset = blockOffsetList[i];
2191 info.block_arrayStride = blockArrayStrideList[i];
2192 info.block_matrixStride = blockMatrixStrideList[i];
2193 info.block_isRowMajor = bool(blockIsRowMajorList[i]);
2195 #ifdef DUMP_MakeLinkResult
2196 printf_stderr("[uniform %u/%u] %s->%s\n", i + 1, count,
2197 userName.c_str(), mappedName.c_str());
2198 #endif
2200 // Get uniform locations
2202 auto locName = baseMappedName;
2203 const auto baseLength = locName.size();
2204 for (const auto i : IntegerRange(info.elemCount)) {
2205 if (isArray) {
2206 locName.erase(
2207 baseLength); // Erase previous [N], but retain capacity.
2208 locName += '[';
2209 locName += std::to_string(i);
2210 locName += ']';
2212 const auto loc = gl.fGetUniformLocation(prog, locName.c_str());
2213 if (loc != -1) {
2214 info.locByIndex[i] = static_cast<uint32_t>(loc);
2215 #ifdef DUMP_MakeLinkResult
2216 printf_stderr(" [%u] @%i\n", i, loc);
2217 #endif
2220 } // anon
2222 ret.activeUniforms.push_back(std::move(info));
2223 } // for i
2224 } // anon
2226 if (webgl2) {
2227 // -------------------------------------
2228 // active uniform blocks
2230 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORM_BLOCKS);
2231 ret.activeUniformBlocks.reserve(count);
2233 for (const auto i : IntegerRange(count)) {
2234 GLsizei lengthWithoutNull = 0;
2235 gl.fGetActiveUniformBlockName(prog, i, stringBuffer.size(),
2236 &lengthWithoutNull,
2237 stringBuffer.data());
2238 const auto mappedName =
2239 std::string(stringBuffer.data(), lengthWithoutNull);
2240 const auto userName = fnUnmapName(mappedName);
2242 // -
2244 auto info = webgl::ActiveUniformBlockInfo{userName};
2245 GLint val = 0;
2247 gl.fGetActiveUniformBlockiv(prog, i, LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE,
2248 &val);
2249 info.dataSize = static_cast<uint32_t>(val);
2251 gl.fGetActiveUniformBlockiv(
2252 prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &val);
2253 info.activeUniformIndices.resize(val);
2254 gl.fGetActiveUniformBlockiv(
2255 prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES,
2256 reinterpret_cast<GLint*>(info.activeUniformIndices.data()));
2258 gl.fGetActiveUniformBlockiv(
2259 prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER,
2260 &val);
2261 info.referencedByVertexShader = bool(val);
2263 gl.fGetActiveUniformBlockiv(
2264 prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER,
2265 &val);
2266 info.referencedByFragmentShader = bool(val);
2268 ret.activeUniformBlocks.push_back(std::move(info));
2269 } // for i
2270 } // anon
2272 // -------------------------------------
2273 // active tf varyings
2275 const auto count = fnGetProgramui(LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS);
2276 ret.activeTfVaryings.reserve(count);
2278 for (const auto i : IntegerRange(count)) {
2279 GLsizei lengthWithoutNull = 0;
2280 GLsizei elemCount = 0; // `size`
2281 GLenum elemType = 0; // `type`
2282 gl.fGetTransformFeedbackVarying(prog, i, stringBuffer.size(),
2283 &lengthWithoutNull, &elemCount,
2284 &elemType, stringBuffer.data());
2285 const auto mappedName =
2286 std::string(stringBuffer.data(), lengthWithoutNull);
2287 const auto userName = fnUnmapName(mappedName);
2289 ret.activeTfVaryings.push_back(
2290 {elemType, static_cast<uint32_t>(elemCount), userName});
2293 } // if webgl2
2294 }();
2295 return ret;
2298 nsCString ToCString(const std::string& s) {
2299 return nsCString(s.data(), s.size());
2302 webgl::CompileResult WebGLContext::GetCompileResult(
2303 const WebGLShader& shader) const {
2304 webgl::CompileResult ret;
2305 [&]() {
2306 ret.pending = false;
2307 const auto& info = shader.CompileResults();
2308 if (!info) return;
2309 if (!info->mValid) {
2310 ret.log = info->mInfoLog.c_str();
2311 return;
2313 // TODO: These could be large and should be made fallible.
2314 ret.translatedSource = ToCString(info->mObjectCode);
2315 ret.log = ToCString(shader.CompileLog());
2316 if (!shader.IsCompiled()) return;
2317 ret.success = true;
2318 }();
2319 return ret;
2322 webgl::LinkResult WebGLContext::GetLinkResult(const WebGLProgram& prog) const {
2323 webgl::LinkResult ret;
2324 [&]() {
2325 ret.pending = false; // Link status polling not yet implemented.
2326 ret.log = ToCString(prog.LinkLog());
2327 const auto& info = prog.LinkInfo();
2328 if (!info) return;
2329 ret.success = true;
2330 ret.active = info->active;
2331 ret.tfBufferMode = info->transformFeedbackBufferMode;
2332 }();
2333 return ret;
2336 // -
2338 GLint WebGLContext::GetFragDataLocation(const WebGLProgram& prog,
2339 const std::string& userName) const {
2340 const auto err = CheckGLSLVariableName(IsWebGL2(), userName);
2341 if (err) {
2342 GenerateError(err->type, "%s", err->info.c_str());
2343 return -1;
2346 const auto& info = prog.LinkInfo();
2347 if (!info) return -1;
2348 const auto& nameMap = info->nameMap;
2350 const auto parts = ExplodeName(userName);
2352 std::ostringstream ret;
2353 for (const auto& part : parts) {
2354 const auto maybe = MaybeFind(nameMap, part);
2355 if (maybe) {
2356 ret << *maybe;
2357 } else {
2358 ret << part;
2361 const auto mappedName = ret.str();
2363 if (gl->WorkAroundDriverBugs() && gl->IsMesa()) {
2364 // Mesa incorrectly generates INVALID_OPERATION for gl_ prefixes here.
2365 if (mappedName.find("gl_") == 0) {
2366 return -1;
2370 return gl->fGetFragDataLocation(prog.mGLName, mappedName.c_str());
2373 // -
2375 WebGLContextBoundObject::WebGLContextBoundObject(WebGLContext* webgl)
2376 : mContext(webgl) {}
2378 // -
2380 Result<webgl::ExplicitPixelPackingState, std::string>
2381 webgl::ExplicitPixelPackingState::ForUseWith(
2382 const webgl::PixelPackingState& stateOrZero, const GLenum target,
2383 const uvec3& subrectSize, const webgl::PackingInfo& pi,
2384 const Maybe<size_t> bytesPerRowStrideOverride) {
2385 auto state = stateOrZero;
2387 if (!IsTexTarget3D(target)) {
2388 state.skipImages = 0;
2389 state.imageHeight = 0;
2391 if (!state.rowLength) {
2392 state.rowLength = subrectSize.x;
2394 if (!state.imageHeight) {
2395 state.imageHeight = subrectSize.y;
2398 // -
2400 const auto mpii = PackingInfoInfo::For(pi);
2401 if (!mpii) {
2402 const auto text =
2403 nsPrintfCString("Invalid pi: { 0x%x, 0x%x}", pi.format, pi.type);
2404 return Err(mozilla::ToString(text));
2406 const auto pii = *mpii;
2407 const auto bytesPerPixel = pii.BytesPerPixel();
2409 const auto ElemsPerRowStride = [&]() {
2410 // GLES 3.0.6 p116:
2411 // p: `Elem*` pointer to the first element of the first row
2412 // N: row number, starting at 0
2413 // l: groups (pixels) per row
2414 // n: elements per group (pixel) in [1,2,3,4]
2415 // s: bytes per element in [1,2,4,8]
2416 // a: UNPACK_ALIGNMENT in [1,2,4,8]
2417 // Pointer to first element of Nth row: p + N*k
2418 // k(s>=a): n*l
2419 // k(s<a): a/s * ceil(s*n*l/a)
2420 const auto n__elemsPerPixel = pii.elementsPerPixel;
2421 const auto l__pixelsPerRow = state.rowLength;
2422 const auto a__alignment = state.alignmentInTypeElems;
2423 const auto s__bytesPerElem = pii.bytesPerElement;
2425 const auto nl = CheckedInt<size_t>(n__elemsPerPixel) * l__pixelsPerRow;
2426 auto k__elemsPerRowStride = nl;
2427 if (s__bytesPerElem < a__alignment) {
2428 // k = a/s * ceil(s*n*l/a)
2429 k__elemsPerRowStride =
2430 a__alignment / s__bytesPerElem *
2431 ((nl * s__bytesPerElem + a__alignment - 1) / a__alignment);
2433 return k__elemsPerRowStride;
2436 // -
2438 if (bytesPerRowStrideOverride) { // E.g. HTMLImageElement
2439 const size_t bytesPerRowStrideRequired = *bytesPerRowStrideOverride;
2440 // We have to reverse-engineer an ALIGNMENT and ROW_LENGTH for this.
2442 // GL does this in elems not bytes, so we should too.
2443 MOZ_RELEASE_ASSERT(bytesPerRowStrideRequired % pii.bytesPerElement == 0);
2444 const auto elemsPerRowStrideRequired =
2445 bytesPerRowStrideRequired / pii.bytesPerElement;
2447 state.rowLength = bytesPerRowStrideRequired / bytesPerPixel;
2448 state.alignmentInTypeElems = 8;
2449 while (true) {
2450 const auto elemPerRowStride = ElemsPerRowStride();
2451 if (elemPerRowStride.isValid() &&
2452 elemPerRowStride.value() == elemsPerRowStrideRequired) {
2453 break;
2455 state.alignmentInTypeElems /= 2;
2456 if (!state.alignmentInTypeElems) {
2457 const auto text = nsPrintfCString(
2458 "No valid alignment found: pi: { 0x%x, 0x%x},"
2459 " bytesPerRowStrideRequired: %zu",
2460 pi.format, pi.type, bytesPerRowStrideRequired);
2461 return Err(mozilla::ToString(text));
2466 // -
2468 const auto usedPixelsPerRow =
2469 CheckedInt<size_t>(state.skipPixels) + subrectSize.x;
2470 if (!usedPixelsPerRow.isValid() ||
2471 usedPixelsPerRow.value() > state.rowLength) {
2472 return Err("UNPACK_SKIP_PIXELS + width > UNPACK_ROW_LENGTH.");
2475 if (subrectSize.y > state.imageHeight) {
2476 return Err("height > UNPACK_IMAGE_HEIGHT.");
2478 // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
2480 // -
2482 auto metrics = Metrics{};
2484 metrics.usedSize = subrectSize;
2485 metrics.bytesPerPixel = BytesPerPixel(pi);
2487 // -
2489 const auto elemsPerRowStride = ElemsPerRowStride();
2490 const auto bytesPerRowStride = pii.bytesPerElement * elemsPerRowStride;
2491 if (!bytesPerRowStride.isValid()) {
2492 return Err("ROW_LENGTH or width too large for packing.");
2494 metrics.bytesPerRowStride = bytesPerRowStride.value();
2496 // -
2498 const auto firstImageTotalRows =
2499 CheckedInt<size_t>(state.skipRows) + metrics.usedSize.y;
2500 const auto totalImages =
2501 CheckedInt<size_t>(state.skipImages) + metrics.usedSize.z;
2502 auto totalRows = CheckedInt<size_t>(0);
2503 if (metrics.usedSize.y && metrics.usedSize.z) {
2504 totalRows = firstImageTotalRows + state.imageHeight * (totalImages - 1);
2506 if (!totalRows.isValid()) {
2507 return Err(
2508 "SKIP_ROWS, height, IMAGE_HEIGHT, SKIP_IMAGES, or depth too large for "
2509 "packing.");
2511 metrics.totalRows = totalRows.value();
2513 // -
2515 const auto totalBytesStrided = totalRows * metrics.bytesPerRowStride;
2516 if (!totalBytesStrided.isValid()) {
2517 return Err("Total byte count too large for packing.");
2519 metrics.totalBytesStrided = totalBytesStrided.value();
2521 metrics.totalBytesUsed = metrics.totalBytesStrided;
2522 if (metrics.usedSize.x && metrics.usedSize.y && metrics.usedSize.z) {
2523 const auto usedBytesPerRow =
2524 usedPixelsPerRow.value() * metrics.bytesPerPixel;
2525 metrics.totalBytesUsed -= metrics.bytesPerRowStride;
2526 metrics.totalBytesUsed += usedBytesPerRow;
2529 // -
2531 return {{state, metrics}};
2534 } // namespace mozilla