Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / dom / canvas / WebGLContext.cpp
blob8c62c548a6b0d7cc493edc465b2cfa4f9cf3dfa1
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 mPendingContextLoss(false),
147 mMaxPerfWarnings(StaticPrefs::webgl_perf_max_warnings()),
148 mMaxAcceptableFBStatusInvals(
149 StaticPrefs::webgl_perf_max_acceptable_fb_status_invals()),
150 mContextLossHandler(this),
151 mMaxWarnings(StaticPrefs::webgl_max_warnings_per_context()),
152 mAllowFBInvalidation(StaticPrefs::webgl_allow_fb_invalidation()),
153 mMsaaSamples((uint8_t)StaticPrefs::webgl_msaa_samples()),
154 mRequestedSize(desc.size) {
155 host.mContext = this;
156 const FuncScope funcScope(*this, "<Create>");
159 WebGLContext::~WebGLContext() { DestroyResourcesAndContext(); }
161 void WebGLContext::DestroyResourcesAndContext() {
162 if (mRemoteTextureOwner) {
163 // Clean up any remote textures registered for framebuffer swap chains.
164 mRemoteTextureOwner->UnregisterAllTextureOwners();
165 mRemoteTextureOwner = nullptr;
168 if (!gl) return;
170 mDefaultFB = nullptr;
171 mResolvedDefaultFB = nullptr;
173 mBound2DTextures.Clear();
174 mBoundCubeMapTextures.Clear();
175 mBound3DTextures.Clear();
176 mBound2DArrayTextures.Clear();
177 mBoundSamplers.Clear();
178 mBoundArrayBuffer = nullptr;
179 mBoundCopyReadBuffer = nullptr;
180 mBoundCopyWriteBuffer = nullptr;
181 mBoundPixelPackBuffer = nullptr;
182 mBoundPixelUnpackBuffer = nullptr;
183 mBoundTransformFeedbackBuffer = nullptr;
184 mBoundUniformBuffer = nullptr;
185 mCurrentProgram = nullptr;
186 mActiveProgramLinkInfo = nullptr;
187 mBoundDrawFramebuffer = nullptr;
188 mBoundReadFramebuffer = nullptr;
189 mBoundVertexArray = nullptr;
190 mDefaultVertexArray = nullptr;
191 mBoundTransformFeedback = nullptr;
192 mDefaultTransformFeedback = nullptr;
194 mQuerySlot_SamplesPassed = nullptr;
195 mQuerySlot_TFPrimsWritten = nullptr;
196 mQuerySlot_TimeElapsed = nullptr;
198 mIndexedUniformBufferBindings.clear();
200 //////
202 if (mEmptyTFO) {
203 gl->fDeleteTransformFeedbacks(1, &mEmptyTFO);
204 mEmptyTFO = 0;
207 //////
209 if (mFakeVertexAttrib0BufferObject) {
210 gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject);
211 mFakeVertexAttrib0BufferObject = 0;
214 // disable all extensions except "WEBGL_lose_context". see bug #927969
215 // spec: http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
216 for (size_t i = 0; i < size_t(WebGLExtensionID::Max); ++i) {
217 WebGLExtensionID extension = WebGLExtensionID(i);
218 if (extension == WebGLExtensionID::WEBGL_lose_context) continue;
219 mExtensions[extension] = nullptr;
222 // We just got rid of everything, so the context had better
223 // have been going away.
224 if (gl::GLContext::ShouldSpew()) {
225 printf_stderr("--- WebGL context destroyed: %p\n", gl.get());
228 MOZ_ASSERT(gl);
229 gl->MarkDestroyed();
230 mGL_OnlyClearInDestroyResourcesAndContext = nullptr;
231 MOZ_ASSERT(!gl);
234 void ClientWebGLContext::MarkCanvasDirty() {
235 if (!mCanvasElement && !mOffscreenCanvas) return;
237 mFrameCaptureState = FrameCaptureState::DIRTY;
239 if (mIsCanvasDirty) return;
240 mIsCanvasDirty = true;
242 if (mCanvasElement) {
243 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
244 mCanvasElement->InvalidateCanvasContent(nullptr);
245 } else if (mOffscreenCanvas) {
246 mOffscreenCanvas->QueueCommitToCompositor();
250 void WebGLContext::OnMemoryPressure() {
251 bool shouldLoseContext = mLoseContextOnMemoryPressure;
253 if (!mCanLoseContextInForeground &&
254 ProcessPriorityManager::CurrentProcessIsForeground()) {
255 shouldLoseContext = false;
258 if (shouldLoseContext) LoseContext();
261 // --
263 bool WebGLContext::CreateAndInitGL(
264 bool forceEnabled, std::vector<FailureReason>* const out_failReasons) {
265 const FuncScope funcScope(*this, "<Create>");
267 // WebGL2 is separately blocked:
268 if (IsWebGL2() && !forceEnabled) {
269 FailureReason reason;
270 if (!gfx::gfxVars::AllowWebgl2()) {
271 reason.info =
272 "AllowWebgl2:false restricts context creation on this system.";
273 out_failReasons->push_back(reason);
274 GenerateWarning("%s", reason.info.BeginReading());
275 return false;
279 gl::CreateContextFlags flags = (gl::CreateContextFlags::NO_VALIDATION |
280 gl::CreateContextFlags::PREFER_ROBUSTNESS);
281 bool tryNativeGL = true;
282 bool tryANGLE = false;
284 // -
286 if (StaticPrefs::webgl_forbid_hardware()) {
287 flags |= gl::CreateContextFlags::FORBID_HARDWARE;
289 if (StaticPrefs::webgl_forbid_software()) {
290 flags |= gl::CreateContextFlags::FORBID_SOFTWARE;
293 if (forceEnabled) {
294 flags &= ~gl::CreateContextFlags::FORBID_HARDWARE;
295 flags &= ~gl::CreateContextFlags::FORBID_SOFTWARE;
298 if ((flags & gl::CreateContextFlags::FORBID_HARDWARE) &&
299 (flags & gl::CreateContextFlags::FORBID_SOFTWARE)) {
300 FailureReason reason;
301 reason.info = "Both hardware and software were forbidden by config.";
302 out_failReasons->push_back(reason);
303 GenerateWarning("%s", reason.info.BeginReading());
304 return false;
307 // -
309 if (StaticPrefs::webgl_cgl_multithreaded()) {
310 flags |= gl::CreateContextFlags::PREFER_MULTITHREADED;
313 if (IsWebGL2()) {
314 flags |= gl::CreateContextFlags::PREFER_ES3;
315 } else {
316 // Request and prefer ES2 context for WebGL1.
317 flags |= gl::CreateContextFlags::PREFER_EXACT_VERSION;
319 if (!StaticPrefs::webgl_1_allow_core_profiles()) {
320 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
325 auto powerPref = mOptions.powerPreference;
327 // If "Use hardware acceleration when available" option is disabled:
328 if (!gfx::gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING)) {
329 powerPref = dom::WebGLPowerPreference::Low_power;
332 const auto overrideVal = StaticPrefs::webgl_power_preference_override();
333 if (overrideVal > 0) {
334 powerPref = dom::WebGLPowerPreference::High_performance;
335 } else if (overrideVal < 0) {
336 powerPref = dom::WebGLPowerPreference::Low_power;
339 if (powerPref == dom::WebGLPowerPreference::High_performance) {
340 flags |= gl::CreateContextFlags::HIGH_POWER;
344 if (!gfx::gfxVars::WebglAllowCoreProfile()) {
345 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
348 // --
350 const bool useEGL = PR_GetEnv("MOZ_WEBGL_FORCE_EGL");
352 #ifdef XP_WIN
353 tryNativeGL = false;
354 tryANGLE = true;
356 if (StaticPrefs::webgl_disable_wgl()) {
357 tryNativeGL = false;
360 if (StaticPrefs::webgl_disable_angle() ||
361 PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL") || useEGL) {
362 tryNativeGL = true;
363 tryANGLE = false;
365 #endif
367 if (tryNativeGL && !forceEnabled) {
368 FailureReason reason;
369 if (!gfx::gfxVars::WebglAllowWindowsNativeGl()) {
370 reason.info =
371 "WebglAllowWindowsNativeGl:false restricts context creation on this "
372 "system.";
374 out_failReasons->push_back(reason);
376 GenerateWarning("%s", reason.info.BeginReading());
377 tryNativeGL = false;
381 // --
383 using fnCreateT = decltype(gl::GLContextProviderEGL::CreateHeadless);
384 const auto fnCreate = [&](fnCreateT* const pfnCreate,
385 const char* const info) {
386 nsCString failureId;
387 const RefPtr<gl::GLContext> gl = pfnCreate({flags}, &failureId);
388 if (!gl) {
389 out_failReasons->push_back(WebGLContext::FailureReason(failureId, info));
391 return gl;
394 const auto newGL = [&]() -> RefPtr<gl::GLContext> {
395 if (tryNativeGL) {
396 if (useEGL)
397 return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "useEGL");
399 const auto ret =
400 fnCreate(&gl::GLContextProvider::CreateHeadless, "tryNativeGL");
401 if (ret) return ret;
404 if (tryANGLE) {
405 return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "tryANGLE");
407 return nullptr;
408 }();
410 if (!newGL) {
411 out_failReasons->push_back(
412 FailureReason("FEATURE_FAILURE_WEBGL_EXHAUSTED_DRIVERS",
413 "Exhausted GL driver options."));
414 return false;
417 // --
419 FailureReason reason;
421 mGL_OnlyClearInDestroyResourcesAndContext = newGL;
422 MOZ_RELEASE_ASSERT(gl);
423 if (!InitAndValidateGL(&reason)) {
424 DestroyResourcesAndContext();
425 MOZ_RELEASE_ASSERT(!gl);
427 // The fail reason here should be specific enough for now.
428 out_failReasons->push_back(reason);
429 return false;
432 const auto val = StaticPrefs::webgl_debug_incomplete_tex_color();
433 if (val) {
434 mIncompleteTexOverride.reset(new gl::Texture(*gl));
435 const gl::ScopedBindTexture autoBind(gl, mIncompleteTexOverride->name);
436 const auto heapVal = std::make_unique<uint32_t>(val);
437 gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 1, 1, 0,
438 LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, heapVal.get());
441 return true;
444 // Fallback for resizes:
446 bool WebGLContext::EnsureDefaultFB() {
447 if (mDefaultFB) {
448 MOZ_ASSERT(*uvec2::FromSize(mDefaultFB->mSize) == mRequestedSize);
449 return true;
452 const bool depthStencil = mOptions.depth || mOptions.stencil;
453 auto attemptSize = gfx::IntSize{mRequestedSize.x, mRequestedSize.y};
455 while (attemptSize.width || attemptSize.height) {
456 attemptSize.width = std::max(attemptSize.width, 1);
457 attemptSize.height = std::max(attemptSize.height, 1);
459 [&]() {
460 if (mOptions.antialias) {
461 MOZ_ASSERT(!mDefaultFB);
462 mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, mMsaaSamples,
463 depthStencil);
464 if (mDefaultFB) return;
465 if (mOptionsFrozen) return;
468 MOZ_ASSERT(!mDefaultFB);
469 mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, 0, depthStencil);
470 }();
472 if (mDefaultFB) break;
474 attemptSize.width /= 2;
475 attemptSize.height /= 2;
478 if (!mDefaultFB) {
479 GenerateWarning("Backbuffer resize failed. Losing context.");
480 LoseContext();
481 return false;
484 mDefaultFB_IsInvalid = true;
486 const auto actualSize = *uvec2::FromSize(mDefaultFB->mSize);
487 if (actualSize != mRequestedSize) {
488 GenerateWarning(
489 "Requested size %ux%u was too large, but resize"
490 " to %ux%u succeeded.",
491 mRequestedSize.x, mRequestedSize.y, actualSize.x, actualSize.y);
493 mRequestedSize = actualSize;
494 return true;
497 void WebGLContext::Resize(uvec2 requestedSize) {
498 // Zero-sized surfaces can cause problems.
499 if (!requestedSize.x) {
500 requestedSize.x = 1;
502 if (!requestedSize.y) {
503 requestedSize.y = 1;
506 // Kill our current default fb(s), for later lazy allocation.
507 mRequestedSize = requestedSize;
508 mDefaultFB = nullptr;
509 mResetLayer = true; // New size means new Layer.
512 UniquePtr<webgl::FormatUsageAuthority> WebGLContext::CreateFormatUsage(
513 gl::GLContext* gl) const {
514 return webgl::FormatUsageAuthority::CreateForWebGL1(gl);
517 /*static*/
518 RefPtr<WebGLContext> WebGLContext::Create(HostWebGLContext& host,
519 const webgl::InitContextDesc& desc,
520 webgl::InitContextResult* const out) {
521 AUTO_PROFILER_LABEL("WebGLContext::Create", GRAPHICS);
522 nsCString failureId = "FEATURE_FAILURE_WEBGL_UNKOWN"_ns;
523 const bool forceEnabled = StaticPrefs::webgl_force_enabled();
524 ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
526 auto res = [&]() -> Result<RefPtr<WebGLContext>, std::string> {
527 bool disabled = StaticPrefs::webgl_disabled();
529 // TODO: When we have software webgl support we should use that instead.
530 disabled |= gfxPlatform::InSafeMode();
532 if (disabled) {
533 if (gfxPlatform::InSafeMode()) {
534 failureId = "FEATURE_FAILURE_WEBGL_SAFEMODE"_ns;
535 } else {
536 failureId = "FEATURE_FAILURE_WEBGL_DISABLED"_ns;
538 return Err("WebGL is currently disabled.");
541 // Alright, now let's start trying.
543 RefPtr<WebGLContext> webgl;
544 if (desc.isWebgl2) {
545 webgl = new WebGL2Context(host, desc);
546 } else {
547 webgl = new WebGLContext(host, desc);
550 MOZ_ASSERT(!webgl->gl);
551 std::vector<FailureReason> failReasons;
552 if (!webgl->CreateAndInitGL(forceEnabled, &failReasons)) {
553 nsCString text("WebGL creation failed: ");
554 for (const auto& cur : failReasons) {
555 // Don't try to accumulate using an empty key if |cur.key| is empty.
556 if (cur.key.IsEmpty()) {
557 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID,
558 "FEATURE_FAILURE_REASON_UNKNOWN"_ns);
559 } else {
560 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, cur.key);
563 const auto str = nsPrintfCString("\n* %s (%s)", cur.info.BeginReading(),
564 cur.key.BeginReading());
565 text.Append(str);
567 failureId = "FEATURE_FAILURE_REASON"_ns;
568 return Err(text.BeginReading());
570 MOZ_ASSERT(webgl->gl);
572 if (desc.options.failIfMajorPerformanceCaveat) {
573 if (webgl->gl->IsWARP()) {
574 failureId = "FEATURE_FAILURE_WEBGL_PERF_WARP"_ns;
575 return Err(
576 "failIfMajorPerformanceCaveat: Driver is not"
577 " hardware-accelerated.");
580 #ifdef XP_WIN
581 if (webgl->gl->GetContextType() == gl::GLContextType::WGL &&
582 !gl::sWGLLib.HasDXInterop2()) {
583 failureId = "FEATURE_FAILURE_WEBGL_DXGL_INTEROP2"_ns;
584 return Err("failIfMajorPerformanceCaveat: WGL without DXGLInterop2.");
586 #endif
589 const FuncScope funcScope(*webgl, "getContext/restoreContext");
591 MOZ_ASSERT(!webgl->mDefaultFB);
592 if (!webgl->EnsureDefaultFB()) {
593 MOZ_ASSERT(!webgl->mDefaultFB);
594 MOZ_ASSERT(webgl->IsContextLost());
595 failureId = "FEATURE_FAILURE_WEBGL_BACKBUFFER"_ns;
596 return Err("Initializing WebGL backbuffer failed.");
599 return webgl;
600 }();
601 if (res.isOk()) {
602 failureId = "SUCCESS"_ns;
604 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, failureId);
606 if (!res.isOk()) {
607 out->error = res.unwrapErr();
608 return nullptr;
610 const auto webgl = res.unwrap();
612 // Update our internal stuff:
613 webgl->FinishInit();
615 reporter.SetSuccessful();
616 if (gl::GLContext::ShouldSpew()) {
617 printf_stderr("--- WebGL context created: %p\n", webgl.get());
620 // -
622 const auto UploadableSdTypes = [&]() {
623 webgl::EnumMask<layers::SurfaceDescriptor::Type> types;
624 types[layers::SurfaceDescriptor::TSurfaceDescriptorBuffer] = true;
625 if (webgl->gl->IsANGLE()) {
626 types[layers::SurfaceDescriptor::TSurfaceDescriptorD3D10] = true;
627 types[layers::SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr] = true;
629 if (kIsMacOS) {
630 types[layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface] = true;
632 if (kIsAndroid) {
633 types[layers::SurfaceDescriptor::TSurfaceTextureDescriptor] = true;
635 if (kIsLinux) {
636 types[layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf] = true;
638 return types;
641 // -
643 out->options = webgl->mOptions;
644 out->limits = *webgl->mLimits;
645 out->uploadableSdTypes = UploadableSdTypes();
646 out->vendor = webgl->gl->Vendor();
648 return webgl;
651 void WebGLContext::FinishInit() {
652 mOptions.antialias &= bool(mDefaultFB->mSamples);
654 if (!mOptions.alpha) {
655 // We always have alpha.
656 mNeedsFakeNoAlpha = true;
659 if (mOptions.depth || mOptions.stencil) {
660 // We always have depth+stencil if we have either.
661 if (!mOptions.depth) {
662 mNeedsFakeNoDepth = true;
664 if (!mOptions.stencil) {
665 mNeedsFakeNoStencil = true;
669 mResetLayer = true;
670 mOptionsFrozen = true;
672 //////
673 // Initial setup.
675 gl->mImplicitMakeCurrent = true;
676 gl->mElideDuplicateBindFramebuffers = true;
678 const auto& size = mDefaultFB->mSize;
680 mViewportX = mViewportY = 0;
681 mViewportWidth = size.width;
682 mViewportHeight = size.height;
683 gl->fViewport(mViewportX, mViewportY, mViewportWidth, mViewportHeight);
685 mScissorRect = {0, 0, size.width, size.height};
686 mScissorRect.Apply(*gl);
688 //////
689 // Check everything
691 AssertCachedBindings();
692 AssertCachedGlobalState();
694 mShouldPresent = true;
696 //////
698 gl->ResetSyncCallCount("WebGLContext Initialization");
699 LoseLruContextIfLimitExceeded();
702 void WebGLContext::SetCompositableHost(
703 RefPtr<layers::CompositableHost>& aCompositableHost) {
704 mCompositableHost = aCompositableHost;
707 void WebGLContext::BumpLruLocked() {
708 if (!mIsContextLost && !mPendingContextLoss) {
709 mLruPosition.AssignLocked(*this);
710 } else {
711 MOZ_ASSERT(!mLruPosition.IsInsertedLocked());
715 void WebGLContext::BumpLru() {
716 StaticMutexAutoLock lock(sLruMutex);
717 BumpLruLocked();
720 void WebGLContext::LoseLruContextIfLimitExceeded() {
721 StaticMutexAutoLock lock(sLruMutex);
723 const auto maxContexts = std::max(1u, StaticPrefs::webgl_max_contexts());
724 const auto maxContextsPerPrincipal =
725 std::max(1u, StaticPrefs::webgl_max_contexts_per_principal());
727 // it's important to update the index on a new context before losing old
728 // contexts, otherwise new unused contexts would all have index 0 and we
729 // couldn't distinguish older ones when choosing which one to lose first.
730 BumpLruLocked();
733 size_t forPrincipal = 0;
734 for (const auto& context : sLru) {
735 if (context->mPrincipalKey == mPrincipalKey) {
736 forPrincipal += 1;
740 while (forPrincipal > maxContextsPerPrincipal) {
741 const auto text = nsPrintfCString(
742 "Exceeded %u live WebGL contexts for this principal, losing the "
743 "least recently used one.",
744 maxContextsPerPrincipal);
745 mHost->JsWarning(ToString(text));
747 for (const auto& context : sLru) {
748 if (context->mPrincipalKey == mPrincipalKey) {
749 MOZ_ASSERT(context != this);
750 context->LoseContextLruLocked(webgl::ContextLossReason::None);
751 forPrincipal -= 1;
752 break;
758 auto total = sLru.size();
759 while (total > maxContexts) {
760 const auto text = nsPrintfCString(
761 "Exceeded %u live WebGL contexts, losing the least "
762 "recently used one.",
763 maxContexts);
764 mHost->JsWarning(ToString(text));
766 const auto& context = sLru.front();
767 MOZ_ASSERT(context != this);
768 context->LoseContextLruLocked(webgl::ContextLossReason::None);
769 total -= 1;
773 // -
775 namespace webgl {
777 ScopedPrepForResourceClear::ScopedPrepForResourceClear(
778 const WebGLContext& webgl_)
779 : webgl(webgl_) {
780 const auto& gl = webgl.gl;
782 if (webgl.mScissorTestEnabled) {
783 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
785 if (webgl.mRasterizerDiscardEnabled) {
786 gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD);
789 // "The clear operation always uses the front stencil write mask
790 // when clearing the stencil buffer."
791 webgl.DoColorMask(Some(0), 0b1111);
792 gl->fDepthMask(true);
793 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff);
795 gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
796 gl->fClearDepth(1.0f); // Depth formats are always cleared to 1.0f, not 0.0f.
797 gl->fClearStencil(0);
800 ScopedPrepForResourceClear::~ScopedPrepForResourceClear() {
801 const auto& gl = webgl.gl;
803 if (webgl.mScissorTestEnabled) {
804 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
806 if (webgl.mRasterizerDiscardEnabled) {
807 gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
810 webgl.DoColorMask(Some(0), webgl.mColorWriteMask0);
811 gl->fDepthMask(webgl.mDepthWriteMask);
812 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, webgl.mStencilWriteMaskFront);
814 gl->fClearColor(webgl.mColorClearValue[0], webgl.mColorClearValue[1],
815 webgl.mColorClearValue[2], webgl.mColorClearValue[3]);
816 gl->fClearDepth(webgl.mDepthClearValue);
817 gl->fClearStencil(webgl.mStencilClearValue);
820 } // namespace webgl
822 // -
824 void WebGLContext::OnEndOfFrame() {
825 if (StaticPrefs::webgl_perf_spew_frame_allocs()) {
826 GeneratePerfWarning("[webgl.perf.spew-frame-allocs] %" PRIu64
827 " data allocations this frame.",
828 mDataAllocGLCallCount);
830 mDataAllocGLCallCount = 0;
831 gl->ResetSyncCallCount("WebGLContext PresentScreenBuffer");
833 mDrawCallsSinceLastFlush = 0;
835 BumpLru();
838 void WebGLContext::BlitBackbufferToCurDriverFB(
839 WebGLFramebuffer* const srcAsWebglFb,
840 const gl::MozFramebuffer* const srcAsMozFb, bool srcIsBGRA) const {
841 // BlitFramebuffer ignores ColorMask().
843 if (mScissorTestEnabled) {
844 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
847 [&]() {
848 // If a MozFramebuffer is supplied, ensure that a WebGLFramebuffer is not
849 // used since it might not have completeness info, while the MozFramebuffer
850 // can still supply the needed information.
851 MOZ_ASSERT(!(srcAsMozFb && srcAsWebglFb));
852 const auto* mozFb = srcAsMozFb ? srcAsMozFb : mDefaultFB.get();
853 GLuint fbo = 0;
854 gfx::IntSize size;
855 if (srcAsWebglFb) {
856 fbo = srcAsWebglFb->mGLName;
857 const auto* info = srcAsWebglFb->GetCompletenessInfo();
858 MOZ_ASSERT(info);
859 size = gfx::IntSize(info->width, info->height);
860 } else {
861 fbo = mozFb->mFB;
862 size = mozFb->mSize;
865 // If no format conversion is necessary, then attempt to directly blit
866 // between framebuffers. Otherwise, if we need to convert to RGBA from
867 // the source format, then we will need to use the texture blit path
868 // below.
869 if (!srcIsBGRA) {
870 if (gl->IsSupported(gl::GLFeature::framebuffer_blit)) {
871 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
872 gl->fBlitFramebuffer(0, 0, size.width, size.height, 0, 0, size.width,
873 size.height, LOCAL_GL_COLOR_BUFFER_BIT,
874 LOCAL_GL_NEAREST);
875 return;
877 if (mDefaultFB->mSamples &&
878 gl->IsExtensionSupported(
879 gl::GLContext::APPLE_framebuffer_multisample)) {
880 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
881 gl->fResolveMultisampleFramebufferAPPLE();
882 return;
886 GLuint colorTex = 0;
887 if (srcAsWebglFb) {
888 const auto& attach = srcAsWebglFb->ColorAttachment0();
889 MOZ_ASSERT(attach.Texture());
890 colorTex = attach.Texture()->mGLName;
891 } else {
892 colorTex = mozFb->ColorTex();
895 // DrawBlit handles ColorMask itself.
896 gl->BlitHelper()->DrawBlitTextureToFramebuffer(
897 colorTex, size, size, LOCAL_GL_TEXTURE_2D, srcIsBGRA);
898 }();
900 if (mScissorTestEnabled) {
901 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
905 // -
907 template <typename T, typename... Args>
908 constexpr auto MakeArray(Args... args) -> std::array<T, sizeof...(Args)> {
909 return {{static_cast<T>(args)...}};
912 inline gfx::ColorSpace2 ToColorSpace2(const WebGLContextOptions& options) {
913 auto ret = gfx::ColorSpace2::UNKNOWN;
914 if (true) {
915 ret = gfx::ColorSpace2::SRGB;
917 if (!options.ignoreColorSpace) {
918 ret = gfx::ToColorSpace2(options.colorSpace);
920 return ret;
923 // -
925 // For an overview of how WebGL compositing works, see:
926 // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
927 bool WebGLContext::PresentInto(gl::SwapChain& swapChain) {
928 OnEndOfFrame();
930 if (!ValidateAndInitFB(nullptr)) return false;
933 const auto colorSpace = ToColorSpace2(mOptions);
934 auto presenter = swapChain.Acquire(mDefaultFB->mSize, colorSpace);
935 if (!presenter) {
936 GenerateWarning("Swap chain surface creation failed.");
937 LoseContext();
938 return false;
941 const auto destFb = presenter->Fb();
942 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
944 BlitBackbufferToCurDriverFB();
946 if (!mOptions.preserveDrawingBuffer) {
947 if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
948 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB);
949 constexpr auto attachments = MakeArray<GLenum>(
950 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
951 gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
952 attachments.size(), attachments.data());
954 mDefaultFB_IsInvalid = true;
957 #ifdef DEBUG
958 if (!mOptions.alpha) {
959 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
960 gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
961 if (IsWebGL2()) {
962 gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, 0);
963 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
964 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
966 uint32_t pixel = 0xffbadbad;
967 gl->fReadPixels(0, 0, 1, 1, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
968 &pixel);
969 MOZ_ASSERT((pixel & 0xff000000) == 0xff000000);
971 #endif
974 return true;
977 bool WebGLContext::PresentIntoXR(gl::SwapChain& swapChain,
978 const gl::MozFramebuffer& fb) {
979 OnEndOfFrame();
981 const auto colorSpace = ToColorSpace2(mOptions);
982 auto presenter = swapChain.Acquire(fb.mSize, colorSpace);
983 if (!presenter) {
984 GenerateWarning("Swap chain surface creation failed.");
985 LoseContext();
986 return false;
989 const auto destFb = presenter->Fb();
990 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
992 BlitBackbufferToCurDriverFB(nullptr, &fb);
994 // https://immersive-web.github.io/webxr/#opaque-framebuffer
995 // Opaque framebuffers will always be cleared regardless of the
996 // associated WebGL context’s preserveDrawingBuffer value.
997 if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
998 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb.mFB);
999 constexpr auto attachments = MakeArray<GLenum>(
1000 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
1001 gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, attachments.size(),
1002 attachments.data());
1005 return true;
1008 // Initialize a swap chain's surface factory given the desired surface type.
1009 void InitSwapChain(gl::GLContext& gl, gl::SwapChain& swapChain,
1010 const layers::TextureType consumerType) {
1011 if (!swapChain.mFactory) {
1012 auto typedFactory = gl::SurfaceFactory::Create(&gl, consumerType);
1013 if (typedFactory) {
1014 swapChain.mFactory = std::move(typedFactory);
1017 if (!swapChain.mFactory) {
1018 NS_WARNING("Failed to make an ideal SurfaceFactory.");
1019 swapChain.mFactory = MakeUnique<gl::SurfaceFactory_Basic>(gl);
1021 MOZ_ASSERT(swapChain.mFactory);
1024 void WebGLContext::Present(WebGLFramebuffer* const xrFb,
1025 const layers::TextureType consumerType,
1026 const bool webvr,
1027 const webgl::SwapChainOptions& options) {
1028 const FuncScope funcScope(*this, "<Present>");
1029 if (IsContextLost()) return;
1031 auto swapChain = GetSwapChain(xrFb, webvr);
1032 const gl::MozFramebuffer* maybeFB = nullptr;
1033 if (xrFb) {
1034 maybeFB = xrFb->mOpaque.get();
1035 } else {
1036 mResolvedDefaultFB = nullptr;
1039 InitSwapChain(*gl, *swapChain, consumerType);
1041 bool valid =
1042 maybeFB ? PresentIntoXR(*swapChain, *maybeFB) : PresentInto(*swapChain);
1043 if (!valid) {
1044 return;
1047 bool useAsync = options.remoteTextureOwnerId.IsValid() &&
1048 options.remoteTextureId.IsValid();
1049 if (useAsync) {
1050 PushRemoteTexture(nullptr, *swapChain, swapChain->FrontBuffer(), options);
1054 void WebGLContext::CopyToSwapChain(WebGLFramebuffer* const srcFb,
1055 const layers::TextureType consumerType,
1056 const webgl::SwapChainOptions& options) {
1057 const FuncScope funcScope(*this, "<CopyToSwapChain>");
1058 if (IsContextLost()) return;
1060 OnEndOfFrame();
1062 if (!srcFb) return;
1063 const auto* info = srcFb->GetCompletenessInfo();
1064 if (!info) {
1065 return;
1067 gfx::IntSize size(info->width, info->height);
1069 InitSwapChain(*gl, srcFb->mSwapChain, consumerType);
1071 bool useAsync = options.remoteTextureOwnerId.IsValid() &&
1072 options.remoteTextureId.IsValid();
1073 // If we're using async present and if there is no way to serialize surfaces,
1074 // then a readback is required to do the copy. In this case, there's no reason
1075 // to copy into a separate shared surface for the front buffer. Just directly
1076 // read back the WebGL framebuffer into and push it as a remote texture.
1077 if (useAsync && srcFb->mSwapChain.mFactory->GetConsumerType() ==
1078 layers::TextureType::Unknown) {
1079 PushRemoteTexture(srcFb, srcFb->mSwapChain, nullptr, options);
1080 return;
1084 // ColorSpace will need to be part of SwapChainOptions for DTWebgl.
1085 const auto colorSpace = ToColorSpace2(mOptions);
1086 auto presenter = srcFb->mSwapChain.Acquire(size, colorSpace);
1087 if (!presenter) {
1088 GenerateWarning("Swap chain surface creation failed.");
1089 LoseContext();
1090 return;
1093 const ScopedFBRebinder saveFB(this);
1095 const auto destFb = presenter->Fb();
1096 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
1098 BlitBackbufferToCurDriverFB(srcFb, nullptr, options.bgra);
1101 if (useAsync) {
1102 PushRemoteTexture(srcFb, srcFb->mSwapChain, srcFb->mSwapChain.FrontBuffer(),
1103 options);
1107 bool WebGLContext::PushRemoteTexture(WebGLFramebuffer* fb,
1108 gl::SwapChain& swapChain,
1109 std::shared_ptr<gl::SharedSurface> surf,
1110 const webgl::SwapChainOptions& options) {
1111 const auto onFailure = [&]() -> bool {
1112 GenerateWarning("Remote texture creation failed.");
1113 LoseContext();
1114 if (mRemoteTextureOwner) {
1115 mRemoteTextureOwner->PushDummyTexture(options.remoteTextureId,
1116 options.remoteTextureOwnerId);
1118 return false;
1121 if (!mRemoteTextureOwner) {
1122 // Ensure we have a remote texture owner client for WebGLParent.
1123 const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr;
1124 if (!outOfProcess) {
1125 return onFailure();
1127 mRemoteTextureOwner =
1128 MakeRefPtr<layers::RemoteTextureOwnerClient>(outOfProcess->OtherPid());
1131 layers::RemoteTextureOwnerId ownerId = options.remoteTextureOwnerId;
1132 layers::RemoteTextureId textureId = options.remoteTextureId;
1134 if (!mRemoteTextureOwner->IsRegistered(ownerId)) {
1135 // Register a texture owner to represent the swap chain.
1136 RefPtr<layers::RemoteTextureOwnerClient> textureOwner = mRemoteTextureOwner;
1137 auto destroyedCallback = [textureOwner, ownerId]() {
1138 textureOwner->UnregisterTextureOwner(ownerId);
1141 swapChain.SetDestroyedCallback(destroyedCallback);
1142 mRemoteTextureOwner->RegisterTextureOwner(
1143 ownerId,
1144 /* aIsSyncMode */ gfx::gfxVars::WebglOopAsyncPresentForceSync());
1147 MOZ_ASSERT(fb || surf);
1148 gfx::IntSize size;
1149 if (surf) {
1150 size = surf->mDesc.size;
1151 } else {
1152 const auto* info = fb->GetCompletenessInfo();
1153 MOZ_ASSERT(info);
1154 size = gfx::IntSize(info->width, info->height);
1157 const auto surfaceFormat = mOptions.alpha ? gfx::SurfaceFormat::B8G8R8A8
1158 : gfx::SurfaceFormat::B8G8R8X8;
1159 Maybe<layers::SurfaceDescriptor> desc;
1160 if (surf) {
1161 desc = surf->ToSurfaceDescriptor();
1163 if (!desc) {
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 /* aSharedSurface */ nullptr);
1204 return true;
1207 // SharedSurfaces of SurfaceDescriptorD3D10 and SurfaceDescriptorMacIOSurface
1208 // need to be kept alive. They will be recycled by
1209 // RemoteTextureOwnerClient::GetRecycledSharedSurface() when their usages are
1210 // ended.
1211 std::shared_ptr<gl::SharedSurface> keepAlive;
1212 switch (desc->type()) {
1213 case layers::SurfaceDescriptor::TSurfaceDescriptorD3D10:
1214 case layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface:
1215 case layers::SurfaceDescriptor::TSurfaceTextureDescriptor:
1216 case layers::SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer:
1217 case layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf:
1218 keepAlive = surf;
1219 break;
1220 default:
1221 break;
1224 auto data =
1225 MakeUnique<layers::SharedSurfaceTextureData>(*desc, surfaceFormat, size);
1226 mRemoteTextureOwner->PushTexture(textureId, ownerId, std::move(data),
1227 keepAlive);
1228 auto recycledSurface = mRemoteTextureOwner->GetRecycledSharedSurface(ownerId);
1229 if (recycledSurface) {
1230 swapChain.StoreRecycledSurface(recycledSurface);
1232 return true;
1235 void WebGLContext::EndOfFrame() {
1236 const FuncScope funcScope(*this, "<EndOfFrame>");
1237 if (IsContextLost()) return;
1239 OnEndOfFrame();
1242 gl::SwapChain* WebGLContext::GetSwapChain(WebGLFramebuffer* const xrFb,
1243 const bool webvr) {
1244 auto swapChain = webvr ? &mWebVRSwapChain : &mSwapChain;
1245 if (xrFb) {
1246 swapChain = &xrFb->mSwapChain;
1248 return swapChain;
1251 Maybe<layers::SurfaceDescriptor> WebGLContext::GetFrontBuffer(
1252 WebGLFramebuffer* const xrFb, const bool webvr) {
1253 auto* swapChain = GetSwapChain(xrFb, webvr);
1254 if (!swapChain) return {};
1255 const auto& front = swapChain->FrontBuffer();
1256 if (!front) return {};
1258 return front->ToSurfaceDescriptor();
1261 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
1262 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
1263 const auto& front = mSwapChain.FrontBuffer();
1264 if (!front) return {};
1265 return FrontBufferSnapshotInto(front, maybeDest, destStride);
1268 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
1269 const std::shared_ptr<gl::SharedSurface>& front,
1270 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
1271 const auto& size = front->mDesc.size;
1272 if (!maybeDest) return Some(*uvec2::FromSize(size));
1274 // -
1276 front->WaitForBufferOwnership();
1277 front->LockProd();
1278 front->ProducerReadAcquire();
1279 auto reset = MakeScopeExit([&] {
1280 front->ProducerReadRelease();
1281 front->UnlockProd();
1284 // -
1286 return SnapshotInto(front->mFb ? front->mFb->mFB : 0, size, *maybeDest,
1287 destStride);
1290 Maybe<uvec2> WebGLContext::SnapshotInto(GLuint srcFb, const gfx::IntSize& size,
1291 const Range<uint8_t>& dest,
1292 const Maybe<size_t> destStride) {
1293 const auto minStride = CheckedInt<size_t>(size.width) * 4;
1294 if (!minStride.isValid()) {
1295 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width;
1296 return {};
1298 size_t stride = destStride.valueOr(minStride.value());
1299 if (stride < minStride.value() || (stride % 4) != 0) {
1300 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width
1301 << ", stride:" << stride;
1302 return {};
1305 gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
1306 if (IsWebGL2()) {
1307 gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH,
1308 stride > minStride.value() ? stride / 4 : 0);
1309 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
1310 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
1313 // -
1315 const auto readFbWas = mBoundReadFramebuffer;
1316 const auto pboWas = mBoundPixelPackBuffer;
1318 GLenum fbTarget = LOCAL_GL_READ_FRAMEBUFFER;
1319 if (!IsWebGL2()) {
1320 fbTarget = LOCAL_GL_FRAMEBUFFER;
1322 auto reset2 = MakeScopeExit([&] {
1323 DoBindFB(readFbWas, fbTarget);
1324 if (pboWas) {
1325 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
1329 gl->fBindFramebuffer(fbTarget, srcFb);
1330 if (pboWas) {
1331 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
1334 // -
1336 const auto srcByteCount = CheckedInt<size_t>(stride) * size.height;
1337 if (!srcByteCount.isValid()) {
1338 gfxCriticalError() << "SnapshotInto: invalid srcByteCount, width:"
1339 << size.width << ", height:" << size.height;
1340 return {};
1342 const auto dstByteCount = dest.length();
1343 if (srcByteCount.value() > dstByteCount) {
1344 gfxCriticalError() << "SnapshotInto: srcByteCount:" << srcByteCount.value()
1345 << " > dstByteCount:" << dstByteCount;
1346 return {};
1348 uint8_t* dstPtr = dest.begin().get();
1349 gl->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA,
1350 LOCAL_GL_UNSIGNED_BYTE, dstPtr);
1352 if (!IsWebGL2() && stride > minStride.value() && size.height > 1) {
1353 // WebGL 1 doesn't support PACK_ROW_LENGTH. Instead, we read the data tight
1354 // into the front of the buffer, and use memmove (since the source and dest
1355 // may overlap) starting from the back to move it to the correct stride
1356 // offsets. We don't move the first row as it is already in the right place.
1357 uint8_t* destRow = dstPtr + stride * (size.height - 1);
1358 const uint8_t* srcRow = dstPtr + minStride.value() * (size.height - 1);
1359 while (destRow > dstPtr) {
1360 memmove(destRow, srcRow, minStride.value());
1361 destRow -= stride;
1362 srcRow -= minStride.value();
1366 return Some(*uvec2::FromSize(size));
1369 void WebGLContext::ClearVRSwapChain() { mWebVRSwapChain.ClearPool(); }
1371 // ------------------------
1373 RefPtr<gfx::DataSourceSurface> GetTempSurface(const gfx::IntSize& aSize,
1374 gfx::SurfaceFormat& aFormat) {
1375 uint32_t stride =
1376 gfx::GetAlignedStride<8>(aSize.width, BytesPerPixel(aFormat));
1377 return gfx::Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat,
1378 stride);
1381 void WebGLContext::DummyReadFramebufferOperation() {
1382 if (!mBoundReadFramebuffer) return; // Infallible.
1384 const auto status = mBoundReadFramebuffer->CheckFramebufferStatus();
1385 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
1386 ErrorInvalidFramebufferOperation("Framebuffer must be complete.");
1390 bool WebGLContext::Has64BitTimestamps() const {
1391 // 'sync' provides glGetInteger64v either by supporting ARB_sync, GL3+, or
1392 // GLES3+.
1393 return gl->IsSupported(gl::GLFeature::sync);
1396 static bool CheckContextLost(gl::GLContext* gl, bool* const out_isGuilty) {
1397 MOZ_ASSERT(gl);
1399 const auto resetStatus = gl->fGetGraphicsResetStatus();
1400 if (resetStatus == LOCAL_GL_NO_ERROR) {
1401 *out_isGuilty = false;
1402 return false;
1405 // Assume guilty unless we find otherwise!
1406 bool isGuilty = true;
1407 switch (resetStatus) {
1408 case LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB:
1409 case LOCAL_GL_PURGED_CONTEXT_RESET_NV:
1410 // Either nothing wrong, or not our fault.
1411 isGuilty = false;
1412 break;
1413 case LOCAL_GL_GUILTY_CONTEXT_RESET_ARB:
1414 NS_WARNING(
1415 "WebGL content on the page definitely caused the graphics"
1416 " card to reset.");
1417 break;
1418 case LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB:
1419 NS_WARNING(
1420 "WebGL content on the page might have caused the graphics"
1421 " card to reset");
1422 // If we can't tell, assume not-guilty.
1423 // Todo: Implement max number of "unknown" resets per document or time.
1424 isGuilty = false;
1425 break;
1426 default:
1427 gfxCriticalError() << "Unexpected glGetGraphicsResetStatus: "
1428 << gfx::hexa(resetStatus);
1429 break;
1432 if (isGuilty) {
1433 NS_WARNING(
1434 "WebGL context on this page is considered guilty, and will"
1435 " not be restored.");
1438 *out_isGuilty = isGuilty;
1439 return true;
1442 void WebGLContext::RunContextLossTimer() { mContextLossHandler.RunTimer(); }
1444 // We use this timer for many things. Here are the things that it is activated
1445 // for:
1446 // 1) If a script is using the MOZ_WEBGL_lose_context extension.
1447 // 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the
1448 // CONTEXT_LOST_WEBGL error has been triggered.
1449 // 3) If we are using ANGLE, or anything that supports ARB_robustness, query the
1450 // GPU periodically to see if the reset status bit has been set.
1451 // In all of these situations, we use this timer to send the script context lost
1452 // and restored events asynchronously. For example, if it triggers a context
1453 // loss, the webglcontextlost event will be sent to it the next time the
1454 // robustness timer fires.
1455 // Note that this timer mechanism is not used unless one of these 3 criteria are
1456 // met.
1457 // At a bare minimum, from context lost to context restores, it would take 3
1458 // full timer iterations: detection, webglcontextlost, webglcontextrestored.
1459 void WebGLContext::CheckForContextLoss() {
1460 bool isGuilty = true;
1461 const auto isContextLost = CheckContextLost(gl, &isGuilty);
1462 if (!isContextLost) return;
1464 mWebGLError = LOCAL_GL_CONTEXT_LOST;
1466 auto reason = webgl::ContextLossReason::None;
1467 if (isGuilty) {
1468 reason = webgl::ContextLossReason::Guilty;
1470 LoseContext(reason);
1473 void WebGLContext::HandlePendingContextLoss() {
1474 mIsContextLost = true;
1475 mHost->OnContextLoss(mPendingContextLossReason);
1478 void WebGLContext::LoseContextLruLocked(const webgl::ContextLossReason reason) {
1479 printf_stderr("WebGL(%p)::LoseContext(%u)\n", this,
1480 static_cast<uint32_t>(reason));
1481 mLruPosition.ResetLocked();
1482 mPendingContextLossReason = reason;
1483 mPendingContextLoss = true;
1486 void WebGLContext::LoseContext(const webgl::ContextLossReason reason) {
1487 StaticMutexAutoLock lock(sLruMutex);
1488 LoseContextLruLocked(reason);
1489 HandlePendingContextLoss();
1490 if (mRemoteTextureOwner) {
1491 mRemoteTextureOwner->NotifyContextLost();
1495 void WebGLContext::DidRefresh() {
1496 if (gl) {
1497 gl->FlushIfHeavyGLCallsSinceLastFlush();
1501 ////////////////////////////////////////////////////////////////////////////////
1503 uvec2 WebGLContext::DrawingBufferSize() {
1504 const FuncScope funcScope(*this, "width/height");
1505 if (IsContextLost()) return {};
1507 if (!EnsureDefaultFB()) return {};
1509 return *uvec2::FromSize(mDefaultFB->mSize);
1512 bool WebGLContext::ValidateAndInitFB(const WebGLFramebuffer* const fb,
1513 const GLenum incompleteFbError) {
1514 if (fb) return fb->ValidateAndInitAttachments(incompleteFbError);
1516 if (!EnsureDefaultFB()) return false;
1518 if (mDefaultFB_IsInvalid) {
1519 // Clear it!
1520 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
1521 const webgl::ScopedPrepForResourceClear scopedPrep(*this);
1522 if (!mOptions.alpha) {
1523 gl->fClearColor(0, 0, 0, 1);
1525 const GLbitfield bits = LOCAL_GL_COLOR_BUFFER_BIT |
1526 LOCAL_GL_DEPTH_BUFFER_BIT |
1527 LOCAL_GL_STENCIL_BUFFER_BIT;
1528 gl->fClear(bits);
1530 mDefaultFB_IsInvalid = false;
1532 return true;
1535 void WebGLContext::DoBindFB(const WebGLFramebuffer* const fb,
1536 const GLenum target) const {
1537 const GLenum driverFB = fb ? fb->mGLName : mDefaultFB->mFB;
1538 gl->fBindFramebuffer(target, driverFB);
1541 bool WebGLContext::BindCurFBForDraw() {
1542 const auto& fb = mBoundDrawFramebuffer;
1543 if (!ValidateAndInitFB(fb)) return false;
1545 DoBindFB(fb);
1546 return true;
1549 bool WebGLContext::BindCurFBForColorRead(
1550 const webgl::FormatUsageInfo** const out_format, uint32_t* const out_width,
1551 uint32_t* const out_height, const GLenum incompleteFbError) {
1552 const auto& fb = mBoundReadFramebuffer;
1554 if (fb) {
1555 if (!ValidateAndInitFB(fb, incompleteFbError)) return false;
1556 if (!fb->ValidateForColorRead(out_format, out_width, out_height))
1557 return false;
1559 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb->mGLName);
1560 return true;
1563 if (!BindDefaultFBForRead()) return false;
1565 if (mDefaultFB_ReadBuffer == LOCAL_GL_NONE) {
1566 ErrorInvalidOperation(
1567 "Can't read from backbuffer when readBuffer mode is NONE.");
1568 return false;
1571 auto effFormat = mOptions.alpha ? webgl::EffectiveFormat::RGBA8
1572 : webgl::EffectiveFormat::RGB8;
1574 *out_format = mFormatUsage->GetUsage(effFormat);
1575 MOZ_ASSERT(*out_format);
1577 *out_width = mDefaultFB->mSize.width;
1578 *out_height = mDefaultFB->mSize.height;
1579 return true;
1582 bool WebGLContext::BindDefaultFBForRead() {
1583 if (!ValidateAndInitFB(nullptr)) return false;
1585 if (!mDefaultFB->mSamples) {
1586 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
1587 return true;
1590 if (!mResolvedDefaultFB) {
1591 mResolvedDefaultFB =
1592 gl::MozFramebuffer::Create(gl, mDefaultFB->mSize, 0, false);
1593 if (!mResolvedDefaultFB) {
1594 gfxCriticalNote << FuncName() << ": Failed to create mResolvedDefaultFB.";
1595 return false;
1599 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
1600 BlitBackbufferToCurDriverFB();
1602 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
1603 return true;
1606 void WebGLContext::DoColorMask(Maybe<GLuint> i, const uint8_t bitmask) const {
1607 if (!IsExtensionEnabled(WebGLExtensionID::OES_draw_buffers_indexed)) {
1608 i = Nothing();
1610 const auto bs = std::bitset<4>(bitmask);
1611 if (i) {
1612 gl->fColorMaski(*i, bs[0], bs[1], bs[2], bs[3]);
1613 } else {
1614 gl->fColorMask(bs[0], bs[1], bs[2], bs[3]);
1618 ////////////////////////////////////////////////////////////////////////////////
1620 ScopedDrawCallWrapper::ScopedDrawCallWrapper(WebGLContext& webgl)
1621 : mWebGL(webgl) {
1622 uint8_t driverColorMask0 = mWebGL.mColorWriteMask0;
1623 bool driverDepthTest = mWebGL.mDepthTestEnabled;
1624 bool driverStencilTest = mWebGL.mStencilTestEnabled;
1625 const auto& fb = mWebGL.mBoundDrawFramebuffer;
1626 if (!fb) {
1627 if (mWebGL.mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE) {
1628 driverColorMask0 = 0; // Is this well-optimized enough for depth-first
1629 // rendering?
1630 } else {
1631 driverColorMask0 &= ~(uint8_t(mWebGL.mNeedsFakeNoAlpha) << 3);
1633 driverDepthTest &= !mWebGL.mNeedsFakeNoDepth;
1634 driverStencilTest &= !mWebGL.mNeedsFakeNoStencil;
1637 const auto& gl = mWebGL.gl;
1638 mWebGL.DoColorMask(Some(0), driverColorMask0);
1639 if (mWebGL.mDriverDepthTest != driverDepthTest) {
1640 // "When disabled, the depth comparison and subsequent possible updates to
1641 // the
1642 // depth buffer value are bypassed and the fragment is passed to the next
1643 // operation." [GLES 3.0.5, p177]
1644 mWebGL.mDriverDepthTest = driverDepthTest;
1645 gl->SetEnabled(LOCAL_GL_DEPTH_TEST, mWebGL.mDriverDepthTest);
1647 if (mWebGL.mDriverStencilTest != driverStencilTest) {
1648 // "When disabled, the stencil test and associated modifications are not
1649 // made, and
1650 // the fragment is always passed." [GLES 3.0.5, p175]
1651 mWebGL.mDriverStencilTest = driverStencilTest;
1652 gl->SetEnabled(LOCAL_GL_STENCIL_TEST, mWebGL.mDriverStencilTest);
1656 ScopedDrawCallWrapper::~ScopedDrawCallWrapper() {
1657 if (mWebGL.mBoundDrawFramebuffer) return;
1659 mWebGL.mResolvedDefaultFB = nullptr;
1660 mWebGL.mShouldPresent = true;
1663 // -
1665 void WebGLContext::ScissorRect::Apply(gl::GLContext& gl) const {
1666 gl.fScissor(x, y, w, h);
1669 ////////////////////////////////////////
1671 IndexedBufferBinding::IndexedBufferBinding() : mRangeStart(0), mRangeSize(0) {}
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