Bug 1858921 - Part 6: Remove unused default template arguments r=sfink
[gecko.git] / dom / canvas / WebGLContext.cpp
blobadef5e7a1520be8f01fa03ef173f6fb2e4752eec
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 gl::CreateContextFlags flags = (gl::CreateContextFlags::NO_VALIDATION |
273 gl::CreateContextFlags::PREFER_ROBUSTNESS);
274 bool tryNativeGL = true;
275 bool tryANGLE = false;
277 // -
279 if (StaticPrefs::webgl_forbid_hardware()) {
280 flags |= gl::CreateContextFlags::FORBID_HARDWARE;
282 if (StaticPrefs::webgl_forbid_software()) {
283 flags |= gl::CreateContextFlags::FORBID_SOFTWARE;
286 if (forceEnabled) {
287 flags &= ~gl::CreateContextFlags::FORBID_HARDWARE;
288 flags &= ~gl::CreateContextFlags::FORBID_SOFTWARE;
291 if ((flags & gl::CreateContextFlags::FORBID_HARDWARE) &&
292 (flags & gl::CreateContextFlags::FORBID_SOFTWARE)) {
293 FailureReason reason;
294 reason.info = "Both hardware and software were forbidden by config.";
295 out_failReasons->push_back(reason);
296 GenerateWarning("%s", reason.info.BeginReading());
297 return false;
300 // -
302 if (StaticPrefs::webgl_cgl_multithreaded()) {
303 flags |= gl::CreateContextFlags::PREFER_MULTITHREADED;
306 if (IsWebGL2()) {
307 flags |= gl::CreateContextFlags::PREFER_ES3;
308 } else {
309 // Request and prefer ES2 context for WebGL1.
310 flags |= gl::CreateContextFlags::PREFER_EXACT_VERSION;
312 if (!StaticPrefs::webgl_1_allow_core_profiles()) {
313 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
318 auto powerPref = mOptions.powerPreference;
320 // If "Use hardware acceleration when available" option is disabled:
321 if (!gfx::gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING)) {
322 powerPref = dom::WebGLPowerPreference::Low_power;
325 const auto overrideVal = StaticPrefs::webgl_power_preference_override();
326 if (overrideVal > 0) {
327 powerPref = dom::WebGLPowerPreference::High_performance;
328 } else if (overrideVal < 0) {
329 powerPref = dom::WebGLPowerPreference::Low_power;
332 if (powerPref == dom::WebGLPowerPreference::High_performance) {
333 flags |= gl::CreateContextFlags::HIGH_POWER;
337 if (!gfx::gfxVars::WebglAllowCoreProfile()) {
338 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
341 // --
343 const bool useEGL = PR_GetEnv("MOZ_WEBGL_FORCE_EGL");
345 #ifdef XP_WIN
346 tryNativeGL = false;
347 tryANGLE = true;
349 if (StaticPrefs::webgl_disable_wgl()) {
350 tryNativeGL = false;
353 if (StaticPrefs::webgl_disable_angle() ||
354 PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL") || useEGL) {
355 tryNativeGL = true;
356 tryANGLE = false;
358 #endif
360 if (tryNativeGL && !forceEnabled) {
361 FailureReason reason;
362 if (!gfx::gfxVars::WebglAllowWindowsNativeGl()) {
363 reason.info =
364 "WebglAllowWindowsNativeGl:false restricts context creation on this "
365 "system.";
367 out_failReasons->push_back(reason);
369 GenerateWarning("%s", reason.info.BeginReading());
370 tryNativeGL = false;
374 // --
376 using fnCreateT = decltype(gl::GLContextProviderEGL::CreateHeadless);
377 const auto fnCreate = [&](fnCreateT* const pfnCreate,
378 const char* const info) {
379 nsCString failureId;
380 const RefPtr<gl::GLContext> gl = pfnCreate({flags}, &failureId);
381 if (!gl) {
382 out_failReasons->push_back(WebGLContext::FailureReason(failureId, info));
384 return gl;
387 const auto newGL = [&]() -> RefPtr<gl::GLContext> {
388 if (tryNativeGL) {
389 if (useEGL)
390 return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "useEGL");
392 const auto ret =
393 fnCreate(&gl::GLContextProvider::CreateHeadless, "tryNativeGL");
394 if (ret) return ret;
397 if (tryANGLE) {
398 return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "tryANGLE");
400 return nullptr;
401 }();
403 if (!newGL) {
404 out_failReasons->push_back(
405 FailureReason("FEATURE_FAILURE_WEBGL_EXHAUSTED_DRIVERS",
406 "Exhausted GL driver options."));
407 return false;
410 // --
412 FailureReason reason;
414 mGL_OnlyClearInDestroyResourcesAndContext = newGL;
415 MOZ_RELEASE_ASSERT(gl);
416 if (!InitAndValidateGL(&reason)) {
417 DestroyResourcesAndContext();
418 MOZ_RELEASE_ASSERT(!gl);
420 // The fail reason here should be specific enough for now.
421 out_failReasons->push_back(reason);
422 return false;
425 const auto val = StaticPrefs::webgl_debug_incomplete_tex_color();
426 if (val) {
427 mIncompleteTexOverride.reset(new gl::Texture(*gl));
428 const gl::ScopedBindTexture autoBind(gl, mIncompleteTexOverride->name);
429 const auto heapVal = std::make_unique<uint32_t>(val);
430 gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 1, 1, 0,
431 LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, heapVal.get());
434 return true;
437 // Fallback for resizes:
439 bool WebGLContext::EnsureDefaultFB() {
440 if (mDefaultFB) {
441 MOZ_ASSERT(*uvec2::FromSize(mDefaultFB->mSize) == mRequestedSize);
442 return true;
445 const bool depthStencil = mOptions.depth || mOptions.stencil;
446 auto attemptSize = gfx::IntSize{mRequestedSize.x, mRequestedSize.y};
448 while (attemptSize.width || attemptSize.height) {
449 attemptSize.width = std::max(attemptSize.width, 1);
450 attemptSize.height = std::max(attemptSize.height, 1);
452 [&]() {
453 if (mOptions.antialias) {
454 MOZ_ASSERT(!mDefaultFB);
455 mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, mMsaaSamples,
456 depthStencil);
457 if (mDefaultFB) return;
458 if (mOptionsFrozen) return;
461 MOZ_ASSERT(!mDefaultFB);
462 mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, 0, depthStencil);
463 }();
465 if (mDefaultFB) break;
467 attemptSize.width /= 2;
468 attemptSize.height /= 2;
471 if (!mDefaultFB) {
472 GenerateWarning("Backbuffer resize failed. Losing context.");
473 LoseContext();
474 return false;
477 mDefaultFB_IsInvalid = true;
479 const auto actualSize = *uvec2::FromSize(mDefaultFB->mSize);
480 if (actualSize != mRequestedSize) {
481 GenerateWarning(
482 "Requested size %ux%u was too large, but resize"
483 " to %ux%u succeeded.",
484 mRequestedSize.x, mRequestedSize.y, actualSize.x, actualSize.y);
486 mRequestedSize = actualSize;
487 return true;
490 void WebGLContext::Resize(uvec2 requestedSize) {
491 // Zero-sized surfaces can cause problems.
492 if (!requestedSize.x) {
493 requestedSize.x = 1;
495 if (!requestedSize.y) {
496 requestedSize.y = 1;
499 // Kill our current default fb(s), for later lazy allocation.
500 mRequestedSize = requestedSize;
501 mDefaultFB = nullptr;
502 mResetLayer = true; // New size means new Layer.
505 UniquePtr<webgl::FormatUsageAuthority> WebGLContext::CreateFormatUsage(
506 gl::GLContext* gl) const {
507 return webgl::FormatUsageAuthority::CreateForWebGL1(gl);
510 /*static*/
511 RefPtr<WebGLContext> WebGLContext::Create(HostWebGLContext& host,
512 const webgl::InitContextDesc& desc,
513 webgl::InitContextResult* const out) {
514 AUTO_PROFILER_LABEL("WebGLContext::Create", GRAPHICS);
515 nsCString failureId = "FEATURE_FAILURE_WEBGL_UNKOWN"_ns;
516 const bool forceEnabled = StaticPrefs::webgl_force_enabled();
517 ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
519 auto res = [&]() -> Result<RefPtr<WebGLContext>, std::string> {
520 bool disabled = StaticPrefs::webgl_disabled();
522 // TODO: When we have software webgl support we should use that instead.
523 disabled |= gfxPlatform::InSafeMode();
525 if (disabled) {
526 if (gfxPlatform::InSafeMode()) {
527 failureId = "FEATURE_FAILURE_WEBGL_SAFEMODE"_ns;
528 } else {
529 failureId = "FEATURE_FAILURE_WEBGL_DISABLED"_ns;
531 return Err("WebGL is currently disabled.");
534 // Alright, now let's start trying.
536 RefPtr<WebGLContext> webgl;
537 if (desc.isWebgl2) {
538 webgl = new WebGL2Context(host, desc);
539 } else {
540 webgl = new WebGLContext(host, desc);
543 MOZ_ASSERT(!webgl->gl);
544 std::vector<FailureReason> failReasons;
545 if (!webgl->CreateAndInitGL(forceEnabled, &failReasons)) {
546 nsCString text("WebGL creation failed: ");
547 for (const auto& cur : failReasons) {
548 // Don't try to accumulate using an empty key if |cur.key| is empty.
549 if (cur.key.IsEmpty()) {
550 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID,
551 "FEATURE_FAILURE_REASON_UNKNOWN"_ns);
552 } else {
553 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, cur.key);
556 const auto str = nsPrintfCString("\n* %s (%s)", cur.info.BeginReading(),
557 cur.key.BeginReading());
558 text.Append(str);
560 failureId = "FEATURE_FAILURE_REASON"_ns;
561 return Err(text.BeginReading());
563 MOZ_ASSERT(webgl->gl);
565 if (desc.options.failIfMajorPerformanceCaveat) {
566 if (webgl->gl->IsWARP()) {
567 failureId = "FEATURE_FAILURE_WEBGL_PERF_WARP"_ns;
568 return Err(
569 "failIfMajorPerformanceCaveat: Driver is not"
570 " hardware-accelerated.");
573 #ifdef XP_WIN
574 if (webgl->gl->GetContextType() == gl::GLContextType::WGL &&
575 !gl::sWGLLib.HasDXInterop2()) {
576 failureId = "FEATURE_FAILURE_WEBGL_DXGL_INTEROP2"_ns;
577 return Err("failIfMajorPerformanceCaveat: WGL without DXGLInterop2.");
579 #endif
582 const FuncScope funcScope(*webgl, "getContext/restoreContext");
584 MOZ_ASSERT(!webgl->mDefaultFB);
585 if (!webgl->EnsureDefaultFB()) {
586 MOZ_ASSERT(!webgl->mDefaultFB);
587 MOZ_ASSERT(webgl->IsContextLost());
588 failureId = "FEATURE_FAILURE_WEBGL_BACKBUFFER"_ns;
589 return Err("Initializing WebGL backbuffer failed.");
592 return webgl;
593 }();
594 if (res.isOk()) {
595 failureId = "SUCCESS"_ns;
597 Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, failureId);
599 if (!res.isOk()) {
600 out->error = res.unwrapErr();
601 return nullptr;
603 const auto webgl = res.unwrap();
605 // Update our internal stuff:
606 webgl->FinishInit();
608 reporter.SetSuccessful();
609 if (gl::GLContext::ShouldSpew()) {
610 printf_stderr("--- WebGL context created: %p\n", webgl.get());
613 // -
615 const auto UploadableSdTypes = [&]() {
616 webgl::EnumMask<layers::SurfaceDescriptor::Type> types;
617 types[layers::SurfaceDescriptor::TSurfaceDescriptorBuffer] = true;
618 if (webgl->gl->IsANGLE()) {
619 types[layers::SurfaceDescriptor::TSurfaceDescriptorD3D10] = true;
620 types[layers::SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr] = true;
622 if (kIsMacOS) {
623 types[layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface] = true;
625 if (kIsAndroid) {
626 types[layers::SurfaceDescriptor::TSurfaceTextureDescriptor] = true;
628 if (kIsLinux) {
629 types[layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf] = true;
631 return types;
634 // -
636 out->options = webgl->mOptions;
637 out->limits = *webgl->mLimits;
638 out->uploadableSdTypes = UploadableSdTypes();
639 out->vendor = webgl->gl->Vendor();
641 return webgl;
644 void WebGLContext::FinishInit() {
645 mOptions.antialias &= bool(mDefaultFB->mSamples);
647 if (!mOptions.alpha) {
648 // We always have alpha.
649 mNeedsFakeNoAlpha = true;
652 if (mOptions.depth || mOptions.stencil) {
653 // We always have depth+stencil if we have either.
654 if (!mOptions.depth) {
655 mNeedsFakeNoDepth = true;
657 if (!mOptions.stencil) {
658 mNeedsFakeNoStencil = true;
662 mResetLayer = true;
663 mOptionsFrozen = true;
665 //////
666 // Initial setup.
668 gl->mImplicitMakeCurrent = true;
669 gl->mElideDuplicateBindFramebuffers = true;
671 const auto& size = mDefaultFB->mSize;
673 mViewportX = mViewportY = 0;
674 mViewportWidth = size.width;
675 mViewportHeight = size.height;
676 gl->fViewport(mViewportX, mViewportY, mViewportWidth, mViewportHeight);
678 mScissorRect = {0, 0, size.width, size.height};
679 mScissorRect.Apply(*gl);
681 //////
682 // Check everything
684 AssertCachedBindings();
685 AssertCachedGlobalState();
687 mShouldPresent = true;
689 //////
691 gl->ResetSyncCallCount("WebGLContext Initialization");
692 LoseLruContextIfLimitExceeded();
695 void WebGLContext::SetCompositableHost(
696 RefPtr<layers::CompositableHost>& aCompositableHost) {
697 mCompositableHost = aCompositableHost;
700 void WebGLContext::BumpLruLocked() {
701 if (!mIsContextLost && !mPendingContextLoss) {
702 mLruPosition.AssignLocked(*this);
703 } else {
704 MOZ_ASSERT(!mLruPosition.IsInsertedLocked());
708 void WebGLContext::BumpLru() {
709 StaticMutexAutoLock lock(sLruMutex);
710 BumpLruLocked();
713 void WebGLContext::LoseLruContextIfLimitExceeded() {
714 StaticMutexAutoLock lock(sLruMutex);
716 const auto maxContexts = std::max(1u, StaticPrefs::webgl_max_contexts());
717 const auto maxContextsPerPrincipal =
718 std::max(1u, StaticPrefs::webgl_max_contexts_per_principal());
720 // it's important to update the index on a new context before losing old
721 // contexts, otherwise new unused contexts would all have index 0 and we
722 // couldn't distinguish older ones when choosing which one to lose first.
723 BumpLruLocked();
726 size_t forPrincipal = 0;
727 for (const auto& context : sLru) {
728 if (context->mPrincipalKey == mPrincipalKey) {
729 forPrincipal += 1;
733 while (forPrincipal > maxContextsPerPrincipal) {
734 const auto text = nsPrintfCString(
735 "Exceeded %u live WebGL contexts for this principal, losing the "
736 "least recently used one.",
737 maxContextsPerPrincipal);
738 mHost->JsWarning(ToString(text));
740 for (const auto& context : sLru) {
741 if (context->mPrincipalKey == mPrincipalKey) {
742 MOZ_ASSERT(context != this);
743 context->LoseContextLruLocked(webgl::ContextLossReason::None);
744 forPrincipal -= 1;
745 break;
751 auto total = sLru.size();
752 while (total > maxContexts) {
753 const auto text = nsPrintfCString(
754 "Exceeded %u live WebGL contexts, losing the least "
755 "recently used one.",
756 maxContexts);
757 mHost->JsWarning(ToString(text));
759 const auto& context = sLru.front();
760 MOZ_ASSERT(context != this);
761 context->LoseContextLruLocked(webgl::ContextLossReason::None);
762 total -= 1;
766 // -
768 namespace webgl {
770 ScopedPrepForResourceClear::ScopedPrepForResourceClear(
771 const WebGLContext& webgl_)
772 : webgl(webgl_) {
773 const auto& gl = webgl.gl;
775 if (webgl.mScissorTestEnabled) {
776 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
778 if (webgl.mRasterizerDiscardEnabled) {
779 gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD);
782 // "The clear operation always uses the front stencil write mask
783 // when clearing the stencil buffer."
784 webgl.DoColorMask(Some(0), 0b1111);
785 gl->fDepthMask(true);
786 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff);
788 gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
789 gl->fClearDepth(1.0f); // Depth formats are always cleared to 1.0f, not 0.0f.
790 gl->fClearStencil(0);
793 ScopedPrepForResourceClear::~ScopedPrepForResourceClear() {
794 const auto& gl = webgl.gl;
796 if (webgl.mScissorTestEnabled) {
797 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
799 if (webgl.mRasterizerDiscardEnabled) {
800 gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
803 webgl.DoColorMask(Some(0), webgl.mColorWriteMask0);
804 gl->fDepthMask(webgl.mDepthWriteMask);
805 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, webgl.mStencilWriteMaskFront);
807 gl->fClearColor(webgl.mColorClearValue[0], webgl.mColorClearValue[1],
808 webgl.mColorClearValue[2], webgl.mColorClearValue[3]);
809 gl->fClearDepth(webgl.mDepthClearValue);
810 gl->fClearStencil(webgl.mStencilClearValue);
813 } // namespace webgl
815 // -
817 void WebGLContext::OnEndOfFrame() {
818 if (StaticPrefs::webgl_perf_spew_frame_allocs()) {
819 GeneratePerfWarning("[webgl.perf.spew-frame-allocs] %" PRIu64
820 " data allocations this frame.",
821 mDataAllocGLCallCount);
823 mDataAllocGLCallCount = 0;
824 gl->ResetSyncCallCount("WebGLContext PresentScreenBuffer");
826 mDrawCallsSinceLastFlush = 0;
828 BumpLru();
831 void WebGLContext::BlitBackbufferToCurDriverFB(
832 WebGLFramebuffer* const srcAsWebglFb,
833 const gl::MozFramebuffer* const srcAsMozFb, bool srcIsBGRA) const {
834 // BlitFramebuffer ignores ColorMask().
836 if (mScissorTestEnabled) {
837 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
840 [&]() {
841 // If a MozFramebuffer is supplied, ensure that a WebGLFramebuffer is not
842 // used since it might not have completeness info, while the MozFramebuffer
843 // can still supply the needed information.
844 MOZ_ASSERT(!(srcAsMozFb && srcAsWebglFb));
845 const auto* mozFb = srcAsMozFb ? srcAsMozFb : mDefaultFB.get();
846 GLuint fbo = 0;
847 gfx::IntSize size;
848 if (srcAsWebglFb) {
849 fbo = srcAsWebglFb->mGLName;
850 const auto* info = srcAsWebglFb->GetCompletenessInfo();
851 MOZ_ASSERT(info);
852 size = gfx::IntSize(info->width, info->height);
853 } else {
854 fbo = mozFb->mFB;
855 size = mozFb->mSize;
858 // If no format conversion is necessary, then attempt to directly blit
859 // between framebuffers. Otherwise, if we need to convert to RGBA from
860 // the source format, then we will need to use the texture blit path
861 // below.
862 if (!srcIsBGRA) {
863 if (gl->IsSupported(gl::GLFeature::framebuffer_blit)) {
864 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
865 gl->fBlitFramebuffer(0, 0, size.width, size.height, 0, 0, size.width,
866 size.height, LOCAL_GL_COLOR_BUFFER_BIT,
867 LOCAL_GL_NEAREST);
868 return;
870 if (mDefaultFB->mSamples &&
871 gl->IsExtensionSupported(
872 gl::GLContext::APPLE_framebuffer_multisample)) {
873 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
874 gl->fResolveMultisampleFramebufferAPPLE();
875 return;
879 GLuint colorTex = 0;
880 if (srcAsWebglFb) {
881 const auto& attach = srcAsWebglFb->ColorAttachment0();
882 MOZ_ASSERT(attach.Texture());
883 colorTex = attach.Texture()->mGLName;
884 } else {
885 colorTex = mozFb->ColorTex();
888 // DrawBlit handles ColorMask itself.
889 gl->BlitHelper()->DrawBlitTextureToFramebuffer(
890 colorTex, size, size, LOCAL_GL_TEXTURE_2D, srcIsBGRA);
891 }();
893 if (mScissorTestEnabled) {
894 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
898 // -
900 template <typename T, typename... Args>
901 constexpr auto MakeArray(Args... args) -> std::array<T, sizeof...(Args)> {
902 return {{static_cast<T>(args)...}};
905 inline gfx::ColorSpace2 ToColorSpace2(const WebGLContextOptions& options) {
906 auto ret = gfx::ColorSpace2::UNKNOWN;
907 if (true) {
908 ret = gfx::ColorSpace2::SRGB;
910 if (!options.ignoreColorSpace) {
911 ret = gfx::ToColorSpace2(options.colorSpace);
913 return ret;
916 // -
918 // For an overview of how WebGL compositing works, see:
919 // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
920 bool WebGLContext::PresentInto(gl::SwapChain& swapChain) {
921 OnEndOfFrame();
923 if (!ValidateAndInitFB(nullptr)) return false;
926 const auto colorSpace = ToColorSpace2(mOptions);
927 auto presenter = swapChain.Acquire(mDefaultFB->mSize, colorSpace);
928 if (!presenter) {
929 GenerateWarning("Swap chain surface creation failed.");
930 LoseContext();
931 return false;
934 const auto destFb = presenter->Fb();
935 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
937 BlitBackbufferToCurDriverFB();
939 if (!mOptions.preserveDrawingBuffer) {
940 if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
941 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB);
942 constexpr auto attachments = MakeArray<GLenum>(
943 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
944 gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
945 attachments.size(), attachments.data());
947 mDefaultFB_IsInvalid = true;
950 #ifdef DEBUG
951 if (!mOptions.alpha) {
952 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
953 gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
954 if (IsWebGL2()) {
955 gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, 0);
956 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
957 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
959 uint32_t pixel = 0xffbadbad;
960 gl->fReadPixels(0, 0, 1, 1, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
961 &pixel);
962 MOZ_ASSERT((pixel & 0xff000000) == 0xff000000);
964 #endif
967 return true;
970 bool WebGLContext::PresentIntoXR(gl::SwapChain& swapChain,
971 const gl::MozFramebuffer& fb) {
972 OnEndOfFrame();
974 const auto colorSpace = ToColorSpace2(mOptions);
975 auto presenter = swapChain.Acquire(fb.mSize, colorSpace);
976 if (!presenter) {
977 GenerateWarning("Swap chain surface creation failed.");
978 LoseContext();
979 return false;
982 const auto destFb = presenter->Fb();
983 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
985 BlitBackbufferToCurDriverFB(nullptr, &fb);
987 // https://immersive-web.github.io/webxr/#opaque-framebuffer
988 // Opaque framebuffers will always be cleared regardless of the
989 // associated WebGL context’s preserveDrawingBuffer value.
990 if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
991 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb.mFB);
992 constexpr auto attachments = MakeArray<GLenum>(
993 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
994 gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, attachments.size(),
995 attachments.data());
998 return true;
1001 // Initialize a swap chain's surface factory given the desired surface type.
1002 void InitSwapChain(gl::GLContext& gl, gl::SwapChain& swapChain,
1003 const layers::TextureType consumerType) {
1004 if (!swapChain.mFactory) {
1005 auto typedFactory = gl::SurfaceFactory::Create(&gl, consumerType);
1006 if (typedFactory) {
1007 swapChain.mFactory = std::move(typedFactory);
1010 if (!swapChain.mFactory) {
1011 NS_WARNING("Failed to make an ideal SurfaceFactory.");
1012 swapChain.mFactory = MakeUnique<gl::SurfaceFactory_Basic>(gl);
1014 MOZ_ASSERT(swapChain.mFactory);
1017 void WebGLContext::Present(WebGLFramebuffer* const xrFb,
1018 const layers::TextureType consumerType,
1019 const bool webvr,
1020 const webgl::SwapChainOptions& options) {
1021 const FuncScope funcScope(*this, "<Present>");
1022 if (IsContextLost()) return;
1024 auto swapChain = GetSwapChain(xrFb, webvr);
1025 const gl::MozFramebuffer* maybeFB = nullptr;
1026 if (xrFb) {
1027 maybeFB = xrFb->mOpaque.get();
1028 } else {
1029 mResolvedDefaultFB = nullptr;
1032 InitSwapChain(*gl, *swapChain, consumerType);
1034 bool valid =
1035 maybeFB ? PresentIntoXR(*swapChain, *maybeFB) : PresentInto(*swapChain);
1036 if (!valid) {
1037 return;
1040 bool useAsync = options.remoteTextureOwnerId.IsValid() &&
1041 options.remoteTextureId.IsValid();
1042 if (useAsync) {
1043 PushRemoteTexture(nullptr, *swapChain, swapChain->FrontBuffer(), options);
1047 void WebGLContext::CopyToSwapChain(WebGLFramebuffer* const srcFb,
1048 const layers::TextureType consumerType,
1049 const webgl::SwapChainOptions& options) {
1050 const FuncScope funcScope(*this, "<CopyToSwapChain>");
1051 if (IsContextLost()) return;
1053 OnEndOfFrame();
1055 if (!srcFb) return;
1056 const auto* info = srcFb->GetCompletenessInfo();
1057 if (!info) {
1058 return;
1060 gfx::IntSize size(info->width, info->height);
1062 InitSwapChain(*gl, srcFb->mSwapChain, consumerType);
1064 bool useAsync = options.remoteTextureOwnerId.IsValid() &&
1065 options.remoteTextureId.IsValid();
1066 // If we're using async present and if there is no way to serialize surfaces,
1067 // then a readback is required to do the copy. In this case, there's no reason
1068 // to copy into a separate shared surface for the front buffer. Just directly
1069 // read back the WebGL framebuffer into and push it as a remote texture.
1070 if (useAsync && srcFb->mSwapChain.mFactory->GetConsumerType() ==
1071 layers::TextureType::Unknown) {
1072 PushRemoteTexture(srcFb, srcFb->mSwapChain, nullptr, options);
1073 return;
1077 // ColorSpace will need to be part of SwapChainOptions for DTWebgl.
1078 const auto colorSpace = ToColorSpace2(mOptions);
1079 auto presenter = srcFb->mSwapChain.Acquire(size, colorSpace);
1080 if (!presenter) {
1081 GenerateWarning("Swap chain surface creation failed.");
1082 LoseContext();
1083 return;
1086 const ScopedFBRebinder saveFB(this);
1088 const auto destFb = presenter->Fb();
1089 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
1091 BlitBackbufferToCurDriverFB(srcFb, nullptr, options.bgra);
1094 if (useAsync) {
1095 PushRemoteTexture(srcFb, srcFb->mSwapChain, srcFb->mSwapChain.FrontBuffer(),
1096 options);
1100 bool WebGLContext::PushRemoteTexture(WebGLFramebuffer* fb,
1101 gl::SwapChain& swapChain,
1102 std::shared_ptr<gl::SharedSurface> surf,
1103 const webgl::SwapChainOptions& options) {
1104 const auto onFailure = [&]() -> bool {
1105 GenerateWarning("Remote texture creation failed.");
1106 LoseContext();
1107 if (mRemoteTextureOwner) {
1108 mRemoteTextureOwner->PushDummyTexture(options.remoteTextureId,
1109 options.remoteTextureOwnerId);
1111 return false;
1114 if (!mRemoteTextureOwner) {
1115 // Ensure we have a remote texture owner client for WebGLParent.
1116 const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr;
1117 if (!outOfProcess) {
1118 return onFailure();
1120 mRemoteTextureOwner =
1121 MakeRefPtr<layers::RemoteTextureOwnerClient>(outOfProcess->OtherPid());
1124 layers::RemoteTextureOwnerId ownerId = options.remoteTextureOwnerId;
1125 layers::RemoteTextureId textureId = options.remoteTextureId;
1127 if (!mRemoteTextureOwner->IsRegistered(ownerId)) {
1128 // Register a texture owner to represent the swap chain.
1129 RefPtr<layers::RemoteTextureOwnerClient> textureOwner = mRemoteTextureOwner;
1130 auto destroyedCallback = [textureOwner, ownerId]() {
1131 textureOwner->UnregisterTextureOwner(ownerId);
1134 swapChain.SetDestroyedCallback(destroyedCallback);
1135 mRemoteTextureOwner->RegisterTextureOwner(
1136 ownerId,
1137 /* aIsSyncMode */ gfx::gfxVars::WebglOopAsyncPresentForceSync());
1140 MOZ_ASSERT(fb || surf);
1141 gfx::IntSize size;
1142 if (surf) {
1143 size = surf->mDesc.size;
1144 } else {
1145 const auto* info = fb->GetCompletenessInfo();
1146 MOZ_ASSERT(info);
1147 size = gfx::IntSize(info->width, info->height);
1150 const auto surfaceFormat = mOptions.alpha ? gfx::SurfaceFormat::B8G8R8A8
1151 : gfx::SurfaceFormat::B8G8R8X8;
1152 Maybe<layers::SurfaceDescriptor> desc;
1153 if (surf) {
1154 desc = surf->ToSurfaceDescriptor();
1156 if (!desc) {
1157 if (surf && surf->mDesc.type != gl::SharedSurfaceType::Basic) {
1158 return onFailure();
1160 // If we can't serialize to a surface descriptor, then we need to create
1161 // a buffer to read back into that will become the remote texture.
1162 auto data = mRemoteTextureOwner->CreateOrRecycleBufferTextureData(
1163 ownerId, size, surfaceFormat);
1164 if (!data) {
1165 gfxCriticalNoteOnce << "Failed to allocate BufferTextureData";
1166 return onFailure();
1169 layers::MappedTextureData mappedData;
1170 if (!data->BorrowMappedData(mappedData)) {
1171 return onFailure();
1174 Range<uint8_t> range = {mappedData.data,
1175 data->AsBufferTextureData()->GetBufferSize()};
1177 // If we have a surface representing the front buffer, then try to snapshot
1178 // that. Otherwise, when there is no surface, we read back directly from the
1179 // WebGL framebuffer.
1180 auto valid =
1181 surf ? FrontBufferSnapshotInto(surf, Some(range),
1182 Some(mappedData.stride))
1183 : SnapshotInto(fb->mGLName, size, range, Some(mappedData.stride));
1184 if (!valid) {
1185 return onFailure();
1188 if (!options.bgra) {
1189 // If the buffer is already BGRA, we don't need to swizzle. However, if it
1190 // is RGBA, then a swizzle to BGRA is required.
1191 bool rv = gfx::SwizzleData(mappedData.data, mappedData.stride,
1192 gfx::SurfaceFormat::R8G8B8A8, mappedData.data,
1193 mappedData.stride,
1194 gfx::SurfaceFormat::B8G8R8A8, mappedData.size);
1195 MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
1198 mRemoteTextureOwner->PushTexture(textureId, ownerId, std::move(data));
1199 return true;
1202 // SharedSurfaces of SurfaceDescriptorD3D10 and SurfaceDescriptorMacIOSurface
1203 // need to be kept alive. They will be recycled by
1204 // RemoteTextureOwnerClient::GetRecycledSharedSurface() when their usages are
1205 // ended.
1206 std::shared_ptr<gl::SharedSurface> keepAlive;
1207 switch (desc->type()) {
1208 case layers::SurfaceDescriptor::TSurfaceDescriptorD3D10:
1209 case layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface:
1210 case layers::SurfaceDescriptor::TSurfaceTextureDescriptor:
1211 case layers::SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer:
1212 case layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf:
1213 keepAlive = surf;
1214 break;
1215 default:
1216 break;
1219 auto data =
1220 MakeUnique<layers::SharedSurfaceTextureData>(*desc, surfaceFormat, size);
1221 mRemoteTextureOwner->PushTexture(textureId, ownerId, std::move(data),
1222 keepAlive);
1223 auto recycledSurface = mRemoteTextureOwner->GetRecycledSharedSurface(ownerId);
1224 if (recycledSurface) {
1225 swapChain.StoreRecycledSurface(recycledSurface);
1227 return true;
1230 void WebGLContext::EndOfFrame() {
1231 const FuncScope funcScope(*this, "<EndOfFrame>");
1232 if (IsContextLost()) return;
1234 OnEndOfFrame();
1237 gl::SwapChain* WebGLContext::GetSwapChain(WebGLFramebuffer* const xrFb,
1238 const bool webvr) {
1239 auto swapChain = webvr ? &mWebVRSwapChain : &mSwapChain;
1240 if (xrFb) {
1241 swapChain = &xrFb->mSwapChain;
1243 return swapChain;
1246 Maybe<layers::SurfaceDescriptor> WebGLContext::GetFrontBuffer(
1247 WebGLFramebuffer* const xrFb, const bool webvr) {
1248 auto* swapChain = GetSwapChain(xrFb, webvr);
1249 if (!swapChain) return {};
1250 const auto& front = swapChain->FrontBuffer();
1251 if (!front) return {};
1253 return front->ToSurfaceDescriptor();
1256 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
1257 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
1258 const auto& front = mSwapChain.FrontBuffer();
1259 if (!front) return {};
1260 return FrontBufferSnapshotInto(front, maybeDest, destStride);
1263 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
1264 const std::shared_ptr<gl::SharedSurface>& front,
1265 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
1266 const auto& size = front->mDesc.size;
1267 if (!maybeDest) return Some(*uvec2::FromSize(size));
1269 // -
1271 front->WaitForBufferOwnership();
1272 front->LockProd();
1273 front->ProducerReadAcquire();
1274 auto reset = MakeScopeExit([&] {
1275 front->ProducerReadRelease();
1276 front->UnlockProd();
1279 // -
1281 return SnapshotInto(front->mFb ? front->mFb->mFB : 0, size, *maybeDest,
1282 destStride);
1285 Maybe<uvec2> WebGLContext::SnapshotInto(GLuint srcFb, const gfx::IntSize& size,
1286 const Range<uint8_t>& dest,
1287 const Maybe<size_t> destStride) {
1288 const auto minStride = CheckedInt<size_t>(size.width) * 4;
1289 if (!minStride.isValid()) {
1290 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width;
1291 return {};
1293 size_t stride = destStride.valueOr(minStride.value());
1294 if (stride < minStride.value() || (stride % 4) != 0) {
1295 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width
1296 << ", stride:" << stride;
1297 return {};
1300 gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
1301 if (IsWebGL2()) {
1302 gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH,
1303 stride > minStride.value() ? stride / 4 : 0);
1304 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
1305 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
1308 // -
1310 const auto readFbWas = mBoundReadFramebuffer;
1311 const auto pboWas = mBoundPixelPackBuffer;
1313 GLenum fbTarget = LOCAL_GL_READ_FRAMEBUFFER;
1314 if (!IsWebGL2()) {
1315 fbTarget = LOCAL_GL_FRAMEBUFFER;
1317 auto reset2 = MakeScopeExit([&] {
1318 DoBindFB(readFbWas, fbTarget);
1319 if (pboWas) {
1320 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
1324 gl->fBindFramebuffer(fbTarget, srcFb);
1325 if (pboWas) {
1326 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
1329 // -
1331 const auto srcByteCount = CheckedInt<size_t>(stride) * size.height;
1332 if (!srcByteCount.isValid()) {
1333 gfxCriticalError() << "SnapshotInto: invalid srcByteCount, width:"
1334 << size.width << ", height:" << size.height;
1335 return {};
1337 const auto dstByteCount = dest.length();
1338 if (srcByteCount.value() > dstByteCount) {
1339 gfxCriticalError() << "SnapshotInto: srcByteCount:" << srcByteCount.value()
1340 << " > dstByteCount:" << dstByteCount;
1341 return {};
1343 uint8_t* dstPtr = dest.begin().get();
1344 gl->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA,
1345 LOCAL_GL_UNSIGNED_BYTE, dstPtr);
1347 if (!IsWebGL2() && stride > minStride.value() && size.height > 1) {
1348 // WebGL 1 doesn't support PACK_ROW_LENGTH. Instead, we read the data tight
1349 // into the front of the buffer, and use memmove (since the source and dest
1350 // may overlap) starting from the back to move it to the correct stride
1351 // offsets. We don't move the first row as it is already in the right place.
1352 uint8_t* destRow = dstPtr + stride * (size.height - 1);
1353 const uint8_t* srcRow = dstPtr + minStride.value() * (size.height - 1);
1354 while (destRow > dstPtr) {
1355 memmove(destRow, srcRow, minStride.value());
1356 destRow -= stride;
1357 srcRow -= minStride.value();
1361 return Some(*uvec2::FromSize(size));
1364 void WebGLContext::ClearVRSwapChain() { mWebVRSwapChain.ClearPool(); }
1366 // ------------------------
1368 RefPtr<gfx::DataSourceSurface> GetTempSurface(const gfx::IntSize& aSize,
1369 gfx::SurfaceFormat& aFormat) {
1370 uint32_t stride =
1371 gfx::GetAlignedStride<8>(aSize.width, BytesPerPixel(aFormat));
1372 return gfx::Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat,
1373 stride);
1376 void WebGLContext::DummyReadFramebufferOperation() {
1377 if (!mBoundReadFramebuffer) return; // Infallible.
1379 const auto status = mBoundReadFramebuffer->CheckFramebufferStatus();
1380 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
1381 ErrorInvalidFramebufferOperation("Framebuffer must be complete.");
1385 bool WebGLContext::Has64BitTimestamps() const {
1386 // 'sync' provides glGetInteger64v either by supporting ARB_sync, GL3+, or
1387 // GLES3+.
1388 return gl->IsSupported(gl::GLFeature::sync);
1391 static bool CheckContextLost(gl::GLContext* gl, bool* const out_isGuilty) {
1392 MOZ_ASSERT(gl);
1394 const auto resetStatus = gl->fGetGraphicsResetStatus();
1395 if (resetStatus == LOCAL_GL_NO_ERROR) {
1396 *out_isGuilty = false;
1397 return false;
1400 // Assume guilty unless we find otherwise!
1401 bool isGuilty = true;
1402 switch (resetStatus) {
1403 case LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB:
1404 case LOCAL_GL_PURGED_CONTEXT_RESET_NV:
1405 // Either nothing wrong, or not our fault.
1406 isGuilty = false;
1407 break;
1408 case LOCAL_GL_GUILTY_CONTEXT_RESET_ARB:
1409 NS_WARNING(
1410 "WebGL content on the page definitely caused the graphics"
1411 " card to reset.");
1412 break;
1413 case LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB:
1414 NS_WARNING(
1415 "WebGL content on the page might have caused the graphics"
1416 " card to reset");
1417 // If we can't tell, assume not-guilty.
1418 // Todo: Implement max number of "unknown" resets per document or time.
1419 isGuilty = false;
1420 break;
1421 default:
1422 gfxCriticalError() << "Unexpected glGetGraphicsResetStatus: "
1423 << gfx::hexa(resetStatus);
1424 break;
1427 if (isGuilty) {
1428 NS_WARNING(
1429 "WebGL context on this page is considered guilty, and will"
1430 " not be restored.");
1433 *out_isGuilty = isGuilty;
1434 return true;
1437 void WebGLContext::RunContextLossTimer() { mContextLossHandler.RunTimer(); }
1439 // We use this timer for many things. Here are the things that it is activated
1440 // for:
1441 // 1) If a script is using the MOZ_WEBGL_lose_context extension.
1442 // 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the
1443 // CONTEXT_LOST_WEBGL error has been triggered.
1444 // 3) If we are using ANGLE, or anything that supports ARB_robustness, query the
1445 // GPU periodically to see if the reset status bit has been set.
1446 // In all of these situations, we use this timer to send the script context lost
1447 // and restored events asynchronously. For example, if it triggers a context
1448 // loss, the webglcontextlost event will be sent to it the next time the
1449 // robustness timer fires.
1450 // Note that this timer mechanism is not used unless one of these 3 criteria are
1451 // met.
1452 // At a bare minimum, from context lost to context restores, it would take 3
1453 // full timer iterations: detection, webglcontextlost, webglcontextrestored.
1454 void WebGLContext::CheckForContextLoss() {
1455 bool isGuilty = true;
1456 const auto isContextLost = CheckContextLost(gl, &isGuilty);
1457 if (!isContextLost) return;
1459 mWebGLError = LOCAL_GL_CONTEXT_LOST;
1461 auto reason = webgl::ContextLossReason::None;
1462 if (isGuilty) {
1463 reason = webgl::ContextLossReason::Guilty;
1465 LoseContext(reason);
1468 void WebGLContext::HandlePendingContextLoss() {
1469 mIsContextLost = true;
1470 mHost->OnContextLoss(mPendingContextLossReason);
1473 void WebGLContext::LoseContextLruLocked(const webgl::ContextLossReason reason) {
1474 printf_stderr("WebGL(%p)::LoseContext(%u)\n", this,
1475 static_cast<uint32_t>(reason));
1476 mLruPosition.ResetLocked();
1477 mPendingContextLossReason = reason;
1478 mPendingContextLoss = true;
1481 void WebGLContext::LoseContext(const webgl::ContextLossReason reason) {
1482 StaticMutexAutoLock lock(sLruMutex);
1483 LoseContextLruLocked(reason);
1484 HandlePendingContextLoss();
1485 if (mRemoteTextureOwner) {
1486 mRemoteTextureOwner->NotifyContextLost();
1490 void WebGLContext::DidRefresh() {
1491 if (gl) {
1492 gl->FlushIfHeavyGLCallsSinceLastFlush();
1496 ////////////////////////////////////////////////////////////////////////////////
1498 uvec2 WebGLContext::DrawingBufferSize() {
1499 const FuncScope funcScope(*this, "width/height");
1500 if (IsContextLost()) return {};
1502 if (!EnsureDefaultFB()) return {};
1504 return *uvec2::FromSize(mDefaultFB->mSize);
1507 bool WebGLContext::ValidateAndInitFB(const WebGLFramebuffer* const fb,
1508 const GLenum incompleteFbError) {
1509 if (fb) return fb->ValidateAndInitAttachments(incompleteFbError);
1511 if (!EnsureDefaultFB()) return false;
1513 if (mDefaultFB_IsInvalid) {
1514 // Clear it!
1515 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
1516 const webgl::ScopedPrepForResourceClear scopedPrep(*this);
1517 if (!mOptions.alpha) {
1518 gl->fClearColor(0, 0, 0, 1);
1520 const GLbitfield bits = LOCAL_GL_COLOR_BUFFER_BIT |
1521 LOCAL_GL_DEPTH_BUFFER_BIT |
1522 LOCAL_GL_STENCIL_BUFFER_BIT;
1523 gl->fClear(bits);
1525 mDefaultFB_IsInvalid = false;
1527 return true;
1530 void WebGLContext::DoBindFB(const WebGLFramebuffer* const fb,
1531 const GLenum target) const {
1532 const GLenum driverFB = fb ? fb->mGLName : mDefaultFB->mFB;
1533 gl->fBindFramebuffer(target, driverFB);
1536 bool WebGLContext::BindCurFBForDraw() {
1537 const auto& fb = mBoundDrawFramebuffer;
1538 if (!ValidateAndInitFB(fb)) return false;
1540 DoBindFB(fb);
1541 return true;
1544 bool WebGLContext::BindCurFBForColorRead(
1545 const webgl::FormatUsageInfo** const out_format, uint32_t* const out_width,
1546 uint32_t* const out_height, const GLenum incompleteFbError) {
1547 const auto& fb = mBoundReadFramebuffer;
1549 if (fb) {
1550 if (!ValidateAndInitFB(fb, incompleteFbError)) return false;
1551 if (!fb->ValidateForColorRead(out_format, out_width, out_height))
1552 return false;
1554 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb->mGLName);
1555 return true;
1558 if (!BindDefaultFBForRead()) return false;
1560 if (mDefaultFB_ReadBuffer == LOCAL_GL_NONE) {
1561 ErrorInvalidOperation(
1562 "Can't read from backbuffer when readBuffer mode is NONE.");
1563 return false;
1566 auto effFormat = mOptions.alpha ? webgl::EffectiveFormat::RGBA8
1567 : webgl::EffectiveFormat::RGB8;
1569 *out_format = mFormatUsage->GetUsage(effFormat);
1570 MOZ_ASSERT(*out_format);
1572 *out_width = mDefaultFB->mSize.width;
1573 *out_height = mDefaultFB->mSize.height;
1574 return true;
1577 bool WebGLContext::BindDefaultFBForRead() {
1578 if (!ValidateAndInitFB(nullptr)) return false;
1580 if (!mDefaultFB->mSamples) {
1581 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
1582 return true;
1585 if (!mResolvedDefaultFB) {
1586 mResolvedDefaultFB =
1587 gl::MozFramebuffer::Create(gl, mDefaultFB->mSize, 0, false);
1588 if (!mResolvedDefaultFB) {
1589 gfxCriticalNote << FuncName() << ": Failed to create mResolvedDefaultFB.";
1590 return false;
1594 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
1595 BlitBackbufferToCurDriverFB();
1597 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
1598 return true;
1601 void WebGLContext::DoColorMask(Maybe<GLuint> i, const uint8_t bitmask) const {
1602 if (!IsExtensionEnabled(WebGLExtensionID::OES_draw_buffers_indexed)) {
1603 i = Nothing();
1605 const auto bs = std::bitset<4>(bitmask);
1606 if (i) {
1607 gl->fColorMaski(*i, bs[0], bs[1], bs[2], bs[3]);
1608 } else {
1609 gl->fColorMask(bs[0], bs[1], bs[2], bs[3]);
1613 ////////////////////////////////////////////////////////////////////////////////
1615 ScopedDrawCallWrapper::ScopedDrawCallWrapper(WebGLContext& webgl)
1616 : mWebGL(webgl) {
1617 uint8_t driverColorMask0 = mWebGL.mColorWriteMask0;
1618 bool driverDepthTest = mWebGL.mDepthTestEnabled;
1619 bool driverStencilTest = mWebGL.mStencilTestEnabled;
1620 const auto& fb = mWebGL.mBoundDrawFramebuffer;
1621 if (!fb) {
1622 if (mWebGL.mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE) {
1623 driverColorMask0 = 0; // Is this well-optimized enough for depth-first
1624 // rendering?
1625 } else {
1626 driverColorMask0 &= ~(uint8_t(mWebGL.mNeedsFakeNoAlpha) << 3);
1628 driverDepthTest &= !mWebGL.mNeedsFakeNoDepth;
1629 driverStencilTest &= !mWebGL.mNeedsFakeNoStencil;
1632 const auto& gl = mWebGL.gl;
1633 mWebGL.DoColorMask(Some(0), driverColorMask0);
1634 if (mWebGL.mDriverDepthTest != driverDepthTest) {
1635 // "When disabled, the depth comparison and subsequent possible updates to
1636 // the
1637 // depth buffer value are bypassed and the fragment is passed to the next
1638 // operation." [GLES 3.0.5, p177]
1639 mWebGL.mDriverDepthTest = driverDepthTest;
1640 gl->SetEnabled(LOCAL_GL_DEPTH_TEST, mWebGL.mDriverDepthTest);
1642 if (mWebGL.mDriverStencilTest != driverStencilTest) {
1643 // "When disabled, the stencil test and associated modifications are not
1644 // made, and
1645 // the fragment is always passed." [GLES 3.0.5, p175]
1646 mWebGL.mDriverStencilTest = driverStencilTest;
1647 gl->SetEnabled(LOCAL_GL_STENCIL_TEST, mWebGL.mDriverStencilTest);
1651 ScopedDrawCallWrapper::~ScopedDrawCallWrapper() {
1652 if (mWebGL.mBoundDrawFramebuffer) return;
1654 mWebGL.mResolvedDefaultFB = nullptr;
1655 mWebGL.mShouldPresent = true;
1658 // -
1660 void WebGLContext::ScissorRect::Apply(gl::GLContext& gl) const {
1661 gl.fScissor(x, y, w, h);
1664 ////////////////////////////////////////
1666 IndexedBufferBinding::IndexedBufferBinding() = default;
1667 IndexedBufferBinding::~IndexedBufferBinding() = default;
1669 uint64_t IndexedBufferBinding::ByteCount() const {
1670 if (!mBufferBinding) return 0;
1672 uint64_t bufferSize = mBufferBinding->ByteLength();
1673 if (!mRangeSize) // BindBufferBase
1674 return bufferSize;
1676 if (mRangeStart >= bufferSize) return 0;
1677 bufferSize -= mRangeStart;
1679 return std::min(bufferSize, mRangeSize);
1682 ////////////////////////////////////////
1684 ScopedFBRebinder::~ScopedFBRebinder() {
1685 const auto fnName = [&](WebGLFramebuffer* fb) {
1686 return fb ? fb->mGLName : 0;
1689 const auto& gl = mWebGL->gl;
1690 if (mWebGL->IsWebGL2()) {
1691 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
1692 fnName(mWebGL->mBoundDrawFramebuffer));
1693 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
1694 fnName(mWebGL->mBoundReadFramebuffer));
1695 } else {
1696 MOZ_ASSERT(mWebGL->mBoundDrawFramebuffer == mWebGL->mBoundReadFramebuffer);
1697 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER,
1698 fnName(mWebGL->mBoundDrawFramebuffer));
1702 ////////////////////
1704 void DoBindBuffer(gl::GLContext& gl, const GLenum target,
1705 const WebGLBuffer* const buffer) {
1706 gl.fBindBuffer(target, buffer ? buffer->mGLName : 0);
1709 ////////////////////////////////////////
1711 bool Intersect(const int32_t srcSize, const int32_t read0,
1712 const int32_t readSize, int32_t* const out_intRead0,
1713 int32_t* const out_intWrite0, int32_t* const out_intSize) {
1714 MOZ_ASSERT(srcSize >= 0);
1715 MOZ_ASSERT(readSize >= 0);
1716 const auto read1 = int64_t(read0) + readSize;
1718 int32_t intRead0 = read0; // Clearly doesn't need validation.
1719 int64_t intWrite0 = 0;
1720 int64_t intSize = readSize;
1722 if (read1 <= 0 || read0 >= srcSize) {
1723 // Disjoint ranges.
1724 intSize = 0;
1725 } else {
1726 if (read0 < 0) {
1727 const auto diff = int64_t(0) - read0;
1728 MOZ_ASSERT(diff >= 0);
1729 intRead0 = 0;
1730 intWrite0 = diff;
1731 intSize -= diff;
1733 if (read1 > srcSize) {
1734 const auto diff = int64_t(read1) - srcSize;
1735 MOZ_ASSERT(diff >= 0);
1736 intSize -= diff;
1739 if (!CheckedInt<int32_t>(intWrite0).isValid() ||
1740 !CheckedInt<int32_t>(intSize).isValid()) {
1741 return false;
1745 *out_intRead0 = intRead0;
1746 *out_intWrite0 = intWrite0;
1747 *out_intSize = intSize;
1748 return true;
1751 // --
1753 uint64_t AvailGroups(const uint64_t totalAvailItems,
1754 const uint64_t firstItemOffset, const uint32_t groupSize,
1755 const uint32_t groupStride) {
1756 MOZ_ASSERT(groupSize && groupStride);
1757 MOZ_ASSERT(groupSize <= groupStride);
1759 if (totalAvailItems <= firstItemOffset) return 0;
1760 const size_t availItems = totalAvailItems - firstItemOffset;
1762 size_t availGroups = availItems / groupStride;
1763 const size_t tailItems = availItems % groupStride;
1764 if (tailItems >= groupSize) {
1765 availGroups += 1;
1767 return availGroups;
1770 ////////////////////////////////////////////////////////////////////////////////
1772 const char* WebGLContext::FuncName() const {
1773 const char* ret;
1774 if (MOZ_LIKELY(mFuncScope)) {
1775 ret = mFuncScope->mFuncName;
1776 } else {
1777 NS_WARNING("FuncScope not on stack!");
1778 ret = "<unknown function>";
1780 return ret;
1783 // -
1785 WebGLContext::FuncScope::FuncScope(const WebGLContext& webgl,
1786 const char* const funcName)
1787 : mWebGL(webgl), mFuncName(bool(mWebGL.mFuncScope) ? nullptr : funcName) {
1788 if (!mFuncName) return;
1789 mWebGL.mFuncScope = this;
1792 WebGLContext::FuncScope::~FuncScope() {
1793 if (mBindFailureGuard) {
1794 gfxCriticalError() << "mBindFailureGuard failure: Early exit from "
1795 << mWebGL.FuncName();
1798 if (!mFuncName) return;
1799 mWebGL.mFuncScope = nullptr;
1802 // --
1804 bool ClientWebGLContext::IsXRCompatible() const { return mXRCompatible; }
1806 already_AddRefed<dom::Promise> ClientWebGLContext::MakeXRCompatible(
1807 ErrorResult& aRv) {
1808 const FuncScope funcScope(*this, "MakeXRCompatible");
1809 nsCOMPtr<nsIGlobalObject> global = GetParentObject();
1810 if (!global) {
1811 aRv.ThrowInvalidAccessError(
1812 "Using a WebGL context that is not attached to either a canvas or an "
1813 "OffscreenCanvas");
1814 return nullptr;
1816 RefPtr<dom::Promise> promise = dom::Promise::Create(global, aRv);
1817 NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
1819 if (IsContextLost()) {
1820 promise->MaybeRejectWithInvalidStateError(
1821 "Can not make context XR compatible when context is already lost.");
1822 return promise.forget();
1825 // TODO: Bug 1580258 - WebGLContext.MakeXRCompatible needs to switch to
1826 // the device connected to the XR hardware
1827 // This should update `options` and lose+restore the context.
1828 mXRCompatible = true;
1829 promise->MaybeResolveWithUndefined();
1830 return promise.forget();
1833 // --
1835 webgl::AvailabilityRunnable& ClientWebGLContext::EnsureAvailabilityRunnable()
1836 const {
1837 if (!mAvailabilityRunnable) {
1838 mAvailabilityRunnable = new webgl::AvailabilityRunnable(this);
1839 auto forgettable = mAvailabilityRunnable;
1840 NS_DispatchToCurrentThread(forgettable.forget());
1842 return *mAvailabilityRunnable;
1845 webgl::AvailabilityRunnable::AvailabilityRunnable(
1846 const ClientWebGLContext* const webgl)
1847 : DiscardableRunnable("webgl::AvailabilityRunnable"), mWebGL(webgl) {}
1849 webgl::AvailabilityRunnable::~AvailabilityRunnable() {
1850 MOZ_ASSERT(mQueries.empty());
1851 MOZ_ASSERT(mSyncs.empty());
1854 nsresult webgl::AvailabilityRunnable::Run() {
1855 for (const auto& cur : mQueries) {
1856 if (!cur) continue;
1857 cur->mCanBeAvailable = true;
1859 mQueries.clear();
1861 for (const auto& cur : mSyncs) {
1862 if (!cur) continue;
1863 cur->mCanBeAvailable = true;
1865 mSyncs.clear();
1867 if (mWebGL) {
1868 mWebGL->mAvailabilityRunnable = nullptr;
1870 return NS_OK;
1873 // -
1875 void WebGLContext::GenerateErrorImpl(const GLenum errOrWarning,
1876 const std::string& text) const {
1877 auto err = errOrWarning;
1878 bool isPerfWarning = false;
1879 if (err == webgl::kErrorPerfWarning) {
1880 err = 0;
1881 isPerfWarning = true;
1884 if (err && mFuncScope && mFuncScope->mBindFailureGuard) {
1885 gfxCriticalError() << "mBindFailureGuard failure: Generating error "
1886 << EnumString(err) << ": " << text;
1889 /* ES2 section 2.5 "GL Errors" states that implementations can have
1890 * multiple 'flags', as errors might be caught in different parts of
1891 * a distributed implementation.
1892 * We're signing up as a distributed implementation here, with
1893 * separate flags for WebGL and the underlying GLContext.
1895 if (!mWebGLError) mWebGLError = err;
1897 if (!mHost) return; // Impossible?
1899 // -
1901 const auto ShouldWarn = [&]() {
1902 if (isPerfWarning) {
1903 return ShouldGeneratePerfWarnings();
1905 return ShouldGenerateWarnings();
1907 if (!ShouldWarn()) return;
1909 // -
1911 auto* pNumWarnings = &mWarningCount;
1912 const char* warningsType = "warnings";
1913 if (isPerfWarning) {
1914 pNumWarnings = &mNumPerfWarnings;
1915 warningsType = "perf warnings";
1918 if (isPerfWarning) {
1919 const auto perfText = std::string("WebGL perf warning: ") + text;
1920 mHost->JsWarning(perfText);
1921 } else {
1922 mHost->JsWarning(text);
1924 *pNumWarnings += 1;
1926 if (!ShouldWarn()) {
1927 const auto& msg = nsPrintfCString(
1928 "After reporting %i, no further %s will be reported for this WebGL "
1929 "context.",
1930 int(*pNumWarnings), warningsType);
1931 mHost->JsWarning(ToString(msg));
1935 // -
1937 Maybe<std::string> WebGLContext::GetString(const GLenum pname) const {
1938 const WebGLContext::FuncScope funcScope(*this, "getParameter");
1939 if (IsContextLost()) return {};
1941 const auto FromRaw = [](const char* const raw) -> Maybe<std::string> {
1942 if (!raw) return {};
1943 return Some(std::string(raw));
1946 switch (pname) {
1947 case LOCAL_GL_EXTENSIONS: {
1948 if (!gl->IsCoreProfile()) {
1949 const auto rawExt = (const char*)gl->fGetString(LOCAL_GL_EXTENSIONS);
1950 return FromRaw(rawExt);
1952 std::string ret;
1953 const auto& numExts = gl->GetIntAs<GLuint>(LOCAL_GL_NUM_EXTENSIONS);
1954 for (GLuint i = 0; i < numExts; i++) {
1955 const auto rawExt =
1956 (const char*)gl->fGetStringi(LOCAL_GL_EXTENSIONS, i);
1957 if (!rawExt) continue;
1959 if (i > 0) {
1960 ret += " ";
1962 ret += rawExt;
1964 return Some(std::move(ret));
1967 case LOCAL_GL_RENDERER:
1968 case LOCAL_GL_VENDOR:
1969 case LOCAL_GL_VERSION: {
1970 const auto raw = (const char*)gl->fGetString(pname);
1971 return FromRaw(raw);
1974 case dom::MOZ_debug_Binding::WSI_INFO: {
1975 nsCString info;
1976 gl->GetWSIInfo(&info);
1977 return Some(std::string(info.BeginReading()));
1980 default:
1981 ErrorInvalidEnumArg("pname", pname);
1982 return {};
1986 // ---------------------------------
1988 Maybe<webgl::IndexedName> webgl::ParseIndexed(const std::string& str) {
1989 static const std::regex kRegex("(.*)\\[([0-9]+)\\]");
1991 std::smatch match;
1992 if (!std::regex_match(str, match, kRegex)) return {};
1994 const auto index = std::stoull(match[2]);
1995 return Some(webgl::IndexedName{match[1], index});
1998 // ExplodeName("foo.bar[3].x") -> ["foo", ".", "bar", "[", "3", "]", ".", "x"]
1999 static std::vector<std::string> ExplodeName(const std::string& str) {
2000 std::vector<std::string> ret;
2002 static const std::regex kSep("[.[\\]]");
2004 auto itr = std::regex_token_iterator<decltype(str.begin())>(
2005 str.begin(), str.end(), kSep, {-1, 0});
2006 const auto end = decltype(itr)();
2008 for (; itr != end; ++itr) {
2009 const auto& part = itr->str();
2010 if (part.size()) {
2011 ret.push_back(part);
2014 return ret;
2019 // #define DUMP_MakeLinkResult
2021 webgl::LinkActiveInfo GetLinkActiveInfo(
2022 gl::GLContext& gl, const GLuint prog, const bool webgl2,
2023 const std::unordered_map<std::string, std::string>& nameUnmap) {
2024 webgl::LinkActiveInfo ret;
2025 [&]() {
2026 const auto fnGetProgramui = [&](const GLenum pname) {
2027 GLint ret = 0;
2028 gl.fGetProgramiv(prog, pname, &ret);
2029 return static_cast<uint32_t>(ret);
2032 std::vector<char> stringBuffer(1);
2033 const auto fnEnsureCapacity = [&](const GLenum pname) {
2034 const auto maxWithNull = fnGetProgramui(pname);
2035 if (maxWithNull > stringBuffer.size()) {
2036 stringBuffer.resize(maxWithNull);
2040 fnEnsureCapacity(LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH);
2041 fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH);
2042 if (webgl2) {
2043 fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH);
2044 fnEnsureCapacity(LOCAL_GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH);
2047 // -
2049 const auto fnUnmapName = [&](const std::string& mappedName) {
2050 const auto parts = ExplodeName(mappedName);
2052 std::ostringstream ret;
2053 for (const auto& part : parts) {
2054 const auto maybe = MaybeFind(nameUnmap, part);
2055 if (maybe) {
2056 ret << *maybe;
2057 } else {
2058 ret << part;
2061 return ret.str();
2064 // -
2067 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_ATTRIBUTES);
2068 ret.activeAttribs.reserve(count);
2069 for (const auto i : IntegerRange(count)) {
2070 GLsizei lengthWithoutNull = 0;
2071 GLint elemCount = 0; // `size`
2072 GLenum elemType = 0; // `type`
2073 gl.fGetActiveAttrib(prog, i, stringBuffer.size(), &lengthWithoutNull,
2074 &elemCount, &elemType, stringBuffer.data());
2075 if (!elemType) {
2076 const auto error = gl.fGetError();
2077 if (error != LOCAL_GL_CONTEXT_LOST) {
2078 gfxCriticalError() << "Failed to do glGetActiveAttrib: " << error;
2080 return;
2082 const auto mappedName =
2083 std::string(stringBuffer.data(), lengthWithoutNull);
2084 const auto userName = fnUnmapName(mappedName);
2086 auto loc = gl.fGetAttribLocation(prog, mappedName.c_str());
2087 if (mappedName.find("gl_") == 0) {
2088 // Bug 1328559: Appears problematic on ANGLE and OSX, but not Linux or
2089 // Win+GL.
2090 loc = -1;
2093 #ifdef DUMP_MakeLinkResult
2094 printf_stderr("[attrib %u/%u] @%i %s->%s\n", i, count, loc,
2095 userName.c_str(), mappedName.c_str());
2096 #endif
2097 webgl::ActiveAttribInfo info;
2098 info.elemType = elemType;
2099 info.elemCount = elemCount;
2100 info.name = userName;
2101 info.location = loc;
2102 info.baseType = webgl::ToAttribBaseType(info.elemType);
2103 ret.activeAttribs.push_back(std::move(info));
2107 // -
2110 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORMS);
2111 ret.activeUniforms.reserve(count);
2113 std::vector<GLint> blockIndexList(count, -1);
2114 std::vector<GLint> blockOffsetList(count, -1);
2115 std::vector<GLint> blockArrayStrideList(count, -1);
2116 std::vector<GLint> blockMatrixStrideList(count, -1);
2117 std::vector<GLint> blockIsRowMajorList(count, 0);
2119 if (webgl2 && count) {
2120 std::vector<GLuint> activeIndices;
2121 activeIndices.reserve(count);
2122 for (const auto i : IntegerRange(count)) {
2123 activeIndices.push_back(i);
2126 gl.fGetActiveUniformsiv(
2127 prog, activeIndices.size(), activeIndices.data(),
2128 LOCAL_GL_UNIFORM_BLOCK_INDEX, blockIndexList.data());
2130 gl.fGetActiveUniformsiv(prog, activeIndices.size(),
2131 activeIndices.data(), LOCAL_GL_UNIFORM_OFFSET,
2132 blockOffsetList.data());
2134 gl.fGetActiveUniformsiv(
2135 prog, activeIndices.size(), activeIndices.data(),
2136 LOCAL_GL_UNIFORM_ARRAY_STRIDE, blockArrayStrideList.data());
2138 gl.fGetActiveUniformsiv(
2139 prog, activeIndices.size(), activeIndices.data(),
2140 LOCAL_GL_UNIFORM_MATRIX_STRIDE, blockMatrixStrideList.data());
2142 gl.fGetActiveUniformsiv(
2143 prog, activeIndices.size(), activeIndices.data(),
2144 LOCAL_GL_UNIFORM_IS_ROW_MAJOR, blockIsRowMajorList.data());
2147 for (const auto i : IntegerRange(count)) {
2148 GLsizei lengthWithoutNull = 0;
2149 GLint elemCount = 0; // `size`
2150 GLenum elemType = 0; // `type`
2151 gl.fGetActiveUniform(prog, i, stringBuffer.size(), &lengthWithoutNull,
2152 &elemCount, &elemType, stringBuffer.data());
2153 if (!elemType) {
2154 const auto error = gl.fGetError();
2155 if (error != LOCAL_GL_CONTEXT_LOST) {
2156 gfxCriticalError() << "Failed to do glGetActiveUniform: " << error;
2158 return;
2160 auto mappedName = std::string(stringBuffer.data(), lengthWithoutNull);
2162 // Get true name
2164 auto baseMappedName = mappedName;
2166 const bool isArray = [&]() {
2167 const auto maybe = webgl::ParseIndexed(mappedName);
2168 if (maybe) {
2169 MOZ_ASSERT(maybe->index == 0);
2170 baseMappedName = std::move(maybe->name);
2171 return true;
2173 return false;
2174 }();
2176 const auto userName = fnUnmapName(mappedName);
2177 if (StartsWith(userName, "webgl_")) continue;
2179 // -
2181 webgl::ActiveUniformInfo info;
2182 info.elemType = elemType;
2183 info.elemCount = static_cast<uint32_t>(elemCount);
2184 info.name = userName;
2185 info.block_index = blockIndexList[i];
2186 info.block_offset = blockOffsetList[i];
2187 info.block_arrayStride = blockArrayStrideList[i];
2188 info.block_matrixStride = blockMatrixStrideList[i];
2189 info.block_isRowMajor = bool(blockIsRowMajorList[i]);
2191 #ifdef DUMP_MakeLinkResult
2192 printf_stderr("[uniform %u/%u] %s->%s\n", i + 1, count,
2193 userName.c_str(), mappedName.c_str());
2194 #endif
2196 // Get uniform locations
2198 auto locName = baseMappedName;
2199 const auto baseLength = locName.size();
2200 for (const auto i : IntegerRange(info.elemCount)) {
2201 if (isArray) {
2202 locName.erase(
2203 baseLength); // Erase previous [N], but retain capacity.
2204 locName += '[';
2205 locName += std::to_string(i);
2206 locName += ']';
2208 const auto loc = gl.fGetUniformLocation(prog, locName.c_str());
2209 if (loc != -1) {
2210 info.locByIndex[i] = static_cast<uint32_t>(loc);
2211 #ifdef DUMP_MakeLinkResult
2212 printf_stderr(" [%u] @%i\n", i, loc);
2213 #endif
2216 } // anon
2218 ret.activeUniforms.push_back(std::move(info));
2219 } // for i
2220 } // anon
2222 if (webgl2) {
2223 // -------------------------------------
2224 // active uniform blocks
2226 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORM_BLOCKS);
2227 ret.activeUniformBlocks.reserve(count);
2229 for (const auto i : IntegerRange(count)) {
2230 GLsizei lengthWithoutNull = 0;
2231 gl.fGetActiveUniformBlockName(prog, i, stringBuffer.size(),
2232 &lengthWithoutNull,
2233 stringBuffer.data());
2234 const auto mappedName =
2235 std::string(stringBuffer.data(), lengthWithoutNull);
2236 const auto userName = fnUnmapName(mappedName);
2238 // -
2240 auto info = webgl::ActiveUniformBlockInfo{userName};
2241 GLint val = 0;
2243 gl.fGetActiveUniformBlockiv(prog, i, LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE,
2244 &val);
2245 info.dataSize = static_cast<uint32_t>(val);
2247 gl.fGetActiveUniformBlockiv(
2248 prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &val);
2249 info.activeUniformIndices.resize(val);
2250 gl.fGetActiveUniformBlockiv(
2251 prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES,
2252 reinterpret_cast<GLint*>(info.activeUniformIndices.data()));
2254 gl.fGetActiveUniformBlockiv(
2255 prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER,
2256 &val);
2257 info.referencedByVertexShader = bool(val);
2259 gl.fGetActiveUniformBlockiv(
2260 prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER,
2261 &val);
2262 info.referencedByFragmentShader = bool(val);
2264 ret.activeUniformBlocks.push_back(std::move(info));
2265 } // for i
2266 } // anon
2268 // -------------------------------------
2269 // active tf varyings
2271 const auto count = fnGetProgramui(LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS);
2272 ret.activeTfVaryings.reserve(count);
2274 for (const auto i : IntegerRange(count)) {
2275 GLsizei lengthWithoutNull = 0;
2276 GLsizei elemCount = 0; // `size`
2277 GLenum elemType = 0; // `type`
2278 gl.fGetTransformFeedbackVarying(prog, i, stringBuffer.size(),
2279 &lengthWithoutNull, &elemCount,
2280 &elemType, stringBuffer.data());
2281 const auto mappedName =
2282 std::string(stringBuffer.data(), lengthWithoutNull);
2283 const auto userName = fnUnmapName(mappedName);
2285 ret.activeTfVaryings.push_back(
2286 {elemType, static_cast<uint32_t>(elemCount), userName});
2289 } // if webgl2
2290 }();
2291 return ret;
2294 nsCString ToCString(const std::string& s) {
2295 return nsCString(s.data(), s.size());
2298 webgl::CompileResult WebGLContext::GetCompileResult(
2299 const WebGLShader& shader) const {
2300 webgl::CompileResult ret;
2301 [&]() {
2302 ret.pending = false;
2303 const auto& info = shader.CompileResults();
2304 if (!info) return;
2305 if (!info->mValid) {
2306 ret.log = info->mInfoLog.c_str();
2307 return;
2309 // TODO: These could be large and should be made fallible.
2310 ret.translatedSource = ToCString(info->mObjectCode);
2311 ret.log = ToCString(shader.CompileLog());
2312 if (!shader.IsCompiled()) return;
2313 ret.success = true;
2314 }();
2315 return ret;
2318 webgl::LinkResult WebGLContext::GetLinkResult(const WebGLProgram& prog) const {
2319 webgl::LinkResult ret;
2320 [&]() {
2321 ret.pending = false; // Link status polling not yet implemented.
2322 ret.log = ToCString(prog.LinkLog());
2323 const auto& info = prog.LinkInfo();
2324 if (!info) return;
2325 ret.success = true;
2326 ret.active = info->active;
2327 ret.tfBufferMode = info->transformFeedbackBufferMode;
2328 }();
2329 return ret;
2332 // -
2334 GLint WebGLContext::GetFragDataLocation(const WebGLProgram& prog,
2335 const std::string& userName) const {
2336 const auto err = CheckGLSLVariableName(IsWebGL2(), userName);
2337 if (err) {
2338 GenerateError(err->type, "%s", err->info.c_str());
2339 return -1;
2342 const auto& info = prog.LinkInfo();
2343 if (!info) return -1;
2344 const auto& nameMap = info->nameMap;
2346 const auto parts = ExplodeName(userName);
2348 std::ostringstream ret;
2349 for (const auto& part : parts) {
2350 const auto maybe = MaybeFind(nameMap, part);
2351 if (maybe) {
2352 ret << *maybe;
2353 } else {
2354 ret << part;
2357 const auto mappedName = ret.str();
2359 if (gl->WorkAroundDriverBugs() && gl->IsMesa()) {
2360 // Mesa incorrectly generates INVALID_OPERATION for gl_ prefixes here.
2361 if (mappedName.find("gl_") == 0) {
2362 return -1;
2366 return gl->fGetFragDataLocation(prog.mGLName, mappedName.c_str());
2369 // -
2371 WebGLContextBoundObject::WebGLContextBoundObject(WebGLContext* webgl)
2372 : mContext(webgl) {}
2374 // -
2376 Result<webgl::ExplicitPixelPackingState, std::string>
2377 webgl::ExplicitPixelPackingState::ForUseWith(
2378 const webgl::PixelPackingState& stateOrZero, const GLenum target,
2379 const uvec3& subrectSize, const webgl::PackingInfo& pi,
2380 const Maybe<size_t> bytesPerRowStrideOverride) {
2381 auto state = stateOrZero;
2383 if (!IsTexTarget3D(target)) {
2384 state.skipImages = 0;
2385 state.imageHeight = 0;
2387 if (!state.rowLength) {
2388 state.rowLength = subrectSize.x;
2390 if (!state.imageHeight) {
2391 state.imageHeight = subrectSize.y;
2394 // -
2396 const auto mpii = PackingInfoInfo::For(pi);
2397 if (!mpii) {
2398 const auto text =
2399 nsPrintfCString("Invalid pi: { 0x%x, 0x%x}", pi.format, pi.type);
2400 return Err(mozilla::ToString(text));
2402 const auto pii = *mpii;
2403 const auto bytesPerPixel = pii.BytesPerPixel();
2405 const auto ElemsPerRowStride = [&]() {
2406 // GLES 3.0.6 p116:
2407 // p: `Elem*` pointer to the first element of the first row
2408 // N: row number, starting at 0
2409 // l: groups (pixels) per row
2410 // n: elements per group (pixel) in [1,2,3,4]
2411 // s: bytes per element in [1,2,4,8]
2412 // a: UNPACK_ALIGNMENT in [1,2,4,8]
2413 // Pointer to first element of Nth row: p + N*k
2414 // k(s>=a): n*l
2415 // k(s<a): a/s * ceil(s*n*l/a)
2416 const auto n__elemsPerPixel = pii.elementsPerPixel;
2417 const auto l__pixelsPerRow = state.rowLength;
2418 const auto a__alignment = state.alignmentInTypeElems;
2419 const auto s__bytesPerElem = pii.bytesPerElement;
2421 const auto nl = CheckedInt<size_t>(n__elemsPerPixel) * l__pixelsPerRow;
2422 auto k__elemsPerRowStride = nl;
2423 if (s__bytesPerElem < a__alignment) {
2424 // k = a/s * ceil(s*n*l/a)
2425 k__elemsPerRowStride =
2426 a__alignment / s__bytesPerElem *
2427 ((nl * s__bytesPerElem + a__alignment - 1) / a__alignment);
2429 return k__elemsPerRowStride;
2432 // -
2434 if (bytesPerRowStrideOverride) { // E.g. HTMLImageElement
2435 const size_t bytesPerRowStrideRequired = *bytesPerRowStrideOverride;
2436 // We have to reverse-engineer an ALIGNMENT and ROW_LENGTH for this.
2438 // GL does this in elems not bytes, so we should too.
2439 MOZ_RELEASE_ASSERT(bytesPerRowStrideRequired % pii.bytesPerElement == 0);
2440 const auto elemsPerRowStrideRequired =
2441 bytesPerRowStrideRequired / pii.bytesPerElement;
2443 state.rowLength = bytesPerRowStrideRequired / bytesPerPixel;
2444 state.alignmentInTypeElems = 8;
2445 while (true) {
2446 const auto elemPerRowStride = ElemsPerRowStride();
2447 if (elemPerRowStride.isValid() &&
2448 elemPerRowStride.value() == elemsPerRowStrideRequired) {
2449 break;
2451 state.alignmentInTypeElems /= 2;
2452 if (!state.alignmentInTypeElems) {
2453 const auto text = nsPrintfCString(
2454 "No valid alignment found: pi: { 0x%x, 0x%x},"
2455 " bytesPerRowStrideRequired: %zu",
2456 pi.format, pi.type, bytesPerRowStrideRequired);
2457 return Err(mozilla::ToString(text));
2462 // -
2464 const auto usedPixelsPerRow =
2465 CheckedInt<size_t>(state.skipPixels) + subrectSize.x;
2466 if (!usedPixelsPerRow.isValid() ||
2467 usedPixelsPerRow.value() > state.rowLength) {
2468 return Err("UNPACK_SKIP_PIXELS + width > UNPACK_ROW_LENGTH.");
2471 if (subrectSize.y > state.imageHeight) {
2472 return Err("height > UNPACK_IMAGE_HEIGHT.");
2474 // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
2476 // -
2478 auto metrics = Metrics{};
2480 metrics.usedSize = subrectSize;
2481 metrics.bytesPerPixel = BytesPerPixel(pi);
2483 // -
2485 const auto elemsPerRowStride = ElemsPerRowStride();
2486 const auto bytesPerRowStride = pii.bytesPerElement * elemsPerRowStride;
2487 if (!bytesPerRowStride.isValid()) {
2488 return Err("ROW_LENGTH or width too large for packing.");
2490 metrics.bytesPerRowStride = bytesPerRowStride.value();
2492 // -
2494 const auto firstImageTotalRows =
2495 CheckedInt<size_t>(state.skipRows) + metrics.usedSize.y;
2496 const auto totalImages =
2497 CheckedInt<size_t>(state.skipImages) + metrics.usedSize.z;
2498 auto totalRows = CheckedInt<size_t>(0);
2499 if (metrics.usedSize.y && metrics.usedSize.z) {
2500 totalRows = firstImageTotalRows + state.imageHeight * (totalImages - 1);
2502 if (!totalRows.isValid()) {
2503 return Err(
2504 "SKIP_ROWS, height, IMAGE_HEIGHT, SKIP_IMAGES, or depth too large for "
2505 "packing.");
2507 metrics.totalRows = totalRows.value();
2509 // -
2511 const auto totalBytesStrided = totalRows * metrics.bytesPerRowStride;
2512 if (!totalBytesStrided.isValid()) {
2513 return Err("Total byte count too large for packing.");
2515 metrics.totalBytesStrided = totalBytesStrided.value();
2517 metrics.totalBytesUsed = metrics.totalBytesStrided;
2518 if (metrics.usedSize.x && metrics.usedSize.y && metrics.usedSize.z) {
2519 const auto usedBytesPerRow =
2520 usedPixelsPerRow.value() * metrics.bytesPerPixel;
2521 metrics.totalBytesUsed -= metrics.bytesPerRowStride;
2522 metrics.totalBytesUsed += usedBytesPerRow;
2525 // -
2527 return {{state, metrics}};
2530 } // namespace mozilla