no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / canvas / WebGLContext.cpp
blobec19bfef3d22bfd6d9ee0da5d9277a50856801cd
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/ContentChild.h"
31 #include "mozilla/dom/Document.h"
32 #include "mozilla/dom/Event.h"
33 #include "mozilla/dom/HTMLVideoElement.h"
34 #include "mozilla/dom/ImageData.h"
35 #include "mozilla/dom/WebGLContextEvent.h"
36 #include "mozilla/EnumeratedArrayCycleCollection.h"
37 #include "mozilla/EnumeratedRange.h"
38 #include "mozilla/gfx/gfxVars.h"
39 #include "mozilla/Preferences.h"
40 #include "mozilla/ProcessPriorityManager.h"
41 #include "mozilla/ResultVariant.h"
42 #include "mozilla/ScopeExit.h"
43 #include "mozilla/Services.h"
44 #include "mozilla/StaticPrefs_webgl.h"
45 #include "mozilla/SVGObserverUtils.h"
46 #include "mozilla/Telemetry.h"
47 #include "nsContentUtils.h"
48 #include "nsDisplayList.h"
49 #include "nsError.h"
50 #include "nsIClassInfoImpl.h"
51 #include "nsIWidget.h"
52 #include "nsServiceManagerUtils.h"
53 #include "SharedSurfaceGL.h"
54 #include "prenv.h"
55 #include "ScopedGLHelpers.h"
56 #include "VRManagerChild.h"
57 #include "mozilla/gfx/Swizzle.h"
58 #include "mozilla/layers/BufferTexture.h"
59 #include "mozilla/layers/RemoteTextureMap.h"
60 #include "mozilla/layers/CompositorBridgeChild.h"
61 #include "mozilla/layers/ImageBridgeChild.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 if (host) {
149 host->mContext = this;
151 const FuncScope funcScope(*this, "<Create>");
152 WebGLMemoryTracker::EnsureRegistered();
155 WebGLContext::~WebGLContext() { DestroyResourcesAndContext(); }
157 void WebGLContext::DestroyResourcesAndContext() {
158 if (mRemoteTextureOwner) {
159 // Clean up any remote textures registered for framebuffer swap chains.
160 mRemoteTextureOwner->UnregisterAllTextureOwners();
161 mRemoteTextureOwner = nullptr;
164 if (!gl) return;
166 mDefaultFB = nullptr;
167 mResolvedDefaultFB = nullptr;
169 mBound2DTextures.Clear();
170 mBoundCubeMapTextures.Clear();
171 mBound3DTextures.Clear();
172 mBound2DArrayTextures.Clear();
173 mBoundSamplers.Clear();
174 mBoundArrayBuffer = nullptr;
175 mBoundCopyReadBuffer = nullptr;
176 mBoundCopyWriteBuffer = nullptr;
177 mBoundPixelPackBuffer = nullptr;
178 mBoundPixelUnpackBuffer = nullptr;
179 mBoundTransformFeedbackBuffer = nullptr;
180 mBoundUniformBuffer = nullptr;
181 mCurrentProgram = nullptr;
182 mActiveProgramLinkInfo = nullptr;
183 mBoundDrawFramebuffer = nullptr;
184 mBoundReadFramebuffer = nullptr;
185 mBoundVertexArray = nullptr;
186 mDefaultVertexArray = nullptr;
187 mBoundTransformFeedback = nullptr;
188 mDefaultTransformFeedback = nullptr;
190 mQuerySlot_SamplesPassed = nullptr;
191 mQuerySlot_TFPrimsWritten = nullptr;
192 mQuerySlot_TimeElapsed = nullptr;
194 mIndexedUniformBufferBindings.clear();
196 //////
198 if (mEmptyTFO) {
199 gl->fDeleteTransformFeedbacks(1, &mEmptyTFO);
200 mEmptyTFO = 0;
203 //////
205 if (mFakeVertexAttrib0BufferObject) {
206 gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject);
207 mFakeVertexAttrib0BufferObject = 0;
210 // disable all extensions except "WEBGL_lose_context". see bug #927969
211 // spec: http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
212 for (size_t i = 0; i < size_t(WebGLExtensionID::Max); ++i) {
213 WebGLExtensionID extension = WebGLExtensionID(i);
214 if (extension == WebGLExtensionID::WEBGL_lose_context) continue;
215 mExtensions[extension] = nullptr;
218 // We just got rid of everything, so the context had better
219 // have been going away.
220 if (gl::GLContext::ShouldSpew()) {
221 printf_stderr("--- WebGL context destroyed: %p\n", gl.get());
224 MOZ_ASSERT(gl);
225 gl->MarkDestroyed();
226 mGL_OnlyClearInDestroyResourcesAndContext = nullptr;
227 MOZ_ASSERT(!gl);
230 void ClientWebGLContext::MarkCanvasDirty() {
231 if (!mCanvasElement && !mOffscreenCanvas) return;
233 mFrameCaptureState = FrameCaptureState::DIRTY;
235 if (mIsCanvasDirty) return;
236 mIsCanvasDirty = true;
238 if (mCanvasElement) {
239 SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
240 mCanvasElement->InvalidateCanvasContent(nullptr);
241 } else if (mOffscreenCanvas) {
242 mOffscreenCanvas->QueueCommitToCompositor();
246 void WebGLContext::OnMemoryPressure() {
247 bool shouldLoseContext = mLoseContextOnMemoryPressure;
249 if (!mCanLoseContextInForeground &&
250 ProcessPriorityManager::CurrentProcessIsForeground()) {
251 shouldLoseContext = false;
254 if (shouldLoseContext) LoseContext();
257 // --
259 bool WebGLContext::CreateAndInitGL(
260 bool forceEnabled, std::vector<FailureReason>* const out_failReasons) {
261 const FuncScope funcScope(*this, "<Create>");
263 // WebGL2 is separately blocked:
264 if (IsWebGL2() && !forceEnabled) {
265 FailureReason reason;
266 if (!gfx::gfxVars::AllowWebgl2()) {
267 reason.info =
268 "AllowWebgl2:false restricts context creation on this system.";
269 out_failReasons->push_back(reason);
270 GenerateWarning("%s", reason.info.BeginReading());
271 return false;
275 auto flags = gl::CreateContextFlags::PREFER_ROBUSTNESS;
277 if (StaticPrefs::webgl_gl_khr_no_error()) {
278 flags |= gl::CreateContextFlags::NO_VALIDATION;
281 // -
283 if (StaticPrefs::webgl_forbid_hardware()) {
284 flags |= gl::CreateContextFlags::FORBID_HARDWARE;
286 if (StaticPrefs::webgl_forbid_software()) {
287 flags |= gl::CreateContextFlags::FORBID_SOFTWARE;
290 if (forceEnabled) {
291 flags &= ~gl::CreateContextFlags::FORBID_HARDWARE;
292 flags &= ~gl::CreateContextFlags::FORBID_SOFTWARE;
295 if ((flags & gl::CreateContextFlags::FORBID_HARDWARE) &&
296 (flags & gl::CreateContextFlags::FORBID_SOFTWARE)) {
297 FailureReason reason;
298 reason.info = "Both hardware and software were forbidden by config.";
299 out_failReasons->push_back(reason);
300 GenerateWarning("%s", reason.info.BeginReading());
301 return false;
304 // -
306 if (StaticPrefs::webgl_cgl_multithreaded()) {
307 flags |= gl::CreateContextFlags::PREFER_MULTITHREADED;
310 if (IsWebGL2()) {
311 flags |= gl::CreateContextFlags::PREFER_ES3;
312 } else {
313 // Request and prefer ES2 context for WebGL1.
314 flags |= gl::CreateContextFlags::PREFER_EXACT_VERSION;
316 if (!StaticPrefs::webgl_1_allow_core_profiles()) {
317 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
322 auto powerPref = mOptions.powerPreference;
324 // If "Use hardware acceleration when available" option is disabled:
325 if (!gfx::gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING)) {
326 powerPref = dom::WebGLPowerPreference::Low_power;
329 const auto overrideVal = StaticPrefs::webgl_power_preference_override();
330 if (overrideVal > 0) {
331 powerPref = dom::WebGLPowerPreference::High_performance;
332 } else if (overrideVal < 0) {
333 powerPref = dom::WebGLPowerPreference::Low_power;
336 if (powerPref == dom::WebGLPowerPreference::High_performance) {
337 flags |= gl::CreateContextFlags::HIGH_POWER;
341 if (!gfx::gfxVars::WebglAllowCoreProfile()) {
342 flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
345 // --
347 const bool useEGL = PR_GetEnv("MOZ_WEBGL_FORCE_EGL");
349 bool tryNativeGL = true;
350 bool tryANGLE = false;
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 // This is conditional on not using the Compositor thread because we may
626 // need to synchronize with the RDD process over the PVideoBridge protocol
627 // to wait for the texture to be available in the compositor process. We
628 // cannot block on the Compositor thread, so in that configuration, we would
629 // prefer to do the readback from the RDD which is guaranteed to work, and
630 // only block the owning thread for WebGL.
631 types[layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo] =
632 gfx::gfxVars::UseCanvasRenderThread() ||
633 !gfx::gfxVars::SupportsThreadsafeGL();
634 if (webgl->gl->IsANGLE()) {
635 types[layers::SurfaceDescriptor::TSurfaceDescriptorD3D10] = true;
636 types[layers::SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr] = true;
638 if (kIsMacOS) {
639 types[layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface] = true;
641 if (kIsAndroid) {
642 types[layers::SurfaceDescriptor::TSurfaceTextureDescriptor] = true;
644 if (kIsLinux) {
645 types[layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf] = true;
647 return types;
650 // -
652 out->options = webgl->mOptions;
653 out->limits = *webgl->mLimits;
654 out->uploadableSdTypes = UploadableSdTypes();
655 out->vendor = webgl->gl->Vendor();
656 out->optionalRenderableFormatBits = webgl->mOptionalRenderableFormatBits;
658 return webgl;
661 void WebGLContext::FinishInit() {
662 mOptions.antialias &= bool(mDefaultFB->mSamples);
664 if (!mOptions.alpha) {
665 // We always have alpha.
666 mNeedsFakeNoAlpha = true;
669 if (mOptions.depth || mOptions.stencil) {
670 // We always have depth+stencil if we have either.
671 if (!mOptions.depth) {
672 mNeedsFakeNoDepth = true;
674 if (!mOptions.stencil) {
675 mNeedsFakeNoStencil = true;
679 mResetLayer = true;
680 mOptionsFrozen = true;
682 //////
683 // Initial setup.
685 gl->mImplicitMakeCurrent = true;
686 gl->mElideDuplicateBindFramebuffers = true;
688 const auto& size = mDefaultFB->mSize;
690 mViewportX = mViewportY = 0;
691 mViewportWidth = size.width;
692 mViewportHeight = size.height;
693 gl->fViewport(mViewportX, mViewportY, mViewportWidth, mViewportHeight);
695 mScissorRect = {0, 0, size.width, size.height};
696 mScissorRect.Apply(*gl);
698 //////
699 // Check everything
701 AssertCachedBindings();
702 AssertCachedGlobalState();
704 mShouldPresent = true;
706 //////
707 // mIsRgb8Renderable
710 const auto tex = gl::ScopedTexture(gl);
711 const auto fb = gl::ScopedFramebuffer(gl);
712 gl->fBindTexture(LOCAL_GL_TEXTURE_2D, tex);
713 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb);
714 gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
715 LOCAL_GL_TEXTURE_2D, tex, 0);
717 const auto IsRenderable = [&](const GLint internalFormat,
718 const GLenum unpackFormat) {
719 gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0,
720 unpackFormat, LOCAL_GL_UNSIGNED_BYTE, nullptr);
721 const auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
722 return (status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
725 if (IsRenderable(LOCAL_GL_RGB, LOCAL_GL_RGB)) {
726 mOptionalRenderableFormatBits |=
727 webgl::OptionalRenderableFormatBits::RGB8;
729 if (gl->IsSupported(gl::GLFeature::sRGB)) {
730 struct {
731 GLint internal;
732 GLenum unpack;
733 } formats = {LOCAL_GL_SRGB8, LOCAL_GL_RGB};
734 const bool isEs2 = (gl->IsGLES() && gl->Version() < 300);
735 if (isEs2) {
736 formats = {LOCAL_GL_SRGB, LOCAL_GL_SRGB};
738 if (IsRenderable(formats.internal, formats.unpack)) {
739 mOptionalRenderableFormatBits |=
740 webgl::OptionalRenderableFormatBits::SRGB8;
745 //////
747 gl->ResetSyncCallCount("WebGLContext Initialization");
748 LoseLruContextIfLimitExceeded();
751 void WebGLContext::SetCompositableHost(
752 RefPtr<layers::CompositableHost>& aCompositableHost) {
753 mCompositableHost = aCompositableHost;
756 void WebGLContext::BumpLruLocked() {
757 if (!mIsContextLost && !mPendingContextLoss) {
758 mLruPosition.AssignLocked(*this);
759 } else {
760 MOZ_ASSERT(!mLruPosition.IsInsertedLocked());
764 void WebGLContext::BumpLru() {
765 StaticMutexAutoLock lock(sLruMutex);
766 BumpLruLocked();
769 void WebGLContext::LoseLruContextIfLimitExceeded() {
770 StaticMutexAutoLock lock(sLruMutex);
772 const auto maxContexts = std::max(1u, StaticPrefs::webgl_max_contexts());
773 const auto maxContextsPerPrincipal =
774 std::max(1u, StaticPrefs::webgl_max_contexts_per_principal());
776 // it's important to update the index on a new context before losing old
777 // contexts, otherwise new unused contexts would all have index 0 and we
778 // couldn't distinguish older ones when choosing which one to lose first.
779 BumpLruLocked();
782 size_t forPrincipal = 0;
783 for (const auto& context : sLru) {
784 if (context->mPrincipalKey == mPrincipalKey) {
785 forPrincipal += 1;
789 while (forPrincipal > maxContextsPerPrincipal) {
790 const auto text = nsPrintfCString(
791 "Exceeded %u live WebGL contexts for this principal, losing the "
792 "least recently used one.",
793 maxContextsPerPrincipal);
794 JsWarning(ToString(text));
796 for (const auto& context : sLru) {
797 if (context->mPrincipalKey == mPrincipalKey) {
798 MOZ_ASSERT(context != this);
799 context->LoseContextLruLocked(webgl::ContextLossReason::None);
800 forPrincipal -= 1;
801 break;
807 auto total = sLru.size();
808 while (total > maxContexts) {
809 const auto text = nsPrintfCString(
810 "Exceeded %u live WebGL contexts, losing the least "
811 "recently used one.",
812 maxContexts);
813 JsWarning(ToString(text));
815 const auto& context = sLru.front();
816 MOZ_ASSERT(context != this);
817 context->LoseContextLruLocked(webgl::ContextLossReason::None);
818 total -= 1;
822 // -
824 namespace webgl {
826 ScopedPrepForResourceClear::ScopedPrepForResourceClear(
827 const WebGLContext& webgl_)
828 : webgl(webgl_) {
829 const auto& gl = webgl.gl;
831 if (webgl.mScissorTestEnabled) {
832 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
834 if (webgl.mRasterizerDiscardEnabled) {
835 gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD);
838 // "The clear operation always uses the front stencil write mask
839 // when clearing the stencil buffer."
840 webgl.DoColorMask(Some(0), 0b1111);
841 gl->fDepthMask(true);
842 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff);
844 gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
845 gl->fClearDepth(1.0f); // Depth formats are always cleared to 1.0f, not 0.0f.
846 gl->fClearStencil(0);
849 ScopedPrepForResourceClear::~ScopedPrepForResourceClear() {
850 const auto& gl = webgl.gl;
852 if (webgl.mScissorTestEnabled) {
853 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
855 if (webgl.mRasterizerDiscardEnabled) {
856 gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
859 webgl.DoColorMask(Some(0), webgl.mColorWriteMask0);
860 gl->fDepthMask(webgl.mDepthWriteMask);
861 gl->fStencilMaskSeparate(LOCAL_GL_FRONT, webgl.mStencilWriteMaskFront);
863 gl->fClearColor(webgl.mColorClearValue[0], webgl.mColorClearValue[1],
864 webgl.mColorClearValue[2], webgl.mColorClearValue[3]);
865 gl->fClearDepth(webgl.mDepthClearValue);
866 gl->fClearStencil(webgl.mStencilClearValue);
869 } // namespace webgl
871 // -
873 void WebGLContext::OnEndOfFrame() {
874 if (StaticPrefs::webgl_perf_spew_frame_allocs()) {
875 GeneratePerfWarning("[webgl.perf.spew-frame-allocs] %" PRIu64
876 " data allocations this frame.",
877 mDataAllocGLCallCount);
879 mDataAllocGLCallCount = 0;
880 gl->ResetSyncCallCount("WebGLContext PresentScreenBuffer");
882 mDrawCallsSinceLastFlush = 0;
884 BumpLru();
887 void WebGLContext::BlitBackbufferToCurDriverFB(
888 WebGLFramebuffer* const srcAsWebglFb,
889 const gl::MozFramebuffer* const srcAsMozFb, bool srcIsBGRA) const {
890 // BlitFramebuffer ignores ColorMask().
892 if (mScissorTestEnabled) {
893 gl->fDisable(LOCAL_GL_SCISSOR_TEST);
896 [&]() {
897 // If a MozFramebuffer is supplied, ensure that a WebGLFramebuffer is not
898 // used since it might not have completeness info, while the MozFramebuffer
899 // can still supply the needed information.
900 MOZ_ASSERT(!(srcAsMozFb && srcAsWebglFb));
901 const auto* mozFb = srcAsMozFb ? srcAsMozFb : mDefaultFB.get();
902 GLuint fbo = 0;
903 gfx::IntSize size;
904 if (srcAsWebglFb) {
905 fbo = srcAsWebglFb->mGLName;
906 const auto* info = srcAsWebglFb->GetCompletenessInfo();
907 MOZ_ASSERT(info);
908 size = gfx::IntSize(info->width, info->height);
909 } else {
910 fbo = mozFb->mFB;
911 size = mozFb->mSize;
914 // If no format conversion is necessary, then attempt to directly blit
915 // between framebuffers. Otherwise, if we need to convert to RGBA from
916 // the source format, then we will need to use the texture blit path
917 // below.
918 if (!srcIsBGRA) {
919 if (gl->IsSupported(gl::GLFeature::framebuffer_blit)) {
920 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
921 gl->fBlitFramebuffer(0, 0, size.width, size.height, 0, 0, size.width,
922 size.height, LOCAL_GL_COLOR_BUFFER_BIT,
923 LOCAL_GL_NEAREST);
924 return;
926 if (mDefaultFB->mSamples &&
927 gl->IsExtensionSupported(
928 gl::GLContext::APPLE_framebuffer_multisample)) {
929 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
930 gl->fResolveMultisampleFramebufferAPPLE();
931 return;
935 GLuint colorTex = 0;
936 if (srcAsWebglFb) {
937 const auto& attach = srcAsWebglFb->ColorAttachment0();
938 MOZ_ASSERT(attach.Texture());
939 colorTex = attach.Texture()->mGLName;
940 } else {
941 colorTex = mozFb->ColorTex();
944 // DrawBlit handles ColorMask itself.
945 gl->BlitHelper()->DrawBlitTextureToFramebuffer(
946 colorTex, size, size, LOCAL_GL_TEXTURE_2D, srcIsBGRA);
947 }();
949 if (mScissorTestEnabled) {
950 gl->fEnable(LOCAL_GL_SCISSOR_TEST);
954 // -
956 template <typename T, typename... Args>
957 constexpr auto MakeArray(Args... args) -> std::array<T, sizeof...(Args)> {
958 return {{static_cast<T>(args)...}};
961 inline gfx::ColorSpace2 ToColorSpace2(const WebGLContextOptions& options) {
962 auto ret = gfx::ColorSpace2::UNKNOWN;
963 if (true) {
964 ret = gfx::ColorSpace2::SRGB;
966 if (!options.ignoreColorSpace) {
967 ret = gfx::ToColorSpace2(options.colorSpace);
969 return ret;
972 // -
974 // For an overview of how WebGL compositing works, see:
975 // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
976 bool WebGLContext::PresentInto(gl::SwapChain& swapChain) {
977 OnEndOfFrame();
979 if (!ValidateAndInitFB(nullptr)) return false;
982 const auto colorSpace = ToColorSpace2(mOptions);
983 auto presenter = swapChain.Acquire(mDefaultFB->mSize, colorSpace);
984 if (!presenter) {
985 GenerateWarning("Swap chain surface creation failed.");
986 LoseContext();
987 return false;
990 const auto destFb = presenter->Fb();
991 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
993 BlitBackbufferToCurDriverFB();
995 if (!mOptions.preserveDrawingBuffer) {
996 if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
997 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB);
998 constexpr auto attachments = MakeArray<GLenum>(
999 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
1000 gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
1001 attachments.size(), attachments.data());
1003 mDefaultFB_IsInvalid = true;
1006 #ifdef DEBUG
1007 if (!mOptions.alpha) {
1008 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
1009 gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
1010 if (IsWebGL2()) {
1011 gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, 0);
1012 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
1013 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
1015 uint32_t pixel = 0xffbadbad;
1016 gl->fReadPixels(0, 0, 1, 1, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
1017 &pixel);
1018 MOZ_ASSERT((pixel & 0xff000000) == 0xff000000);
1020 #endif
1023 return true;
1026 bool WebGLContext::PresentIntoXR(gl::SwapChain& swapChain,
1027 const gl::MozFramebuffer& fb) {
1028 OnEndOfFrame();
1030 const auto colorSpace = ToColorSpace2(mOptions);
1031 auto presenter = swapChain.Acquire(fb.mSize, colorSpace);
1032 if (!presenter) {
1033 GenerateWarning("Swap chain surface creation failed.");
1034 LoseContext();
1035 return false;
1038 const auto destFb = presenter->Fb();
1039 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
1041 BlitBackbufferToCurDriverFB(nullptr, &fb);
1043 // https://immersive-web.github.io/webxr/#opaque-framebuffer
1044 // Opaque framebuffers will always be cleared regardless of the
1045 // associated WebGL context’s preserveDrawingBuffer value.
1046 if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
1047 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb.mFB);
1048 constexpr auto attachments = MakeArray<GLenum>(
1049 LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
1050 gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, attachments.size(),
1051 attachments.data());
1054 return true;
1057 // Initialize a swap chain's surface factory given the desired surface type.
1058 void InitSwapChain(gl::GLContext& gl, gl::SwapChain& swapChain,
1059 const layers::TextureType consumerType) {
1060 if (!swapChain.mFactory) {
1061 auto typedFactory = gl::SurfaceFactory::Create(&gl, consumerType);
1062 if (typedFactory) {
1063 swapChain.mFactory = std::move(typedFactory);
1066 if (!swapChain.mFactory) {
1067 NS_WARNING("Failed to make an ideal SurfaceFactory.");
1068 swapChain.mFactory = MakeUnique<gl::SurfaceFactory_Basic>(gl);
1070 MOZ_ASSERT(swapChain.mFactory);
1073 void WebGLContext::Present(WebGLFramebuffer* const xrFb,
1074 const layers::TextureType consumerType,
1075 const bool webvr,
1076 const webgl::SwapChainOptions& options) {
1077 const FuncScope funcScope(*this, "<Present>");
1078 if (IsContextLost()) {
1079 EnsureContextLostRemoteTextureOwner(options);
1080 return;
1083 auto swapChain = GetSwapChain(xrFb, webvr);
1084 const gl::MozFramebuffer* maybeFB = nullptr;
1085 if (xrFb) {
1086 maybeFB = xrFb->mOpaque.get();
1087 } else {
1088 mResolvedDefaultFB = nullptr;
1091 InitSwapChain(*gl, *swapChain, consumerType);
1093 bool valid =
1094 maybeFB ? PresentIntoXR(*swapChain, *maybeFB) : PresentInto(*swapChain);
1095 if (!valid) {
1096 EnsureContextLostRemoteTextureOwner(options);
1097 return;
1100 bool useAsync = options.remoteTextureOwnerId.IsValid() &&
1101 options.remoteTextureId.IsValid();
1102 if (useAsync) {
1103 PushRemoteTexture(nullptr, *swapChain, swapChain->FrontBuffer(), options);
1107 void WebGLContext::WaitForTxn(layers::RemoteTextureOwnerId ownerId,
1108 layers::RemoteTextureTxnType txnType,
1109 layers::RemoteTextureTxnId txnId) {
1110 if (!ownerId.IsValid() || !txnType || !txnId) {
1111 return;
1113 if (mRemoteTextureOwner && mRemoteTextureOwner->IsRegistered(ownerId)) {
1114 mRemoteTextureOwner->WaitForTxn(ownerId, txnType, txnId);
1118 bool WebGLContext::CopyToSwapChain(
1119 WebGLFramebuffer* const srcFb, const layers::TextureType consumerType,
1120 const webgl::SwapChainOptions& options,
1121 layers::RemoteTextureOwnerClient* ownerClient) {
1122 const FuncScope funcScope(*this, "<CopyToSwapChain>");
1123 if (IsContextLost()) {
1124 return false;
1127 OnEndOfFrame();
1129 if (!srcFb) {
1130 return false;
1132 const auto* info = srcFb->GetCompletenessInfo();
1133 if (!info) {
1134 return false;
1136 gfx::IntSize size(info->width, info->height);
1138 InitSwapChain(*gl, srcFb->mSwapChain, consumerType);
1140 bool useAsync = options.remoteTextureOwnerId.IsValid() &&
1141 options.remoteTextureId.IsValid();
1142 // If we're using async present and if there is no way to serialize surfaces,
1143 // then a readback is required to do the copy. In this case, there's no reason
1144 // to copy into a separate shared surface for the front buffer. Just directly
1145 // read back the WebGL framebuffer into and push it as a remote texture.
1146 if (useAsync && srcFb->mSwapChain.mFactory->GetConsumerType() ==
1147 layers::TextureType::Unknown) {
1148 return PushRemoteTexture(srcFb, srcFb->mSwapChain, nullptr, options,
1149 ownerClient);
1153 // ColorSpace will need to be part of SwapChainOptions for DTWebgl.
1154 const auto colorSpace = ToColorSpace2(mOptions);
1155 auto presenter = srcFb->mSwapChain.Acquire(size, colorSpace);
1156 if (!presenter) {
1157 GenerateWarning("Swap chain surface creation failed.");
1158 LoseContext();
1159 return false;
1162 const ScopedFBRebinder saveFB(this);
1164 const auto destFb = presenter->Fb();
1165 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
1167 BlitBackbufferToCurDriverFB(srcFb, nullptr, options.bgra);
1170 if (useAsync) {
1171 return PushRemoteTexture(srcFb, srcFb->mSwapChain,
1172 srcFb->mSwapChain.FrontBuffer(), options,
1173 ownerClient);
1175 return true;
1178 bool WebGLContext::PushRemoteTexture(
1179 WebGLFramebuffer* fb, gl::SwapChain& swapChain,
1180 std::shared_ptr<gl::SharedSurface> surf,
1181 const webgl::SwapChainOptions& options,
1182 layers::RemoteTextureOwnerClient* ownerClient) {
1183 const auto onFailure = [&]() -> bool {
1184 GenerateWarning("Remote texture creation failed.");
1185 LoseContext();
1186 if (ownerClient && ownerClient == mRemoteTextureOwner) {
1187 ownerClient->PushDummyTexture(options.remoteTextureId,
1188 options.remoteTextureOwnerId);
1190 return false;
1193 if (!ownerClient) {
1194 if (!mRemoteTextureOwner) {
1195 // Ensure we have a remote texture owner client for WebGLParent.
1196 const auto* outOfProcess =
1197 mHost ? mHost->mOwnerData.outOfProcess : nullptr;
1198 if (!outOfProcess) {
1199 return onFailure();
1201 auto pid = outOfProcess->OtherPid();
1202 mRemoteTextureOwner = MakeRefPtr<layers::RemoteTextureOwnerClient>(pid);
1204 ownerClient = mRemoteTextureOwner;
1207 layers::RemoteTextureOwnerId ownerId = options.remoteTextureOwnerId;
1208 layers::RemoteTextureId textureId = options.remoteTextureId;
1210 if (!ownerClient->IsRegistered(ownerId)) {
1211 // Register a texture owner to represent the swap chain.
1212 RefPtr<layers::RemoteTextureOwnerClient> textureOwner = ownerClient;
1213 auto destroyedCallback = [textureOwner, ownerId]() {
1214 textureOwner->UnregisterTextureOwner(ownerId);
1217 swapChain.SetDestroyedCallback(destroyedCallback);
1218 ownerClient->RegisterTextureOwner(ownerId,
1219 /* aSharedRecycling */ !!fb);
1222 MOZ_ASSERT(fb || surf);
1223 gfx::IntSize size;
1224 if (surf) {
1225 size = surf->mDesc.size;
1226 } else {
1227 const auto* info = fb->GetCompletenessInfo();
1228 MOZ_ASSERT(info);
1229 size = gfx::IntSize(info->width, info->height);
1232 const auto surfaceFormat = mOptions.alpha ? gfx::SurfaceFormat::B8G8R8A8
1233 : gfx::SurfaceFormat::B8G8R8X8;
1234 Maybe<layers::SurfaceDescriptor> desc;
1235 if (surf) {
1236 desc = surf->ToSurfaceDescriptor();
1238 if (!desc) {
1239 if (surf && surf->mDesc.type != gl::SharedSurfaceType::Basic) {
1240 return onFailure();
1242 // If we can't serialize to a surface descriptor, then we need to create
1243 // a buffer to read back into that will become the remote texture.
1244 auto data = ownerClient->CreateOrRecycleBufferTextureData(
1245 size, surfaceFormat, ownerId);
1246 if (!data) {
1247 gfxCriticalNoteOnce << "Failed to allocate BufferTextureData";
1248 return onFailure();
1251 layers::MappedTextureData mappedData;
1252 if (!data->BorrowMappedData(mappedData)) {
1253 return onFailure();
1256 Range<uint8_t> range = {mappedData.data,
1257 data->AsBufferTextureData()->GetBufferSize()};
1259 // If we have a surface representing the front buffer, then try to snapshot
1260 // that. Otherwise, when there is no surface, we read back directly from the
1261 // WebGL framebuffer.
1262 auto valid =
1263 surf ? FrontBufferSnapshotInto(surf, Some(range),
1264 Some(mappedData.stride))
1265 : SnapshotInto(fb->mGLName, size, range, Some(mappedData.stride));
1266 if (!valid) {
1267 return onFailure();
1270 if (!options.bgra) {
1271 // If the buffer is already BGRA, we don't need to swizzle. However, if it
1272 // is RGBA, then a swizzle to BGRA is required.
1273 bool rv = gfx::SwizzleData(mappedData.data, mappedData.stride,
1274 gfx::SurfaceFormat::R8G8B8A8, mappedData.data,
1275 mappedData.stride,
1276 gfx::SurfaceFormat::B8G8R8A8, mappedData.size);
1277 MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
1280 ownerClient->PushTexture(textureId, ownerId, std::move(data));
1281 return true;
1284 // SharedSurfaces of SurfaceDescriptorD3D10 and SurfaceDescriptorMacIOSurface
1285 // need to be kept alive. They will be recycled by
1286 // RemoteTextureOwnerClient::GetRecycledSharedSurface() when their usages are
1287 // ended.
1288 std::shared_ptr<gl::SharedSurface> keepAlive;
1289 switch (desc->type()) {
1290 case layers::SurfaceDescriptor::TSurfaceDescriptorD3D10:
1291 case layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface:
1292 case layers::SurfaceDescriptor::TSurfaceTextureDescriptor:
1293 case layers::SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer:
1294 case layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf:
1295 keepAlive = surf;
1296 break;
1297 default:
1298 break;
1301 ownerClient->PushTexture(textureId, ownerId, keepAlive, size, surfaceFormat,
1302 *desc);
1304 // Look for a recycled surface that matches the swap chain.
1305 while (auto recycledSurface = ownerClient->GetRecycledSharedSurface(
1306 size, surfaceFormat, desc->type(), ownerId)) {
1307 if (swapChain.StoreRecycledSurface(recycledSurface)) {
1308 break;
1311 return true;
1314 void WebGLContext::EnsureContextLostRemoteTextureOwner(
1315 const webgl::SwapChainOptions& options) {
1316 if (!options.remoteTextureOwnerId.IsValid()) {
1317 return;
1320 if (!mRemoteTextureOwner) {
1321 // Ensure we have a remote texture owner client for WebGLParent.
1322 const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr;
1323 if (!outOfProcess) {
1324 return;
1326 auto pid = outOfProcess->OtherPid();
1327 mRemoteTextureOwner = MakeRefPtr<layers::RemoteTextureOwnerClient>(pid);
1330 layers::RemoteTextureOwnerId ownerId = options.remoteTextureOwnerId;
1332 if (!mRemoteTextureOwner->IsRegistered(ownerId)) {
1333 mRemoteTextureOwner->RegisterTextureOwner(ownerId);
1335 mRemoteTextureOwner->NotifyContextLost();
1338 void WebGLContext::EndOfFrame() {
1339 const FuncScope funcScope(*this, "<EndOfFrame>");
1340 if (IsContextLost()) return;
1342 OnEndOfFrame();
1345 gl::SwapChain* WebGLContext::GetSwapChain(WebGLFramebuffer* const xrFb,
1346 const bool webvr) {
1347 auto swapChain = webvr ? &mWebVRSwapChain : &mSwapChain;
1348 if (xrFb) {
1349 swapChain = &xrFb->mSwapChain;
1351 return swapChain;
1354 Maybe<layers::SurfaceDescriptor> WebGLContext::GetFrontBuffer(
1355 WebGLFramebuffer* const xrFb, const bool webvr) {
1356 auto* swapChain = GetSwapChain(xrFb, webvr);
1357 if (!swapChain) return {};
1358 const auto& front = swapChain->FrontBuffer();
1359 if (!front) return {};
1361 return front->ToSurfaceDescriptor();
1364 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
1365 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
1366 const auto& front = mSwapChain.FrontBuffer();
1367 if (!front) return {};
1368 return FrontBufferSnapshotInto(front, maybeDest, destStride);
1371 Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
1372 const std::shared_ptr<gl::SharedSurface>& front,
1373 const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
1374 const auto& size = front->mDesc.size;
1375 if (!maybeDest) return Some(*uvec2::FromSize(size));
1377 // -
1379 front->WaitForBufferOwnership();
1380 front->LockProd();
1381 front->ProducerReadAcquire();
1382 auto reset = MakeScopeExit([&] {
1383 front->ProducerReadRelease();
1384 front->UnlockProd();
1387 // -
1389 return SnapshotInto(front->mFb ? front->mFb->mFB : 0, size, *maybeDest,
1390 destStride);
1393 Maybe<uvec2> WebGLContext::SnapshotInto(GLuint srcFb, const gfx::IntSize& size,
1394 const Range<uint8_t>& dest,
1395 const Maybe<size_t> destStride) {
1396 const auto minStride = CheckedInt<size_t>(size.width) * 4;
1397 if (!minStride.isValid()) {
1398 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width;
1399 return {};
1401 size_t stride = destStride.valueOr(minStride.value());
1402 if (stride < minStride.value() || (stride % 4) != 0) {
1403 gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width
1404 << ", stride:" << stride;
1405 return {};
1408 gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
1409 if (IsWebGL2()) {
1410 gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH,
1411 stride > minStride.value() ? stride / 4 : 0);
1412 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
1413 gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
1416 // -
1418 const auto readFbWas = mBoundReadFramebuffer;
1419 const auto pboWas = mBoundPixelPackBuffer;
1421 GLenum fbTarget = LOCAL_GL_READ_FRAMEBUFFER;
1422 if (!IsWebGL2()) {
1423 fbTarget = LOCAL_GL_FRAMEBUFFER;
1425 auto reset2 = MakeScopeExit([&] {
1426 DoBindFB(readFbWas, fbTarget);
1427 if (pboWas) {
1428 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
1432 gl->fBindFramebuffer(fbTarget, srcFb);
1433 if (pboWas) {
1434 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
1437 // -
1439 const auto srcByteCount = CheckedInt<size_t>(stride) * size.height;
1440 if (!srcByteCount.isValid()) {
1441 gfxCriticalError() << "SnapshotInto: invalid srcByteCount, width:"
1442 << size.width << ", height:" << size.height;
1443 return {};
1445 const auto dstByteCount = dest.length();
1446 if (srcByteCount.value() > dstByteCount) {
1447 gfxCriticalError() << "SnapshotInto: srcByteCount:" << srcByteCount.value()
1448 << " > dstByteCount:" << dstByteCount;
1449 return {};
1451 uint8_t* dstPtr = dest.begin().get();
1452 gl->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA,
1453 LOCAL_GL_UNSIGNED_BYTE, dstPtr);
1455 if (!IsWebGL2() && stride > minStride.value() && size.height > 1) {
1456 // WebGL 1 doesn't support PACK_ROW_LENGTH. Instead, we read the data tight
1457 // into the front of the buffer, and use memmove (since the source and dest
1458 // may overlap) starting from the back to move it to the correct stride
1459 // offsets. We don't move the first row as it is already in the right place.
1460 uint8_t* destRow = dstPtr + stride * (size.height - 1);
1461 const uint8_t* srcRow = dstPtr + minStride.value() * (size.height - 1);
1462 while (destRow > dstPtr) {
1463 memmove(destRow, srcRow, minStride.value());
1464 destRow -= stride;
1465 srcRow -= minStride.value();
1469 return Some(*uvec2::FromSize(size));
1472 void WebGLContext::ClearVRSwapChain() { mWebVRSwapChain.ClearPool(); }
1474 // ------------------------
1476 RefPtr<gfx::DataSourceSurface> GetTempSurface(const gfx::IntSize& aSize,
1477 gfx::SurfaceFormat& aFormat) {
1478 uint32_t stride =
1479 gfx::GetAlignedStride<8>(aSize.width, BytesPerPixel(aFormat));
1480 return gfx::Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat,
1481 stride);
1484 void WebGLContext::DummyReadFramebufferOperation() {
1485 if (!mBoundReadFramebuffer) return; // Infallible.
1487 const auto status = mBoundReadFramebuffer->CheckFramebufferStatus();
1488 if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
1489 ErrorInvalidFramebufferOperation("Framebuffer must be complete.");
1493 dom::ContentParentId WebGLContext::GetContentId() const {
1494 const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr;
1495 if (outOfProcess) {
1496 return outOfProcess->mContentId;
1498 if (XRE_IsContentProcess()) {
1499 return dom::ContentChild::GetSingleton()->GetID();
1501 return dom::ContentParentId();
1504 bool WebGLContext::Has64BitTimestamps() const {
1505 // 'sync' provides glGetInteger64v either by supporting ARB_sync, GL3+, or
1506 // GLES3+.
1507 return gl->IsSupported(gl::GLFeature::sync);
1510 static bool CheckContextLost(gl::GLContext* gl, bool* const out_isGuilty) {
1511 MOZ_ASSERT(gl);
1513 const auto resetStatus = gl->fGetGraphicsResetStatus();
1514 if (resetStatus == LOCAL_GL_NO_ERROR) {
1515 *out_isGuilty = false;
1516 return false;
1519 // Assume guilty unless we find otherwise!
1520 bool isGuilty = true;
1521 switch (resetStatus) {
1522 case LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB:
1523 case LOCAL_GL_PURGED_CONTEXT_RESET_NV:
1524 // Either nothing wrong, or not our fault.
1525 isGuilty = false;
1526 break;
1527 case LOCAL_GL_GUILTY_CONTEXT_RESET_ARB:
1528 NS_WARNING(
1529 "WebGL content on the page definitely caused the graphics"
1530 " card to reset.");
1531 break;
1532 case LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB:
1533 NS_WARNING(
1534 "WebGL content on the page might have caused the graphics"
1535 " card to reset");
1536 // If we can't tell, assume not-guilty.
1537 // Todo: Implement max number of "unknown" resets per document or time.
1538 isGuilty = false;
1539 break;
1540 default:
1541 gfxCriticalError() << "Unexpected glGetGraphicsResetStatus: "
1542 << gfx::hexa(resetStatus);
1543 break;
1546 if (isGuilty) {
1547 NS_WARNING(
1548 "WebGL context on this page is considered guilty, and will"
1549 " not be restored.");
1552 *out_isGuilty = isGuilty;
1553 return true;
1556 void WebGLContext::RunContextLossTimer() { mContextLossHandler.RunTimer(); }
1558 // We use this timer for many things. Here are the things that it is activated
1559 // for:
1560 // 1) If a script is using the MOZ_WEBGL_lose_context extension.
1561 // 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the
1562 // CONTEXT_LOST_WEBGL error has been triggered.
1563 // 3) If we are using ANGLE, or anything that supports ARB_robustness, query the
1564 // GPU periodically to see if the reset status bit has been set.
1565 // In all of these situations, we use this timer to send the script context lost
1566 // and restored events asynchronously. For example, if it triggers a context
1567 // loss, the webglcontextlost event will be sent to it the next time the
1568 // robustness timer fires.
1569 // Note that this timer mechanism is not used unless one of these 3 criteria are
1570 // met.
1571 // At a bare minimum, from context lost to context restores, it would take 3
1572 // full timer iterations: detection, webglcontextlost, webglcontextrestored.
1573 void WebGLContext::CheckForContextLoss() {
1574 bool isGuilty = true;
1575 const auto isContextLost = CheckContextLost(gl, &isGuilty);
1576 if (!isContextLost) return;
1578 mWebGLError = LOCAL_GL_CONTEXT_LOST;
1580 auto reason = webgl::ContextLossReason::None;
1581 if (isGuilty) {
1582 reason = webgl::ContextLossReason::Guilty;
1584 LoseContext(reason);
1587 void WebGLContext::HandlePendingContextLoss() {
1588 mIsContextLost = true;
1589 if (mHost) {
1590 mHost->OnContextLoss(mPendingContextLossReason);
1594 void WebGLContext::LoseContextLruLocked(const webgl::ContextLossReason reason) {
1595 printf_stderr("WebGL(%p)::LoseContext(%u)\n", this,
1596 static_cast<uint32_t>(reason));
1597 mLruPosition.ResetLocked();
1598 mPendingContextLossReason = reason;
1599 mPendingContextLoss = true;
1602 void WebGLContext::LoseContext(const webgl::ContextLossReason reason) {
1603 StaticMutexAutoLock lock(sLruMutex);
1604 LoseContextLruLocked(reason);
1605 HandlePendingContextLoss();
1606 if (mRemoteTextureOwner) {
1607 mRemoteTextureOwner->NotifyContextLost();
1611 void WebGLContext::DidRefresh() {
1612 if (gl) {
1613 gl->FlushIfHeavyGLCallsSinceLastFlush();
1617 ////////////////////////////////////////////////////////////////////////////////
1619 uvec2 WebGLContext::DrawingBufferSize() {
1620 const FuncScope funcScope(*this, "width/height");
1621 if (IsContextLost()) return {};
1623 if (!EnsureDefaultFB()) return {};
1625 return *uvec2::FromSize(mDefaultFB->mSize);
1628 bool WebGLContext::ValidateAndInitFB(const WebGLFramebuffer* const fb,
1629 const GLenum incompleteFbError) {
1630 if (fb) return fb->ValidateAndInitAttachments(incompleteFbError);
1632 if (!EnsureDefaultFB()) return false;
1634 if (mDefaultFB_IsInvalid) {
1635 // Clear it!
1636 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
1637 const webgl::ScopedPrepForResourceClear scopedPrep(*this);
1638 if (!mOptions.alpha) {
1639 gl->fClearColor(0, 0, 0, 1);
1641 const GLbitfield bits = LOCAL_GL_COLOR_BUFFER_BIT |
1642 LOCAL_GL_DEPTH_BUFFER_BIT |
1643 LOCAL_GL_STENCIL_BUFFER_BIT;
1644 gl->fClear(bits);
1646 mDefaultFB_IsInvalid = false;
1648 return true;
1651 void WebGLContext::DoBindFB(const WebGLFramebuffer* const fb,
1652 const GLenum target) const {
1653 const GLenum driverFB = fb ? fb->mGLName : mDefaultFB->mFB;
1654 gl->fBindFramebuffer(target, driverFB);
1657 bool WebGLContext::BindCurFBForDraw() {
1658 const auto& fb = mBoundDrawFramebuffer;
1659 if (!ValidateAndInitFB(fb)) return false;
1661 DoBindFB(fb);
1662 return true;
1665 bool WebGLContext::BindCurFBForColorRead(
1666 const webgl::FormatUsageInfo** const out_format, uint32_t* const out_width,
1667 uint32_t* const out_height, const GLenum incompleteFbError) {
1668 const auto& fb = mBoundReadFramebuffer;
1670 if (fb) {
1671 if (!ValidateAndInitFB(fb, incompleteFbError)) return false;
1672 if (!fb->ValidateForColorRead(out_format, out_width, out_height))
1673 return false;
1675 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb->mGLName);
1676 return true;
1679 if (!BindDefaultFBForRead()) return false;
1681 if (mDefaultFB_ReadBuffer == LOCAL_GL_NONE) {
1682 ErrorInvalidOperation(
1683 "Can't read from backbuffer when readBuffer mode is NONE.");
1684 return false;
1687 auto effFormat = mOptions.alpha ? webgl::EffectiveFormat::RGBA8
1688 : webgl::EffectiveFormat::RGB8;
1690 *out_format = mFormatUsage->GetUsage(effFormat);
1691 MOZ_ASSERT(*out_format);
1693 *out_width = mDefaultFB->mSize.width;
1694 *out_height = mDefaultFB->mSize.height;
1695 return true;
1698 bool WebGLContext::BindDefaultFBForRead() {
1699 if (!ValidateAndInitFB(nullptr)) return false;
1701 if (!mDefaultFB->mSamples) {
1702 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
1703 return true;
1706 if (!mResolvedDefaultFB) {
1707 mResolvedDefaultFB =
1708 gl::MozFramebuffer::Create(gl, mDefaultFB->mSize, 0, false);
1709 if (!mResolvedDefaultFB) {
1710 gfxCriticalNote << FuncName() << ": Failed to create mResolvedDefaultFB.";
1711 return false;
1715 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
1716 BlitBackbufferToCurDriverFB();
1718 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
1719 return true;
1722 void WebGLContext::DoColorMask(Maybe<GLuint> i, const uint8_t bitmask) const {
1723 if (!IsExtensionEnabled(WebGLExtensionID::OES_draw_buffers_indexed)) {
1724 i = Nothing();
1726 const auto bs = std::bitset<4>(bitmask);
1727 if (i) {
1728 gl->fColorMaski(*i, bs[0], bs[1], bs[2], bs[3]);
1729 } else {
1730 gl->fColorMask(bs[0], bs[1], bs[2], bs[3]);
1734 ////////////////////////////////////////////////////////////////////////////////
1736 ScopedDrawCallWrapper::ScopedDrawCallWrapper(WebGLContext& webgl)
1737 : mWebGL(webgl) {
1738 uint8_t driverColorMask0 = mWebGL.mColorWriteMask0;
1739 bool driverDepthTest = mWebGL.mDepthTestEnabled;
1740 bool driverStencilTest = mWebGL.mStencilTestEnabled;
1741 const auto& fb = mWebGL.mBoundDrawFramebuffer;
1742 if (!fb) {
1743 if (mWebGL.mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE) {
1744 driverColorMask0 = 0; // Is this well-optimized enough for depth-first
1745 // rendering?
1746 } else {
1747 driverColorMask0 &= ~(uint8_t(mWebGL.mNeedsFakeNoAlpha) << 3);
1749 driverDepthTest &= !mWebGL.mNeedsFakeNoDepth;
1750 driverStencilTest &= !mWebGL.mNeedsFakeNoStencil;
1753 const auto& gl = mWebGL.gl;
1754 mWebGL.DoColorMask(Some(0), driverColorMask0);
1755 if (mWebGL.mDriverDepthTest != driverDepthTest) {
1756 // "When disabled, the depth comparison and subsequent possible updates to
1757 // the
1758 // depth buffer value are bypassed and the fragment is passed to the next
1759 // operation." [GLES 3.0.5, p177]
1760 mWebGL.mDriverDepthTest = driverDepthTest;
1761 gl->SetEnabled(LOCAL_GL_DEPTH_TEST, mWebGL.mDriverDepthTest);
1763 if (mWebGL.mDriverStencilTest != driverStencilTest) {
1764 // "When disabled, the stencil test and associated modifications are not
1765 // made, and
1766 // the fragment is always passed." [GLES 3.0.5, p175]
1767 mWebGL.mDriverStencilTest = driverStencilTest;
1768 gl->SetEnabled(LOCAL_GL_STENCIL_TEST, mWebGL.mDriverStencilTest);
1772 ScopedDrawCallWrapper::~ScopedDrawCallWrapper() {
1773 if (mWebGL.mBoundDrawFramebuffer) return;
1775 mWebGL.mResolvedDefaultFB = nullptr;
1776 mWebGL.mShouldPresent = true;
1779 // -
1781 void WebGLContext::ScissorRect::Apply(gl::GLContext& gl) const {
1782 gl.fScissor(x, y, w, h);
1785 ////////////////////////////////////////
1787 IndexedBufferBinding::IndexedBufferBinding() = default;
1788 IndexedBufferBinding::~IndexedBufferBinding() = default;
1790 uint64_t IndexedBufferBinding::ByteCount() const {
1791 if (!mBufferBinding) return 0;
1793 uint64_t bufferSize = mBufferBinding->ByteLength();
1794 if (!mRangeSize) // BindBufferBase
1795 return bufferSize;
1797 if (mRangeStart >= bufferSize) return 0;
1798 bufferSize -= mRangeStart;
1800 return std::min(bufferSize, mRangeSize);
1803 ////////////////////////////////////////
1805 ScopedFBRebinder::~ScopedFBRebinder() {
1806 const auto fnName = [&](WebGLFramebuffer* fb) {
1807 return fb ? fb->mGLName : 0;
1810 const auto& gl = mWebGL->gl;
1811 if (mWebGL->IsWebGL2()) {
1812 gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
1813 fnName(mWebGL->mBoundDrawFramebuffer));
1814 gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
1815 fnName(mWebGL->mBoundReadFramebuffer));
1816 } else {
1817 MOZ_ASSERT(mWebGL->mBoundDrawFramebuffer == mWebGL->mBoundReadFramebuffer);
1818 gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER,
1819 fnName(mWebGL->mBoundDrawFramebuffer));
1823 ////////////////////
1825 void DoBindBuffer(gl::GLContext& gl, const GLenum target,
1826 const WebGLBuffer* const buffer) {
1827 gl.fBindBuffer(target, buffer ? buffer->mGLName : 0);
1830 ////////////////////////////////////////
1832 bool Intersect(const int32_t srcSize, const int32_t read0,
1833 const int32_t readSize, int32_t* const out_intRead0,
1834 int32_t* const out_intWrite0, int32_t* const out_intSize) {
1835 MOZ_ASSERT(srcSize >= 0);
1836 MOZ_ASSERT(readSize >= 0);
1837 const auto read1 = int64_t(read0) + readSize;
1839 int32_t intRead0 = read0; // Clearly doesn't need validation.
1840 int64_t intWrite0 = 0;
1841 int64_t intSize = readSize;
1843 if (read1 <= 0 || read0 >= srcSize) {
1844 // Disjoint ranges.
1845 intSize = 0;
1846 } else {
1847 if (read0 < 0) {
1848 const auto diff = int64_t(0) - read0;
1849 MOZ_ASSERT(diff >= 0);
1850 intRead0 = 0;
1851 intWrite0 = diff;
1852 intSize -= diff;
1854 if (read1 > srcSize) {
1855 const auto diff = int64_t(read1) - srcSize;
1856 MOZ_ASSERT(diff >= 0);
1857 intSize -= diff;
1860 if (!CheckedInt<int32_t>(intWrite0).isValid() ||
1861 !CheckedInt<int32_t>(intSize).isValid()) {
1862 return false;
1866 *out_intRead0 = intRead0;
1867 *out_intWrite0 = intWrite0;
1868 *out_intSize = intSize;
1869 return true;
1872 // --
1874 uint64_t AvailGroups(const uint64_t totalAvailItems,
1875 const uint64_t firstItemOffset, const uint32_t groupSize,
1876 const uint32_t groupStride) {
1877 MOZ_ASSERT(groupSize && groupStride);
1878 MOZ_ASSERT(groupSize <= groupStride);
1880 if (totalAvailItems <= firstItemOffset) return 0;
1881 const size_t availItems = totalAvailItems - firstItemOffset;
1883 size_t availGroups = availItems / groupStride;
1884 const size_t tailItems = availItems % groupStride;
1885 if (tailItems >= groupSize) {
1886 availGroups += 1;
1888 return availGroups;
1891 ////////////////////////////////////////////////////////////////////////////////
1893 const char* WebGLContext::FuncName() const {
1894 const char* ret;
1895 if (MOZ_LIKELY(mFuncScope)) {
1896 ret = mFuncScope->mFuncName;
1897 } else {
1898 NS_WARNING("FuncScope not on stack!");
1899 ret = "<unknown function>";
1901 return ret;
1904 // -
1906 WebGLContext::FuncScope::FuncScope(const WebGLContext& webgl,
1907 const char* const funcName)
1908 : mWebGL(webgl), mFuncName(bool(mWebGL.mFuncScope) ? nullptr : funcName) {
1909 if (!mFuncName) return;
1910 mWebGL.mFuncScope = this;
1913 WebGLContext::FuncScope::~FuncScope() {
1914 if (mBindFailureGuard) {
1915 gfxCriticalError() << "mBindFailureGuard failure: Early exit from "
1916 << mWebGL.FuncName();
1919 if (!mFuncName) return;
1920 mWebGL.mFuncScope = nullptr;
1923 // --
1925 bool ClientWebGLContext::IsXRCompatible() const { return mXRCompatible; }
1927 already_AddRefed<dom::Promise> ClientWebGLContext::MakeXRCompatible(
1928 ErrorResult& aRv) {
1929 const FuncScope funcScope(*this, "MakeXRCompatible");
1930 nsCOMPtr<nsIGlobalObject> global = GetParentObject();
1931 if (!global) {
1932 aRv.ThrowInvalidAccessError(
1933 "Using a WebGL context that is not attached to either a canvas or an "
1934 "OffscreenCanvas");
1935 return nullptr;
1937 RefPtr<dom::Promise> promise = dom::Promise::Create(global, aRv);
1938 NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
1940 if (IsContextLost()) {
1941 promise->MaybeRejectWithInvalidStateError(
1942 "Can not make context XR compatible when context is already lost.");
1943 return promise.forget();
1946 // TODO: Bug 1580258 - WebGLContext.MakeXRCompatible needs to switch to
1947 // the device connected to the XR hardware
1948 // This should update `options` and lose+restore the context.
1949 mXRCompatible = true;
1950 promise->MaybeResolveWithUndefined();
1951 return promise.forget();
1954 // --
1956 webgl::AvailabilityRunnable& ClientWebGLContext::EnsureAvailabilityRunnable()
1957 const {
1958 if (!mAvailabilityRunnable) {
1959 mAvailabilityRunnable = new webgl::AvailabilityRunnable(this);
1960 auto forgettable = mAvailabilityRunnable;
1961 NS_DispatchToCurrentThread(forgettable.forget());
1963 return *mAvailabilityRunnable;
1966 webgl::AvailabilityRunnable::AvailabilityRunnable(
1967 const ClientWebGLContext* const webgl)
1968 : DiscardableRunnable("webgl::AvailabilityRunnable"), mWebGL(webgl) {}
1970 webgl::AvailabilityRunnable::~AvailabilityRunnable() {
1971 MOZ_ASSERT(mQueries.empty());
1972 MOZ_ASSERT(mSyncs.empty());
1975 nsresult webgl::AvailabilityRunnable::Run() {
1976 for (const auto& cur : mQueries) {
1977 if (!cur) continue;
1978 cur->mCanBeAvailable = true;
1980 mQueries.clear();
1982 for (const auto& cur : mSyncs) {
1983 if (!cur) continue;
1984 cur->mCanBeAvailable = true;
1986 mSyncs.clear();
1988 if (mWebGL) {
1989 mWebGL->mAvailabilityRunnable = nullptr;
1991 return NS_OK;
1994 // -
1996 void WebGLContext::JsWarning(const std::string& text) const {
1997 if (mHost) {
1998 mHost->JsWarning(text);
2000 #ifdef DEBUG
2001 if (!mHost) {
2002 NS_WARNING(text.c_str());
2004 #endif
2007 void WebGLContext::GenerateErrorImpl(const GLenum errOrWarning,
2008 const std::string& text) const {
2009 auto err = errOrWarning;
2010 bool isPerfWarning = false;
2011 if (err == webgl::kErrorPerfWarning) {
2012 err = 0;
2013 isPerfWarning = true;
2016 if (err && mFuncScope && mFuncScope->mBindFailureGuard) {
2017 gfxCriticalError() << "mBindFailureGuard failure: Generating error "
2018 << EnumString(err) << ": " << text;
2021 /* ES2 section 2.5 "GL Errors" states that implementations can have
2022 * multiple 'flags', as errors might be caught in different parts of
2023 * a distributed implementation.
2024 * We're signing up as a distributed implementation here, with
2025 * separate flags for WebGL and the underlying GLContext.
2027 if (!mWebGLError) mWebGLError = err;
2029 if (!mHost) return; // Impossible?
2031 // -
2033 const auto ShouldWarn = [&]() {
2034 if (isPerfWarning) {
2035 return ShouldGeneratePerfWarnings();
2037 return ShouldGenerateWarnings();
2039 if (!ShouldWarn()) return;
2041 // -
2043 auto* pNumWarnings = &mWarningCount;
2044 const char* warningsType = "warnings";
2045 if (isPerfWarning) {
2046 pNumWarnings = &mNumPerfWarnings;
2047 warningsType = "perf warnings";
2050 if (isPerfWarning) {
2051 const auto perfText = std::string("WebGL perf warning: ") + text;
2052 JsWarning(perfText);
2053 } else {
2054 JsWarning(text);
2056 *pNumWarnings += 1;
2058 if (!ShouldWarn()) {
2059 const auto& msg = nsPrintfCString(
2060 "After reporting %i, no further %s will be reported for this WebGL "
2061 "context.",
2062 int(*pNumWarnings), warningsType);
2063 JsWarning(ToString(msg));
2067 // -
2069 Maybe<std::string> WebGLContext::GetString(const GLenum pname) const {
2070 const WebGLContext::FuncScope funcScope(*this, "getParameter");
2071 if (IsContextLost()) return {};
2073 const auto FromRaw = [](const char* const raw) -> Maybe<std::string> {
2074 if (!raw) return {};
2075 return Some(std::string(raw));
2078 switch (pname) {
2079 case LOCAL_GL_EXTENSIONS: {
2080 if (!gl->IsCoreProfile()) {
2081 const auto rawExt = (const char*)gl->fGetString(LOCAL_GL_EXTENSIONS);
2082 return FromRaw(rawExt);
2084 std::string ret;
2085 const auto& numExts = gl->GetIntAs<GLuint>(LOCAL_GL_NUM_EXTENSIONS);
2086 for (GLuint i = 0; i < numExts; i++) {
2087 const auto rawExt =
2088 (const char*)gl->fGetStringi(LOCAL_GL_EXTENSIONS, i);
2089 if (!rawExt) continue;
2091 if (i > 0) {
2092 ret += " ";
2094 ret += rawExt;
2096 return Some(std::move(ret));
2099 case LOCAL_GL_RENDERER:
2100 case LOCAL_GL_VENDOR:
2101 case LOCAL_GL_VERSION: {
2102 const auto raw = (const char*)gl->fGetString(pname);
2103 return FromRaw(raw);
2106 case dom::MOZ_debug_Binding::WSI_INFO: {
2107 nsCString info;
2108 gl->GetWSIInfo(&info);
2109 return Some(std::string(info.BeginReading()));
2112 default:
2113 ErrorInvalidEnumArg("pname", pname);
2114 return {};
2118 // ---------------------------------
2120 Maybe<webgl::IndexedName> webgl::ParseIndexed(const std::string& str) {
2121 static const std::regex kRegex("(.*)\\[([0-9]+)\\]");
2123 std::smatch match;
2124 if (!std::regex_match(str, match, kRegex)) return {};
2126 const auto index = std::stoull(match[2]);
2127 return Some(webgl::IndexedName{match[1], index});
2130 // ExplodeName("foo.bar[3].x") -> ["foo", ".", "bar", "[", "3", "]", ".", "x"]
2131 static std::vector<std::string> ExplodeName(const std::string& str) {
2132 std::vector<std::string> ret;
2134 static const std::regex kSep("[.[\\]]");
2136 auto itr = std::regex_token_iterator<decltype(str.begin())>(
2137 str.begin(), str.end(), kSep, {-1, 0});
2138 const auto end = decltype(itr)();
2140 for (; itr != end; ++itr) {
2141 const auto& part = itr->str();
2142 if (part.size()) {
2143 ret.push_back(part);
2146 return ret;
2151 // #define DUMP_MakeLinkResult
2153 webgl::LinkActiveInfo GetLinkActiveInfo(
2154 gl::GLContext& gl, const GLuint prog, const bool webgl2,
2155 const std::unordered_map<std::string, std::string>& nameUnmap) {
2156 webgl::LinkActiveInfo ret;
2157 [&]() {
2158 const auto fnGetProgramui = [&](const GLenum pname) {
2159 GLint ret = 0;
2160 gl.fGetProgramiv(prog, pname, &ret);
2161 return static_cast<uint32_t>(ret);
2164 std::vector<char> stringBuffer(1);
2165 const auto fnEnsureCapacity = [&](const GLenum pname) {
2166 const auto maxWithNull = fnGetProgramui(pname);
2167 if (maxWithNull > stringBuffer.size()) {
2168 stringBuffer.resize(maxWithNull);
2172 fnEnsureCapacity(LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH);
2173 fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH);
2174 if (webgl2) {
2175 fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH);
2176 fnEnsureCapacity(LOCAL_GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH);
2179 // -
2181 const auto fnUnmapName = [&](const std::string& mappedName) {
2182 const auto parts = ExplodeName(mappedName);
2184 std::ostringstream ret;
2185 for (const auto& part : parts) {
2186 const auto maybe = MaybeFind(nameUnmap, part);
2187 if (maybe) {
2188 ret << *maybe;
2189 } else {
2190 ret << part;
2193 return ret.str();
2196 // -
2199 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_ATTRIBUTES);
2200 ret.activeAttribs.reserve(count);
2201 for (const auto i : IntegerRange(count)) {
2202 GLsizei lengthWithoutNull = 0;
2203 GLint elemCount = 0; // `size`
2204 GLenum elemType = 0; // `type`
2205 gl.fGetActiveAttrib(prog, i, stringBuffer.size(), &lengthWithoutNull,
2206 &elemCount, &elemType, stringBuffer.data());
2207 if (!elemType) {
2208 const auto error = gl.fGetError();
2209 if (error != LOCAL_GL_CONTEXT_LOST) {
2210 gfxCriticalError() << "Failed to do glGetActiveAttrib: " << error;
2212 return;
2214 const auto mappedName =
2215 std::string(stringBuffer.data(), lengthWithoutNull);
2216 const auto userName = fnUnmapName(mappedName);
2218 auto loc = gl.fGetAttribLocation(prog, mappedName.c_str());
2219 if (mappedName.find("gl_") == 0) {
2220 // Bug 1328559: Appears problematic on ANGLE and OSX, but not Linux or
2221 // Win+GL.
2222 loc = -1;
2225 #ifdef DUMP_MakeLinkResult
2226 printf_stderr("[attrib %u/%u] @%i %s->%s\n", i, count, loc,
2227 userName.c_str(), mappedName.c_str());
2228 #endif
2229 webgl::ActiveAttribInfo info;
2230 info.elemType = elemType;
2231 info.elemCount = elemCount;
2232 info.name = userName;
2233 info.location = loc;
2234 info.baseType = webgl::ToAttribBaseType(info.elemType);
2235 ret.activeAttribs.push_back(std::move(info));
2239 // -
2242 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORMS);
2243 ret.activeUniforms.reserve(count);
2245 std::vector<GLint> blockIndexList(count, -1);
2246 std::vector<GLint> blockOffsetList(count, -1);
2247 std::vector<GLint> blockArrayStrideList(count, -1);
2248 std::vector<GLint> blockMatrixStrideList(count, -1);
2249 std::vector<GLint> blockIsRowMajorList(count, 0);
2251 if (webgl2 && count) {
2252 std::vector<GLuint> activeIndices;
2253 activeIndices.reserve(count);
2254 for (const auto i : IntegerRange(count)) {
2255 activeIndices.push_back(i);
2258 gl.fGetActiveUniformsiv(
2259 prog, activeIndices.size(), activeIndices.data(),
2260 LOCAL_GL_UNIFORM_BLOCK_INDEX, blockIndexList.data());
2262 gl.fGetActiveUniformsiv(prog, activeIndices.size(),
2263 activeIndices.data(), LOCAL_GL_UNIFORM_OFFSET,
2264 blockOffsetList.data());
2266 gl.fGetActiveUniformsiv(
2267 prog, activeIndices.size(), activeIndices.data(),
2268 LOCAL_GL_UNIFORM_ARRAY_STRIDE, blockArrayStrideList.data());
2270 gl.fGetActiveUniformsiv(
2271 prog, activeIndices.size(), activeIndices.data(),
2272 LOCAL_GL_UNIFORM_MATRIX_STRIDE, blockMatrixStrideList.data());
2274 gl.fGetActiveUniformsiv(
2275 prog, activeIndices.size(), activeIndices.data(),
2276 LOCAL_GL_UNIFORM_IS_ROW_MAJOR, blockIsRowMajorList.data());
2279 for (const auto i : IntegerRange(count)) {
2280 GLsizei lengthWithoutNull = 0;
2281 GLint elemCount = 0; // `size`
2282 GLenum elemType = 0; // `type`
2283 gl.fGetActiveUniform(prog, i, stringBuffer.size(), &lengthWithoutNull,
2284 &elemCount, &elemType, stringBuffer.data());
2285 if (!elemType) {
2286 const auto error = gl.fGetError();
2287 if (error != LOCAL_GL_CONTEXT_LOST) {
2288 gfxCriticalError() << "Failed to do glGetActiveUniform: " << error;
2290 return;
2292 auto mappedName = std::string(stringBuffer.data(), lengthWithoutNull);
2294 // Get true name
2296 auto baseMappedName = mappedName;
2298 const bool isArray = [&]() {
2299 const auto maybe = webgl::ParseIndexed(mappedName);
2300 if (maybe) {
2301 MOZ_ASSERT(maybe->index == 0);
2302 baseMappedName = std::move(maybe->name);
2303 return true;
2305 return false;
2306 }();
2308 const auto userName = fnUnmapName(mappedName);
2309 if (StartsWith(userName, "webgl_")) continue;
2311 // -
2313 webgl::ActiveUniformInfo info;
2314 info.elemType = elemType;
2315 info.elemCount = static_cast<uint32_t>(elemCount);
2316 info.name = userName;
2317 info.block_index = blockIndexList[i];
2318 info.block_offset = blockOffsetList[i];
2319 info.block_arrayStride = blockArrayStrideList[i];
2320 info.block_matrixStride = blockMatrixStrideList[i];
2321 info.block_isRowMajor = bool(blockIsRowMajorList[i]);
2323 #ifdef DUMP_MakeLinkResult
2324 printf_stderr("[uniform %u/%u] %s->%s\n", i + 1, count,
2325 userName.c_str(), mappedName.c_str());
2326 #endif
2328 // Get uniform locations
2330 auto locName = baseMappedName;
2331 const auto baseLength = locName.size();
2332 for (const auto i : IntegerRange(info.elemCount)) {
2333 if (isArray) {
2334 locName.erase(
2335 baseLength); // Erase previous [N], but retain capacity.
2336 locName += '[';
2337 locName += std::to_string(i);
2338 locName += ']';
2340 const auto loc = gl.fGetUniformLocation(prog, locName.c_str());
2341 if (loc != -1) {
2342 info.locByIndex[i] = static_cast<uint32_t>(loc);
2343 #ifdef DUMP_MakeLinkResult
2344 printf_stderr(" [%u] @%i\n", i, loc);
2345 #endif
2348 } // anon
2350 ret.activeUniforms.push_back(std::move(info));
2351 } // for i
2352 } // anon
2354 if (webgl2) {
2355 // -------------------------------------
2356 // active uniform blocks
2358 const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORM_BLOCKS);
2359 ret.activeUniformBlocks.reserve(count);
2361 for (const auto i : IntegerRange(count)) {
2362 GLsizei lengthWithoutNull = 0;
2363 gl.fGetActiveUniformBlockName(prog, i, stringBuffer.size(),
2364 &lengthWithoutNull,
2365 stringBuffer.data());
2366 const auto mappedName =
2367 std::string(stringBuffer.data(), lengthWithoutNull);
2368 const auto userName = fnUnmapName(mappedName);
2370 // -
2372 auto info = webgl::ActiveUniformBlockInfo{userName};
2373 GLint val = 0;
2375 gl.fGetActiveUniformBlockiv(prog, i, LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE,
2376 &val);
2377 info.dataSize = static_cast<uint32_t>(val);
2379 gl.fGetActiveUniformBlockiv(
2380 prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &val);
2381 info.activeUniformIndices.resize(val);
2382 gl.fGetActiveUniformBlockiv(
2383 prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES,
2384 reinterpret_cast<GLint*>(info.activeUniformIndices.data()));
2386 gl.fGetActiveUniformBlockiv(
2387 prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER,
2388 &val);
2389 info.referencedByVertexShader = bool(val);
2391 gl.fGetActiveUniformBlockiv(
2392 prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER,
2393 &val);
2394 info.referencedByFragmentShader = bool(val);
2396 ret.activeUniformBlocks.push_back(std::move(info));
2397 } // for i
2398 } // anon
2400 // -------------------------------------
2401 // active tf varyings
2403 const auto count = fnGetProgramui(LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS);
2404 ret.activeTfVaryings.reserve(count);
2406 for (const auto i : IntegerRange(count)) {
2407 GLsizei lengthWithoutNull = 0;
2408 GLsizei elemCount = 0; // `size`
2409 GLenum elemType = 0; // `type`
2410 gl.fGetTransformFeedbackVarying(prog, i, stringBuffer.size(),
2411 &lengthWithoutNull, &elemCount,
2412 &elemType, stringBuffer.data());
2413 const auto mappedName =
2414 std::string(stringBuffer.data(), lengthWithoutNull);
2415 const auto userName = fnUnmapName(mappedName);
2417 ret.activeTfVaryings.push_back(
2418 {elemType, static_cast<uint32_t>(elemCount), userName});
2421 } // if webgl2
2422 }();
2423 return ret;
2426 nsCString ToCString(const std::string& s) {
2427 return nsCString(s.data(), s.size());
2430 webgl::CompileResult WebGLContext::GetCompileResult(
2431 const WebGLShader& shader) const {
2432 webgl::CompileResult ret;
2433 [&]() {
2434 ret.pending = false;
2435 const auto& info = shader.CompileResults();
2436 if (!info) return;
2437 if (!info->mValid) {
2438 ret.log = info->mInfoLog.c_str();
2439 return;
2441 // TODO: These could be large and should be made fallible.
2442 ret.translatedSource = ToCString(info->mObjectCode);
2443 ret.log = ToCString(shader.CompileLog());
2444 if (!shader.IsCompiled()) return;
2445 ret.success = true;
2446 }();
2447 return ret;
2450 webgl::LinkResult WebGLContext::GetLinkResult(const WebGLProgram& prog) const {
2451 webgl::LinkResult ret;
2452 [&]() {
2453 ret.pending = false; // Link status polling not yet implemented.
2454 ret.log = ToCString(prog.LinkLog());
2455 const auto& info = prog.LinkInfo();
2456 if (!info) return;
2457 ret.success = true;
2458 ret.active = info->active;
2459 ret.tfBufferMode = info->transformFeedbackBufferMode;
2460 }();
2461 return ret;
2464 // -
2466 GLint WebGLContext::GetFragDataLocation(const WebGLProgram& prog,
2467 const std::string& userName) const {
2468 const auto err = CheckGLSLVariableName(IsWebGL2(), userName);
2469 if (err) {
2470 GenerateError(err->type, "%s", err->info.c_str());
2471 return -1;
2474 const auto& info = prog.LinkInfo();
2475 if (!info) return -1;
2476 const auto& nameMap = info->nameMap;
2478 const auto parts = ExplodeName(userName);
2480 std::ostringstream ret;
2481 for (const auto& part : parts) {
2482 const auto maybe = MaybeFind(nameMap, part);
2483 if (maybe) {
2484 ret << *maybe;
2485 } else {
2486 ret << part;
2489 const auto mappedName = ret.str();
2491 if (gl->WorkAroundDriverBugs() && gl->IsMesa()) {
2492 // Mesa incorrectly generates INVALID_OPERATION for gl_ prefixes here.
2493 if (mappedName.find("gl_") == 0) {
2494 return -1;
2498 return gl->fGetFragDataLocation(prog.mGLName, mappedName.c_str());
2501 // -
2503 WebGLContextBoundObject::WebGLContextBoundObject(WebGLContext* webgl)
2504 : mContext(webgl) {}
2506 // -
2508 Result<webgl::ExplicitPixelPackingState, std::string>
2509 webgl::ExplicitPixelPackingState::ForUseWith(
2510 const webgl::PixelPackingState& stateOrZero, const GLenum target,
2511 const uvec3& subrectSize, const webgl::PackingInfo& pi,
2512 const Maybe<size_t> bytesPerRowStrideOverride) {
2513 auto state = stateOrZero;
2515 if (!IsTexTarget3D(target)) {
2516 state.skipImages = 0;
2517 state.imageHeight = 0;
2519 if (!state.rowLength) {
2520 state.rowLength = subrectSize.x;
2522 if (!state.imageHeight) {
2523 state.imageHeight = subrectSize.y;
2526 // -
2528 const auto mpii = PackingInfoInfo::For(pi);
2529 if (!mpii) {
2530 const auto text =
2531 nsPrintfCString("Invalid pi: { 0x%x, 0x%x}", pi.format, pi.type);
2532 return Err(mozilla::ToString(text));
2534 const auto pii = *mpii;
2535 const auto bytesPerPixel = pii.BytesPerPixel();
2537 const auto ElemsPerRowStride = [&]() {
2538 // GLES 3.0.6 p116:
2539 // p: `Elem*` pointer to the first element of the first row
2540 // N: row number, starting at 0
2541 // l: groups (pixels) per row
2542 // n: elements per group (pixel) in [1,2,3,4]
2543 // s: bytes per element in [1,2,4,8]
2544 // a: UNPACK_ALIGNMENT in [1,2,4,8]
2545 // Pointer to first element of Nth row: p + N*k
2546 // k(s>=a): n*l
2547 // k(s<a): a/s * ceil(s*n*l/a)
2548 const auto n__elemsPerPixel = pii.elementsPerPixel;
2549 const auto l__pixelsPerRow = state.rowLength;
2550 const auto a__alignment = state.alignmentInTypeElems;
2551 const auto s__bytesPerElem = pii.bytesPerElement;
2553 const auto nl = CheckedInt<size_t>(n__elemsPerPixel) * l__pixelsPerRow;
2554 auto k__elemsPerRowStride = nl;
2555 if (s__bytesPerElem < a__alignment) {
2556 // k = a/s * ceil(s*n*l/a)
2557 k__elemsPerRowStride =
2558 a__alignment / s__bytesPerElem *
2559 ((nl * s__bytesPerElem + a__alignment - 1) / a__alignment);
2561 return k__elemsPerRowStride;
2564 // -
2566 if (bytesPerRowStrideOverride) { // E.g. HTMLImageElement
2567 const size_t bytesPerRowStrideRequired = *bytesPerRowStrideOverride;
2568 // We have to reverse-engineer an ALIGNMENT and ROW_LENGTH for this.
2570 // GL does this in elems not bytes, so we should too.
2571 MOZ_RELEASE_ASSERT(bytesPerRowStrideRequired % pii.bytesPerElement == 0);
2572 const auto elemsPerRowStrideRequired =
2573 bytesPerRowStrideRequired / pii.bytesPerElement;
2575 state.rowLength = bytesPerRowStrideRequired / bytesPerPixel;
2576 state.alignmentInTypeElems = 8;
2577 while (true) {
2578 const auto elemPerRowStride = ElemsPerRowStride();
2579 if (elemPerRowStride.isValid() &&
2580 elemPerRowStride.value() == elemsPerRowStrideRequired) {
2581 break;
2583 state.alignmentInTypeElems /= 2;
2584 if (!state.alignmentInTypeElems) {
2585 const auto text = nsPrintfCString(
2586 "No valid alignment found: pi: { 0x%x, 0x%x},"
2587 " bytesPerRowStrideRequired: %zu",
2588 pi.format, pi.type, bytesPerRowStrideRequired);
2589 return Err(mozilla::ToString(text));
2594 // -
2596 const auto usedPixelsPerRow =
2597 CheckedInt<size_t>(state.skipPixels) + subrectSize.x;
2598 if (!usedPixelsPerRow.isValid() ||
2599 usedPixelsPerRow.value() > state.rowLength) {
2600 return Err("UNPACK_SKIP_PIXELS + width > UNPACK_ROW_LENGTH.");
2603 if (subrectSize.y > state.imageHeight) {
2604 return Err("height > UNPACK_IMAGE_HEIGHT.");
2606 // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
2608 // -
2610 auto metrics = Metrics{};
2612 metrics.usedSize = subrectSize;
2613 metrics.bytesPerPixel = BytesPerPixel(pi);
2615 // -
2617 const auto elemsPerRowStride = ElemsPerRowStride();
2618 const auto bytesPerRowStride = pii.bytesPerElement * elemsPerRowStride;
2619 if (!bytesPerRowStride.isValid()) {
2620 return Err("ROW_LENGTH or width too large for packing.");
2622 metrics.bytesPerRowStride = bytesPerRowStride.value();
2624 // -
2626 const auto firstImageTotalRows =
2627 CheckedInt<size_t>(state.skipRows) + metrics.usedSize.y;
2628 const auto totalImages =
2629 CheckedInt<size_t>(state.skipImages) + metrics.usedSize.z;
2630 auto totalRows = CheckedInt<size_t>(0);
2631 if (metrics.usedSize.y && metrics.usedSize.z) {
2632 totalRows = firstImageTotalRows + state.imageHeight * (totalImages - 1);
2634 if (!totalRows.isValid()) {
2635 return Err(
2636 "SKIP_ROWS, height, IMAGE_HEIGHT, SKIP_IMAGES, or depth too large for "
2637 "packing.");
2639 metrics.totalRows = totalRows.value();
2641 // -
2643 const auto totalBytesStrided = totalRows * metrics.bytesPerRowStride;
2644 if (!totalBytesStrided.isValid()) {
2645 return Err("Total byte count too large for packing.");
2647 metrics.totalBytesStrided = totalBytesStrided.value();
2649 metrics.totalBytesUsed = metrics.totalBytesStrided;
2650 if (metrics.usedSize.x && metrics.usedSize.y && metrics.usedSize.z) {
2651 const auto usedBytesPerRow =
2652 usedPixelsPerRow.value() * metrics.bytesPerPixel;
2653 metrics.totalBytesUsed -= metrics.bytesPerRowStride;
2654 metrics.totalBytesUsed += usedBytesPerRow;
2657 // -
2659 return {{state, metrics}};
2662 } // namespace mozilla