summaryrefslogtreecommitdiffstats
path: root/dom/canvas/WebGLContext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/WebGLContext.cpp')
-rw-r--r--dom/canvas/WebGLContext.cpp2555
1 files changed, 2555 insertions, 0 deletions
diff --git a/dom/canvas/WebGLContext.cpp b/dom/canvas/WebGLContext.cpp
new file mode 100644
index 0000000000..1a0b180909
--- /dev/null
+++ b/dom/canvas/WebGLContext.cpp
@@ -0,0 +1,2555 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebGLContext.h"
+
+#include <algorithm>
+#include <bitset>
+#include <queue>
+#include <regex>
+
+#include "AccessCheck.h"
+#include "CompositableHost.h"
+#include "gfxConfig.h"
+#include "gfxContext.h"
+#include "gfxCrashReporterUtils.h"
+#include "gfxEnv.h"
+#include "gfxPattern.h"
+#include "MozFramebuffer.h"
+#include "GLBlitHelper.h"
+#include "GLContext.h"
+#include "GLContextProvider.h"
+#include "GLReadTexImageHelper.h"
+#include "GLScreenBuffer.h"
+#include "ImageContainer.h"
+#include "ImageEncoder.h"
+#include "LayerUserData.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/HTMLVideoElement.h"
+#include "mozilla/dom/ImageData.h"
+#include "mozilla/dom/WebGLContextEvent.h"
+#include "mozilla/EnumeratedArrayCycleCollection.h"
+#include "mozilla/EnumeratedRange.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProcessPriorityManager.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPrefs_webgl.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/Telemetry.h"
+#include "nsContentUtils.h"
+#include "nsDisplayList.h"
+#include "nsError.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIWidget.h"
+#include "nsServiceManagerUtils.h"
+#include "SharedSurfaceGL.h"
+#include "prenv.h"
+#include "ScopedGLHelpers.h"
+#include "VRManagerChild.h"
+#include "mozilla/gfx/Swizzle.h"
+#include "mozilla/layers/BufferTexture.h"
+#include "mozilla/layers/RemoteTextureMap.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "mozilla/layers/TextureClientSharedSurface.h"
+#include "mozilla/layers/WebRenderUserData.h"
+#include "mozilla/layers/WebRenderCanvasRenderer.h"
+
+// Local
+#include "CanvasUtils.h"
+#include "ClientWebGLContext.h"
+#include "HostWebGLContext.h"
+#include "WebGLBuffer.h"
+#include "WebGLChild.h"
+#include "WebGLContextLossHandler.h"
+#include "WebGLContextUtils.h"
+#include "WebGLExtensions.h"
+#include "WebGLFormats.h"
+#include "WebGLFramebuffer.h"
+#include "WebGLMemoryTracker.h"
+#include "WebGLObjectModel.h"
+#include "WebGLParent.h"
+#include "WebGLProgram.h"
+#include "WebGLQuery.h"
+#include "WebGLSampler.h"
+#include "WebGLShader.h"
+#include "WebGLShaderValidator.h"
+#include "WebGLSync.h"
+#include "WebGLTransformFeedback.h"
+#include "WebGLValidateStrings.h"
+#include "WebGLVertexArray.h"
+
+#ifdef MOZ_WIDGET_COCOA
+# include "nsCocoaFeatures.h"
+#endif
+
+#ifdef XP_WIN
+# include "WGLLibrary.h"
+#endif
+
+// Generated
+#include "mozilla/dom/WebGLRenderingContextBinding.h"
+
+namespace mozilla {
+
+WebGLContextOptions::WebGLContextOptions() {
+ // Set default alpha state based on preference.
+ alpha = !StaticPrefs::webgl_default_no_alpha();
+ antialias = StaticPrefs::webgl_default_antialias();
+}
+
+StaticMutex WebGLContext::sLruMutex;
+std::list<WebGLContext*> WebGLContext::sLru;
+
+WebGLContext::LruPosition::LruPosition() {
+ StaticMutexAutoLock lock(sLruMutex);
+ mItr = sLru.end();
+} // NOLINT
+
+WebGLContext::LruPosition::LruPosition(WebGLContext& context) {
+ StaticMutexAutoLock lock(sLruMutex);
+ mItr = sLru.insert(sLru.end(), &context);
+}
+
+void WebGLContext::LruPosition::AssignLocked(WebGLContext& aContext) {
+ ResetLocked();
+ mItr = sLru.insert(sLru.end(), &aContext);
+}
+
+void WebGLContext::LruPosition::ResetLocked() {
+ const auto end = sLru.end();
+ if (mItr != end) {
+ sLru.erase(mItr);
+ mItr = end;
+ }
+}
+
+void WebGLContext::LruPosition::Reset() {
+ StaticMutexAutoLock lock(sLruMutex);
+ ResetLocked();
+}
+
+bool WebGLContext::LruPosition::IsInsertedLocked() const {
+ return mItr != sLru.end();
+}
+
+WebGLContext::WebGLContext(HostWebGLContext& host,
+ const webgl::InitContextDesc& desc)
+ : gl(mGL_OnlyClearInDestroyResourcesAndContext), // const reference
+ mHost(&host),
+ mResistFingerprinting(desc.resistFingerprinting),
+ mOptions(desc.options),
+ mPrincipalKey(desc.principalKey),
+ mPendingContextLoss(false),
+ mMaxPerfWarnings(StaticPrefs::webgl_perf_max_warnings()),
+ mMaxAcceptableFBStatusInvals(
+ StaticPrefs::webgl_perf_max_acceptable_fb_status_invals()),
+ mContextLossHandler(this),
+ mMaxWarnings(StaticPrefs::webgl_max_warnings_per_context()),
+ mAllowFBInvalidation(StaticPrefs::webgl_allow_fb_invalidation()),
+ mMsaaSamples((uint8_t)StaticPrefs::webgl_msaa_samples()),
+ mRequestedSize(desc.size) {
+ host.mContext = this;
+ const FuncScope funcScope(*this, "<Create>");
+}
+
+WebGLContext::~WebGLContext() { DestroyResourcesAndContext(); }
+
+void WebGLContext::DestroyResourcesAndContext() {
+ if (mRemoteTextureOwner) {
+ // Clean up any remote textures registered for framebuffer swap chains.
+ mRemoteTextureOwner->UnregisterAllTextureOwners();
+ mRemoteTextureOwner = nullptr;
+ }
+
+ if (!gl) return;
+
+ mDefaultFB = nullptr;
+ mResolvedDefaultFB = nullptr;
+
+ mBound2DTextures.Clear();
+ mBoundCubeMapTextures.Clear();
+ mBound3DTextures.Clear();
+ mBound2DArrayTextures.Clear();
+ mBoundSamplers.Clear();
+ mBoundArrayBuffer = nullptr;
+ mBoundCopyReadBuffer = nullptr;
+ mBoundCopyWriteBuffer = nullptr;
+ mBoundPixelPackBuffer = nullptr;
+ mBoundPixelUnpackBuffer = nullptr;
+ mBoundTransformFeedbackBuffer = nullptr;
+ mBoundUniformBuffer = nullptr;
+ mCurrentProgram = nullptr;
+ mActiveProgramLinkInfo = nullptr;
+ mBoundDrawFramebuffer = nullptr;
+ mBoundReadFramebuffer = nullptr;
+ mBoundVertexArray = nullptr;
+ mDefaultVertexArray = nullptr;
+ mBoundTransformFeedback = nullptr;
+ mDefaultTransformFeedback = nullptr;
+
+ mQuerySlot_SamplesPassed = nullptr;
+ mQuerySlot_TFPrimsWritten = nullptr;
+ mQuerySlot_TimeElapsed = nullptr;
+
+ mIndexedUniformBufferBindings.clear();
+
+ //////
+
+ if (mEmptyTFO) {
+ gl->fDeleteTransformFeedbacks(1, &mEmptyTFO);
+ mEmptyTFO = 0;
+ }
+
+ //////
+
+ if (mFakeVertexAttrib0BufferObject) {
+ gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject);
+ mFakeVertexAttrib0BufferObject = 0;
+ }
+
+ // disable all extensions except "WEBGL_lose_context". see bug #927969
+ // spec: http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
+ for (size_t i = 0; i < size_t(WebGLExtensionID::Max); ++i) {
+ WebGLExtensionID extension = WebGLExtensionID(i);
+ if (extension == WebGLExtensionID::WEBGL_lose_context) continue;
+ mExtensions[extension] = nullptr;
+ }
+
+ // We just got rid of everything, so the context had better
+ // have been going away.
+ if (gl::GLContext::ShouldSpew()) {
+ printf_stderr("--- WebGL context destroyed: %p\n", gl.get());
+ }
+
+ MOZ_ASSERT(gl);
+ gl->MarkDestroyed();
+ mGL_OnlyClearInDestroyResourcesAndContext = nullptr;
+ MOZ_ASSERT(!gl);
+}
+
+void ClientWebGLContext::MarkCanvasDirty() {
+ if (!mCanvasElement && !mOffscreenCanvas) return;
+
+ mFrameCaptureState = FrameCaptureState::DIRTY;
+
+ if (mIsCanvasDirty) return;
+ mIsCanvasDirty = true;
+
+ if (mCanvasElement) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
+ mCanvasElement->InvalidateCanvasContent(nullptr);
+ } else if (mOffscreenCanvas) {
+ mOffscreenCanvas->QueueCommitToCompositor();
+ }
+}
+
+void WebGLContext::OnMemoryPressure() {
+ bool shouldLoseContext = mLoseContextOnMemoryPressure;
+
+ if (!mCanLoseContextInForeground &&
+ ProcessPriorityManager::CurrentProcessIsForeground()) {
+ shouldLoseContext = false;
+ }
+
+ if (shouldLoseContext) LoseContext();
+}
+
+// --
+
+bool WebGLContext::CreateAndInitGL(
+ bool forceEnabled, std::vector<FailureReason>* const out_failReasons) {
+ const FuncScope funcScope(*this, "<Create>");
+
+ // WebGL2 is separately blocked:
+ if (IsWebGL2() && !forceEnabled) {
+ FailureReason reason;
+ if (!gfx::gfxVars::AllowWebgl2()) {
+ reason.info =
+ "AllowWebgl2:false restricts context creation on this system.";
+ out_failReasons->push_back(reason);
+ GenerateWarning("%s", reason.info.BeginReading());
+ return false;
+ }
+ }
+
+ auto flags = gl::CreateContextFlags::PREFER_ROBUSTNESS;
+
+ if (StaticPrefs::webgl_gl_khr_no_error()) {
+ flags |= gl::CreateContextFlags::NO_VALIDATION;
+ }
+
+ // -
+
+ if (StaticPrefs::webgl_forbid_hardware()) {
+ flags |= gl::CreateContextFlags::FORBID_HARDWARE;
+ }
+ if (StaticPrefs::webgl_forbid_software()) {
+ flags |= gl::CreateContextFlags::FORBID_SOFTWARE;
+ }
+
+ if (forceEnabled) {
+ flags &= ~gl::CreateContextFlags::FORBID_HARDWARE;
+ flags &= ~gl::CreateContextFlags::FORBID_SOFTWARE;
+ }
+
+ if ((flags & gl::CreateContextFlags::FORBID_HARDWARE) &&
+ (flags & gl::CreateContextFlags::FORBID_SOFTWARE)) {
+ FailureReason reason;
+ reason.info = "Both hardware and software were forbidden by config.";
+ out_failReasons->push_back(reason);
+ GenerateWarning("%s", reason.info.BeginReading());
+ return false;
+ }
+
+ // -
+
+ if (StaticPrefs::webgl_cgl_multithreaded()) {
+ flags |= gl::CreateContextFlags::PREFER_MULTITHREADED;
+ }
+
+ if (IsWebGL2()) {
+ flags |= gl::CreateContextFlags::PREFER_ES3;
+ } else {
+ // Request and prefer ES2 context for WebGL1.
+ flags |= gl::CreateContextFlags::PREFER_EXACT_VERSION;
+
+ if (!StaticPrefs::webgl_1_allow_core_profiles()) {
+ flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
+ }
+ }
+
+ {
+ auto powerPref = mOptions.powerPreference;
+
+ // If "Use hardware acceleration when available" option is disabled:
+ if (!gfx::gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING)) {
+ powerPref = dom::WebGLPowerPreference::Low_power;
+ }
+
+ const auto overrideVal = StaticPrefs::webgl_power_preference_override();
+ if (overrideVal > 0) {
+ powerPref = dom::WebGLPowerPreference::High_performance;
+ } else if (overrideVal < 0) {
+ powerPref = dom::WebGLPowerPreference::Low_power;
+ }
+
+ if (powerPref == dom::WebGLPowerPreference::High_performance) {
+ flags |= gl::CreateContextFlags::HIGH_POWER;
+ }
+ }
+
+ if (!gfx::gfxVars::WebglAllowCoreProfile()) {
+ flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
+ }
+
+ // --
+
+ const bool useEGL = PR_GetEnv("MOZ_WEBGL_FORCE_EGL");
+
+ bool tryNativeGL = true;
+ bool tryANGLE = false;
+
+#ifdef XP_WIN
+ tryNativeGL = false;
+ tryANGLE = true;
+
+ if (StaticPrefs::webgl_disable_wgl()) {
+ tryNativeGL = false;
+ }
+
+ if (StaticPrefs::webgl_disable_angle() ||
+ PR_GetEnv("MOZ_WEBGL_FORCE_OPENGL") || useEGL) {
+ tryNativeGL = true;
+ tryANGLE = false;
+ }
+#endif
+
+ if (tryNativeGL && !forceEnabled) {
+ FailureReason reason;
+ if (!gfx::gfxVars::WebglAllowWindowsNativeGl()) {
+ reason.info =
+ "WebglAllowWindowsNativeGl:false restricts context creation on this "
+ "system.";
+
+ out_failReasons->push_back(reason);
+
+ GenerateWarning("%s", reason.info.BeginReading());
+ tryNativeGL = false;
+ }
+ }
+
+ // --
+
+ using fnCreateT = decltype(gl::GLContextProviderEGL::CreateHeadless);
+ const auto fnCreate = [&](fnCreateT* const pfnCreate,
+ const char* const info) {
+ nsCString failureId;
+ const RefPtr<gl::GLContext> gl = pfnCreate({flags}, &failureId);
+ if (!gl) {
+ out_failReasons->push_back(WebGLContext::FailureReason(failureId, info));
+ }
+ return gl;
+ };
+
+ const auto newGL = [&]() -> RefPtr<gl::GLContext> {
+ if (tryNativeGL) {
+ if (useEGL)
+ return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "useEGL");
+
+ const auto ret =
+ fnCreate(&gl::GLContextProvider::CreateHeadless, "tryNativeGL");
+ if (ret) return ret;
+ }
+
+ if (tryANGLE) {
+ return fnCreate(&gl::GLContextProviderEGL::CreateHeadless, "tryANGLE");
+ }
+ return nullptr;
+ }();
+
+ if (!newGL) {
+ out_failReasons->push_back(
+ FailureReason("FEATURE_FAILURE_WEBGL_EXHAUSTED_DRIVERS",
+ "Exhausted GL driver options."));
+ return false;
+ }
+
+ // --
+
+ FailureReason reason;
+
+ mGL_OnlyClearInDestroyResourcesAndContext = newGL;
+ MOZ_RELEASE_ASSERT(gl);
+ if (!InitAndValidateGL(&reason)) {
+ DestroyResourcesAndContext();
+ MOZ_RELEASE_ASSERT(!gl);
+
+ // The fail reason here should be specific enough for now.
+ out_failReasons->push_back(reason);
+ return false;
+ }
+
+ const auto val = StaticPrefs::webgl_debug_incomplete_tex_color();
+ if (val) {
+ mIncompleteTexOverride.reset(new gl::Texture(*gl));
+ const gl::ScopedBindTexture autoBind(gl, mIncompleteTexOverride->name);
+ const auto heapVal = std::make_unique<uint32_t>(val);
+ gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 1, 1, 0,
+ LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, heapVal.get());
+ }
+
+ return true;
+}
+
+// Fallback for resizes:
+
+bool WebGLContext::EnsureDefaultFB() {
+ if (mDefaultFB) {
+ MOZ_ASSERT(*uvec2::FromSize(mDefaultFB->mSize) == mRequestedSize);
+ return true;
+ }
+
+ const bool depthStencil = mOptions.depth || mOptions.stencil;
+ auto attemptSize = gfx::IntSize{mRequestedSize.x, mRequestedSize.y};
+
+ while (attemptSize.width || attemptSize.height) {
+ attemptSize.width = std::max(attemptSize.width, 1);
+ attemptSize.height = std::max(attemptSize.height, 1);
+
+ [&]() {
+ if (mOptions.antialias) {
+ MOZ_ASSERT(!mDefaultFB);
+ mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, mMsaaSamples,
+ depthStencil);
+ if (mDefaultFB) return;
+ if (mOptionsFrozen) return;
+ }
+
+ MOZ_ASSERT(!mDefaultFB);
+ mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, 0, depthStencil);
+ }();
+
+ if (mDefaultFB) break;
+
+ attemptSize.width /= 2;
+ attemptSize.height /= 2;
+ }
+
+ if (!mDefaultFB) {
+ GenerateWarning("Backbuffer resize failed. Losing context.");
+ LoseContext();
+ return false;
+ }
+
+ mDefaultFB_IsInvalid = true;
+
+ const auto actualSize = *uvec2::FromSize(mDefaultFB->mSize);
+ if (actualSize != mRequestedSize) {
+ GenerateWarning(
+ "Requested size %ux%u was too large, but resize"
+ " to %ux%u succeeded.",
+ mRequestedSize.x, mRequestedSize.y, actualSize.x, actualSize.y);
+ }
+ mRequestedSize = actualSize;
+ return true;
+}
+
+void WebGLContext::Resize(uvec2 requestedSize) {
+ // Zero-sized surfaces can cause problems.
+ if (!requestedSize.x) {
+ requestedSize.x = 1;
+ }
+ if (!requestedSize.y) {
+ requestedSize.y = 1;
+ }
+
+ // Kill our current default fb(s), for later lazy allocation.
+ mRequestedSize = requestedSize;
+ mDefaultFB = nullptr;
+ mResetLayer = true; // New size means new Layer.
+}
+
+UniquePtr<webgl::FormatUsageAuthority> WebGLContext::CreateFormatUsage(
+ gl::GLContext* gl) const {
+ return webgl::FormatUsageAuthority::CreateForWebGL1(gl);
+}
+
+/*static*/
+RefPtr<WebGLContext> WebGLContext::Create(HostWebGLContext& host,
+ const webgl::InitContextDesc& desc,
+ webgl::InitContextResult* const out) {
+ AUTO_PROFILER_LABEL("WebGLContext::Create", GRAPHICS);
+ nsCString failureId = "FEATURE_FAILURE_WEBGL_UNKOWN"_ns;
+ const bool forceEnabled = StaticPrefs::webgl_force_enabled();
+ ScopedGfxFeatureReporter reporter("WebGL", forceEnabled);
+
+ auto res = [&]() -> Result<RefPtr<WebGLContext>, std::string> {
+ bool disabled = StaticPrefs::webgl_disabled();
+
+ // TODO: When we have software webgl support we should use that instead.
+ disabled |= gfxPlatform::InSafeMode();
+
+ if (disabled) {
+ if (gfxPlatform::InSafeMode()) {
+ failureId = "FEATURE_FAILURE_WEBGL_SAFEMODE"_ns;
+ } else {
+ failureId = "FEATURE_FAILURE_WEBGL_DISABLED"_ns;
+ }
+ return Err("WebGL is currently disabled.");
+ }
+
+ // Alright, now let's start trying.
+
+ RefPtr<WebGLContext> webgl;
+ if (desc.isWebgl2) {
+ webgl = new WebGL2Context(host, desc);
+ } else {
+ webgl = new WebGLContext(host, desc);
+ }
+
+ MOZ_ASSERT(!webgl->gl);
+ std::vector<FailureReason> failReasons;
+ if (!webgl->CreateAndInitGL(forceEnabled, &failReasons)) {
+ nsCString text("WebGL creation failed: ");
+ for (const auto& cur : failReasons) {
+ // Don't try to accumulate using an empty key if |cur.key| is empty.
+ if (cur.key.IsEmpty()) {
+ Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID,
+ "FEATURE_FAILURE_REASON_UNKNOWN"_ns);
+ } else {
+ Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, cur.key);
+ }
+
+ const auto str = nsPrintfCString("\n* %s (%s)", cur.info.BeginReading(),
+ cur.key.BeginReading());
+ text.Append(str);
+ }
+ failureId = "FEATURE_FAILURE_REASON"_ns;
+ return Err(text.BeginReading());
+ }
+ MOZ_ASSERT(webgl->gl);
+
+ if (desc.options.failIfMajorPerformanceCaveat) {
+ if (webgl->gl->IsWARP()) {
+ failureId = "FEATURE_FAILURE_WEBGL_PERF_WARP"_ns;
+ return Err(
+ "failIfMajorPerformanceCaveat: Driver is not"
+ " hardware-accelerated.");
+ }
+
+#ifdef XP_WIN
+ if (webgl->gl->GetContextType() == gl::GLContextType::WGL &&
+ !gl::sWGLLib.HasDXInterop2()) {
+ failureId = "FEATURE_FAILURE_WEBGL_DXGL_INTEROP2"_ns;
+ return Err("failIfMajorPerformanceCaveat: WGL without DXGLInterop2.");
+ }
+#endif
+ }
+
+ const FuncScope funcScope(*webgl, "getContext/restoreContext");
+
+ MOZ_ASSERT(!webgl->mDefaultFB);
+ if (!webgl->EnsureDefaultFB()) {
+ MOZ_ASSERT(!webgl->mDefaultFB);
+ MOZ_ASSERT(webgl->IsContextLost());
+ failureId = "FEATURE_FAILURE_WEBGL_BACKBUFFER"_ns;
+ return Err("Initializing WebGL backbuffer failed.");
+ }
+
+ return webgl;
+ }();
+ if (res.isOk()) {
+ failureId = "SUCCESS"_ns;
+ }
+ Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, failureId);
+
+ if (!res.isOk()) {
+ out->error = res.unwrapErr();
+ return nullptr;
+ }
+ const auto webgl = res.unwrap();
+
+ // Update our internal stuff:
+ webgl->FinishInit();
+
+ reporter.SetSuccessful();
+ if (gl::GLContext::ShouldSpew()) {
+ printf_stderr("--- WebGL context created: %p\n", webgl.get());
+ }
+
+ // -
+
+ const auto UploadableSdTypes = [&]() {
+ webgl::EnumMask<layers::SurfaceDescriptor::Type> types;
+ types[layers::SurfaceDescriptor::TSurfaceDescriptorBuffer] = true;
+ if (webgl->gl->IsANGLE()) {
+ types[layers::SurfaceDescriptor::TSurfaceDescriptorD3D10] = true;
+ types[layers::SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr] = true;
+ }
+ if (kIsMacOS) {
+ types[layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface] = true;
+ }
+ if (kIsAndroid) {
+ types[layers::SurfaceDescriptor::TSurfaceTextureDescriptor] = true;
+ }
+ if (kIsX11 || kIsWayland) {
+ types[layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf] = true;
+ }
+ return types;
+ };
+
+ // -
+
+ out->options = webgl->mOptions;
+ out->limits = *webgl->mLimits;
+ out->uploadableSdTypes = UploadableSdTypes();
+ out->vendor = webgl->gl->Vendor();
+
+ return webgl;
+}
+
+void WebGLContext::FinishInit() {
+ mOptions.antialias &= bool(mDefaultFB->mSamples);
+
+ if (!mOptions.alpha) {
+ // We always have alpha.
+ mNeedsFakeNoAlpha = true;
+ }
+
+ if (mOptions.depth || mOptions.stencil) {
+ // We always have depth+stencil if we have either.
+ if (!mOptions.depth) {
+ mNeedsFakeNoDepth = true;
+ }
+ if (!mOptions.stencil) {
+ mNeedsFakeNoStencil = true;
+ }
+ }
+
+ mNeedsFakeNoStencil_UserFBs = false;
+#ifdef MOZ_WIDGET_COCOA
+ if (!nsCocoaFeatures::IsAtLeastVersion(10, 12) &&
+ gl->Vendor() == gl::GLVendor::Intel) {
+ mNeedsFakeNoStencil_UserFBs = true;
+ }
+#endif
+
+ mResetLayer = true;
+ mOptionsFrozen = true;
+
+ //////
+ // Initial setup.
+
+ gl->mImplicitMakeCurrent = true;
+ gl->mElideDuplicateBindFramebuffers = true;
+
+ const auto& size = mDefaultFB->mSize;
+
+ mViewportX = mViewportY = 0;
+ mViewportWidth = size.width;
+ mViewportHeight = size.height;
+ gl->fViewport(mViewportX, mViewportY, mViewportWidth, mViewportHeight);
+
+ mScissorRect = {0, 0, size.width, size.height};
+ mScissorRect.Apply(*gl);
+
+ //////
+ // Check everything
+
+ AssertCachedBindings();
+ AssertCachedGlobalState();
+
+ mShouldPresent = true;
+
+ //////
+
+ gl->ResetSyncCallCount("WebGLContext Initialization");
+ LoseLruContextIfLimitExceeded();
+}
+
+void WebGLContext::SetCompositableHost(
+ RefPtr<layers::CompositableHost>& aCompositableHost) {
+ mCompositableHost = aCompositableHost;
+}
+
+void WebGLContext::BumpLruLocked() {
+ if (!mIsContextLost && !mPendingContextLoss) {
+ mLruPosition.AssignLocked(*this);
+ } else {
+ MOZ_ASSERT(!mLruPosition.IsInsertedLocked());
+ }
+}
+
+void WebGLContext::BumpLru() {
+ StaticMutexAutoLock lock(sLruMutex);
+ BumpLruLocked();
+}
+
+void WebGLContext::LoseLruContextIfLimitExceeded() {
+ StaticMutexAutoLock lock(sLruMutex);
+
+ const auto maxContexts = std::max(1u, StaticPrefs::webgl_max_contexts());
+ const auto maxContextsPerPrincipal =
+ std::max(1u, StaticPrefs::webgl_max_contexts_per_principal());
+
+ // it's important to update the index on a new context before losing old
+ // contexts, otherwise new unused contexts would all have index 0 and we
+ // couldn't distinguish older ones when choosing which one to lose first.
+ BumpLruLocked();
+
+ {
+ size_t forPrincipal = 0;
+ for (const auto& context : sLru) {
+ if (context->mPrincipalKey == mPrincipalKey) {
+ forPrincipal += 1;
+ }
+ }
+
+ while (forPrincipal > maxContextsPerPrincipal) {
+ const auto text = nsPrintfCString(
+ "Exceeded %u live WebGL contexts for this principal, losing the "
+ "least recently used one.",
+ maxContextsPerPrincipal);
+ mHost->JsWarning(ToString(text));
+
+ for (const auto& context : sLru) {
+ if (context->mPrincipalKey == mPrincipalKey) {
+ MOZ_ASSERT(context != this);
+ context->LoseContextLruLocked(webgl::ContextLossReason::None);
+ forPrincipal -= 1;
+ break;
+ }
+ }
+ }
+ }
+
+ auto total = sLru.size();
+ while (total > maxContexts) {
+ const auto text = nsPrintfCString(
+ "Exceeded %u live WebGL contexts, losing the least "
+ "recently used one.",
+ maxContexts);
+ mHost->JsWarning(ToString(text));
+
+ const auto& context = sLru.front();
+ MOZ_ASSERT(context != this);
+ context->LoseContextLruLocked(webgl::ContextLossReason::None);
+ total -= 1;
+ }
+}
+
+// -
+
+namespace webgl {
+
+ScopedPrepForResourceClear::ScopedPrepForResourceClear(
+ const WebGLContext& webgl_)
+ : webgl(webgl_) {
+ const auto& gl = webgl.gl;
+
+ if (webgl.mScissorTestEnabled) {
+ gl->fDisable(LOCAL_GL_SCISSOR_TEST);
+ }
+ if (webgl.mRasterizerDiscardEnabled) {
+ gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD);
+ }
+
+ // "The clear operation always uses the front stencil write mask
+ // when clearing the stencil buffer."
+ webgl.DoColorMask(Some(0), 0b1111);
+ gl->fDepthMask(true);
+ gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff);
+
+ gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+ gl->fClearDepth(1.0f); // Depth formats are always cleared to 1.0f, not 0.0f.
+ gl->fClearStencil(0);
+}
+
+ScopedPrepForResourceClear::~ScopedPrepForResourceClear() {
+ const auto& gl = webgl.gl;
+
+ if (webgl.mScissorTestEnabled) {
+ gl->fEnable(LOCAL_GL_SCISSOR_TEST);
+ }
+ if (webgl.mRasterizerDiscardEnabled) {
+ gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
+ }
+
+ webgl.DoColorMask(Some(0), webgl.mColorWriteMask0);
+ gl->fDepthMask(webgl.mDepthWriteMask);
+ gl->fStencilMaskSeparate(LOCAL_GL_FRONT, webgl.mStencilWriteMaskFront);
+
+ gl->fClearColor(webgl.mColorClearValue[0], webgl.mColorClearValue[1],
+ webgl.mColorClearValue[2], webgl.mColorClearValue[3]);
+ gl->fClearDepth(webgl.mDepthClearValue);
+ gl->fClearStencil(webgl.mStencilClearValue);
+}
+
+} // namespace webgl
+
+// -
+
+void WebGLContext::OnEndOfFrame() {
+ if (StaticPrefs::webgl_perf_spew_frame_allocs()) {
+ GeneratePerfWarning("[webgl.perf.spew-frame-allocs] %" PRIu64
+ " data allocations this frame.",
+ mDataAllocGLCallCount);
+ }
+ mDataAllocGLCallCount = 0;
+ gl->ResetSyncCallCount("WebGLContext PresentScreenBuffer");
+
+ mDrawCallsSinceLastFlush = 0;
+
+ BumpLru();
+}
+
+void WebGLContext::BlitBackbufferToCurDriverFB(
+ WebGLFramebuffer* const srcAsWebglFb,
+ const gl::MozFramebuffer* const srcAsMozFb, bool srcIsBGRA) const {
+ // BlitFramebuffer ignores ColorMask().
+
+ if (mScissorTestEnabled) {
+ gl->fDisable(LOCAL_GL_SCISSOR_TEST);
+ }
+
+ [&]() {
+ // If a MozFramebuffer is supplied, ensure that a WebGLFramebuffer is not
+ // used since it might not have completeness info, while the MozFramebuffer
+ // can still supply the needed information.
+ MOZ_ASSERT(!(srcAsMozFb && srcAsWebglFb));
+ const auto* mozFb = srcAsMozFb ? srcAsMozFb : mDefaultFB.get();
+ GLuint fbo = 0;
+ gfx::IntSize size;
+ if (srcAsWebglFb) {
+ fbo = srcAsWebglFb->mGLName;
+ const auto* info = srcAsWebglFb->GetCompletenessInfo();
+ MOZ_ASSERT(info);
+ size = gfx::IntSize(info->width, info->height);
+ } else {
+ fbo = mozFb->mFB;
+ size = mozFb->mSize;
+ }
+
+ // If no format conversion is necessary, then attempt to directly blit
+ // between framebuffers. Otherwise, if we need to convert to RGBA from
+ // the source format, then we will need to use the texture blit path
+ // below.
+ if (!srcIsBGRA) {
+ if (gl->IsSupported(gl::GLFeature::framebuffer_blit)) {
+ gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
+ gl->fBlitFramebuffer(0, 0, size.width, size.height, 0, 0, size.width,
+ size.height, LOCAL_GL_COLOR_BUFFER_BIT,
+ LOCAL_GL_NEAREST);
+ return;
+ }
+ if (mDefaultFB->mSamples &&
+ gl->IsExtensionSupported(
+ gl::GLContext::APPLE_framebuffer_multisample)) {
+ gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbo);
+ gl->fResolveMultisampleFramebufferAPPLE();
+ return;
+ }
+ }
+
+ GLuint colorTex = 0;
+ if (srcAsWebglFb) {
+ const auto& attach = srcAsWebglFb->ColorAttachment0();
+ MOZ_ASSERT(attach.Texture());
+ colorTex = attach.Texture()->mGLName;
+ } else {
+ colorTex = mozFb->ColorTex();
+ }
+
+ // DrawBlit handles ColorMask itself.
+ gl->BlitHelper()->DrawBlitTextureToFramebuffer(
+ colorTex, size, size, LOCAL_GL_TEXTURE_2D, srcIsBGRA);
+ }();
+
+ if (mScissorTestEnabled) {
+ gl->fEnable(LOCAL_GL_SCISSOR_TEST);
+ }
+}
+
+// -
+
+template <typename T, typename... Args>
+constexpr auto MakeArray(Args... args) -> std::array<T, sizeof...(Args)> {
+ return {{static_cast<T>(args)...}};
+}
+
+inline gfx::ColorSpace2 ToColorSpace2(const WebGLContextOptions& options) {
+ auto ret = gfx::ColorSpace2::UNKNOWN;
+ if (true) {
+ ret = gfx::ColorSpace2::SRGB;
+ }
+ if (!options.ignoreColorSpace) {
+ ret = gfx::ToColorSpace2(options.colorSpace);
+ }
+ return ret;
+}
+
+// -
+
+// For an overview of how WebGL compositing works, see:
+// https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
+bool WebGLContext::PresentInto(gl::SwapChain& swapChain) {
+ OnEndOfFrame();
+
+ if (!ValidateAndInitFB(nullptr)) return false;
+
+ {
+ const auto colorSpace = ToColorSpace2(mOptions);
+ auto presenter = swapChain.Acquire(mDefaultFB->mSize, colorSpace);
+ if (!presenter) {
+ GenerateWarning("Swap chain surface creation failed.");
+ LoseContext();
+ return false;
+ }
+
+ const auto destFb = presenter->Fb();
+ gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
+
+ BlitBackbufferToCurDriverFB();
+
+ if (!mOptions.preserveDrawingBuffer) {
+ if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
+ gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB);
+ constexpr auto attachments = MakeArray<GLenum>(
+ LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
+ gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
+ attachments.size(), attachments.data());
+ }
+ mDefaultFB_IsInvalid = true;
+ }
+
+#ifdef DEBUG
+ if (!mOptions.alpha) {
+ gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
+ gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 4);
+ if (IsWebGL2()) {
+ gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, 0);
+ gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
+ gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
+ }
+ uint32_t pixel = 0xffbadbad;
+ gl->fReadPixels(0, 0, 1, 1, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
+ &pixel);
+ MOZ_ASSERT((pixel & 0xff000000) == 0xff000000);
+ }
+#endif
+ }
+
+ return true;
+}
+
+bool WebGLContext::PresentIntoXR(gl::SwapChain& swapChain,
+ const gl::MozFramebuffer& fb) {
+ OnEndOfFrame();
+
+ const auto colorSpace = ToColorSpace2(mOptions);
+ auto presenter = swapChain.Acquire(fb.mSize, colorSpace);
+ if (!presenter) {
+ GenerateWarning("Swap chain surface creation failed.");
+ LoseContext();
+ return false;
+ }
+
+ const auto destFb = presenter->Fb();
+ gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
+
+ BlitBackbufferToCurDriverFB(nullptr, &fb);
+
+ // https://immersive-web.github.io/webxr/#opaque-framebuffer
+ // Opaque framebuffers will always be cleared regardless of the
+ // associated WebGL context’s preserveDrawingBuffer value.
+ if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
+ gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb.mFB);
+ constexpr auto attachments = MakeArray<GLenum>(
+ LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
+ gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, attachments.size(),
+ attachments.data());
+ }
+
+ return true;
+}
+
+// Initialize a swap chain's surface factory given the desired surface type.
+void InitSwapChain(gl::GLContext& gl, gl::SwapChain& swapChain,
+ const layers::TextureType consumerType) {
+ if (!swapChain.mFactory) {
+ auto typedFactory = gl::SurfaceFactory::Create(&gl, consumerType);
+ if (typedFactory) {
+ swapChain.mFactory = std::move(typedFactory);
+ }
+ }
+ if (!swapChain.mFactory) {
+ NS_WARNING("Failed to make an ideal SurfaceFactory.");
+ swapChain.mFactory = MakeUnique<gl::SurfaceFactory_Basic>(gl);
+ }
+ MOZ_ASSERT(swapChain.mFactory);
+}
+
+void WebGLContext::Present(WebGLFramebuffer* const xrFb,
+ const layers::TextureType consumerType,
+ const bool webvr,
+ const webgl::SwapChainOptions& options) {
+ const FuncScope funcScope(*this, "<Present>");
+ if (IsContextLost()) return;
+
+ auto swapChain = GetSwapChain(xrFb, webvr);
+ const gl::MozFramebuffer* maybeFB = nullptr;
+ if (xrFb) {
+ maybeFB = xrFb->mOpaque.get();
+ } else {
+ mResolvedDefaultFB = nullptr;
+ }
+
+ InitSwapChain(*gl, *swapChain, consumerType);
+
+ bool valid =
+ maybeFB ? PresentIntoXR(*swapChain, *maybeFB) : PresentInto(*swapChain);
+ if (!valid) {
+ return;
+ }
+
+ bool useAsync = options.remoteTextureOwnerId.IsValid() &&
+ options.remoteTextureId.IsValid();
+ if (useAsync) {
+ PushRemoteTexture(nullptr, *swapChain, swapChain->FrontBuffer(), options);
+ }
+}
+
+void WebGLContext::CopyToSwapChain(WebGLFramebuffer* const srcFb,
+ const layers::TextureType consumerType,
+ const webgl::SwapChainOptions& options) {
+ const FuncScope funcScope(*this, "<CopyToSwapChain>");
+ if (IsContextLost()) return;
+
+ OnEndOfFrame();
+
+ if (!srcFb) return;
+ const auto* info = srcFb->GetCompletenessInfo();
+ if (!info) {
+ return;
+ }
+ gfx::IntSize size(info->width, info->height);
+
+ InitSwapChain(*gl, srcFb->mSwapChain, consumerType);
+
+ bool useAsync = options.remoteTextureOwnerId.IsValid() &&
+ options.remoteTextureId.IsValid();
+ // If we're using async present and if there is no way to serialize surfaces,
+ // then a readback is required to do the copy. In this case, there's no reason
+ // to copy into a separate shared surface for the front buffer. Just directly
+ // read back the WebGL framebuffer into and push it as a remote texture.
+ if (useAsync && srcFb->mSwapChain.mFactory->GetConsumerType() ==
+ layers::TextureType::Unknown) {
+ PushRemoteTexture(srcFb, srcFb->mSwapChain, nullptr, options);
+ return;
+ }
+
+ {
+ // ColorSpace will need to be part of SwapChainOptions for DTWebgl.
+ const auto colorSpace = ToColorSpace2(mOptions);
+ auto presenter = srcFb->mSwapChain.Acquire(size, colorSpace);
+ if (!presenter) {
+ GenerateWarning("Swap chain surface creation failed.");
+ LoseContext();
+ return;
+ }
+
+ const ScopedFBRebinder saveFB(this);
+
+ const auto destFb = presenter->Fb();
+ gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
+
+ BlitBackbufferToCurDriverFB(srcFb, nullptr, options.bgra);
+ }
+
+ if (useAsync) {
+ PushRemoteTexture(srcFb, srcFb->mSwapChain, srcFb->mSwapChain.FrontBuffer(),
+ options);
+ }
+}
+
+bool WebGLContext::PushRemoteTexture(WebGLFramebuffer* fb,
+ gl::SwapChain& swapChain,
+ std::shared_ptr<gl::SharedSurface> surf,
+ const webgl::SwapChainOptions& options) {
+ const auto onFailure = [&]() -> bool {
+ GenerateWarning("Remote texture creation failed.");
+ LoseContext();
+ if (mRemoteTextureOwner) {
+ mRemoteTextureOwner->PushDummyTexture(options.remoteTextureId,
+ options.remoteTextureOwnerId);
+ }
+ return false;
+ };
+
+ if (!mRemoteTextureOwner) {
+ // Ensure we have a remote texture owner client for WebGLParent.
+ const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr;
+ if (!outOfProcess) {
+ return onFailure();
+ }
+ mRemoteTextureOwner =
+ MakeRefPtr<layers::RemoteTextureOwnerClient>(outOfProcess->OtherPid());
+ }
+
+ layers::RemoteTextureOwnerId ownerId = options.remoteTextureOwnerId;
+ layers::RemoteTextureId textureId = options.remoteTextureId;
+
+ if (!mRemoteTextureOwner->IsRegistered(ownerId)) {
+ // Register a texture owner to represent the swap chain.
+ RefPtr<layers::RemoteTextureOwnerClient> textureOwner = mRemoteTextureOwner;
+ auto destroyedCallback = [textureOwner, ownerId]() {
+ textureOwner->UnregisterTextureOwner(ownerId);
+ };
+
+ swapChain.SetDestroyedCallback(destroyedCallback);
+ mRemoteTextureOwner->RegisterTextureOwner(
+ ownerId,
+ /* aIsSyncMode */ gfx::gfxVars::WebglOopAsyncPresentForceSync());
+ }
+
+ MOZ_ASSERT(fb || surf);
+ gfx::IntSize size;
+ if (surf) {
+ size = surf->mDesc.size;
+ } else {
+ const auto* info = fb->GetCompletenessInfo();
+ MOZ_ASSERT(info);
+ size = gfx::IntSize(info->width, info->height);
+ }
+
+ const auto surfaceFormat = mOptions.alpha ? gfx::SurfaceFormat::B8G8R8A8
+ : gfx::SurfaceFormat::B8G8R8X8;
+ Maybe<layers::SurfaceDescriptor> desc;
+ if (surf) {
+ desc = surf->ToSurfaceDescriptor();
+ }
+ if (!desc) {
+ if (surf && surf->mDesc.type != gl::SharedSurfaceType::Basic) {
+ return onFailure();
+ }
+ // If we can't serialize to a surface descriptor, then we need to create
+ // a buffer to read back into that will become the remote texture.
+ auto data = mRemoteTextureOwner->CreateOrRecycleBufferTextureData(
+ ownerId, size, surfaceFormat);
+ if (!data) {
+ gfxCriticalNoteOnce << "Failed to allocate BufferTextureData";
+ return onFailure();
+ }
+
+ layers::MappedTextureData mappedData;
+ if (!data->BorrowMappedData(mappedData)) {
+ return onFailure();
+ }
+
+ Range<uint8_t> range = {mappedData.data,
+ data->AsBufferTextureData()->GetBufferSize()};
+
+ // If we have a surface representing the front buffer, then try to snapshot
+ // that. Otherwise, when there is no surface, we read back directly from the
+ // WebGL framebuffer.
+ auto valid =
+ surf ? FrontBufferSnapshotInto(surf, Some(range),
+ Some(mappedData.stride))
+ : SnapshotInto(fb->mGLName, size, range, Some(mappedData.stride));
+ if (!valid) {
+ return onFailure();
+ }
+
+ if (!options.bgra) {
+ // If the buffer is already BGRA, we don't need to swizzle. However, if it
+ // is RGBA, then a swizzle to BGRA is required.
+ bool rv = gfx::SwizzleData(mappedData.data, mappedData.stride,
+ gfx::SurfaceFormat::R8G8B8A8, mappedData.data,
+ mappedData.stride,
+ gfx::SurfaceFormat::B8G8R8A8, mappedData.size);
+ MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
+ }
+
+ mRemoteTextureOwner->PushTexture(textureId, ownerId, std::move(data),
+ /* aSharedSurface */ nullptr);
+ return true;
+ }
+
+ // SharedSurfaces of SurfaceDescriptorD3D10 and SurfaceDescriptorMacIOSurface
+ // need to be kept alive. They will be recycled by
+ // RemoteTextureOwnerClient::GetRecycledSharedSurface() when their usages are
+ // ended.
+ std::shared_ptr<gl::SharedSurface> keepAlive;
+ switch (desc->type()) {
+ case layers::SurfaceDescriptor::TSurfaceDescriptorD3D10:
+ case layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface:
+ case layers::SurfaceDescriptor::TSurfaceTextureDescriptor:
+ case layers::SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer:
+ keepAlive = surf;
+ break;
+ default:
+ break;
+ }
+
+ auto data =
+ MakeUnique<layers::SharedSurfaceTextureData>(*desc, surfaceFormat, size);
+ mRemoteTextureOwner->PushTexture(textureId, ownerId, std::move(data),
+ keepAlive);
+ auto recycledSurface = mRemoteTextureOwner->GetRecycledSharedSurface(ownerId);
+ if (recycledSurface) {
+ swapChain.StoreRecycledSurface(recycledSurface);
+ }
+ return true;
+}
+
+void WebGLContext::EndOfFrame() {
+ const FuncScope funcScope(*this, "<EndOfFrame>");
+ if (IsContextLost()) return;
+
+ OnEndOfFrame();
+}
+
+gl::SwapChain* WebGLContext::GetSwapChain(WebGLFramebuffer* const xrFb,
+ const bool webvr) {
+ auto swapChain = webvr ? &mWebVRSwapChain : &mSwapChain;
+ if (xrFb) {
+ swapChain = &xrFb->mSwapChain;
+ }
+ return swapChain;
+}
+
+Maybe<layers::SurfaceDescriptor> WebGLContext::GetFrontBuffer(
+ WebGLFramebuffer* const xrFb, const bool webvr) {
+ auto* swapChain = GetSwapChain(xrFb, webvr);
+ if (!swapChain) return {};
+ const auto& front = swapChain->FrontBuffer();
+ if (!front) return {};
+
+ return front->ToSurfaceDescriptor();
+}
+
+Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
+ const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
+ const auto& front = mSwapChain.FrontBuffer();
+ if (!front) return {};
+ return FrontBufferSnapshotInto(front, maybeDest, destStride);
+}
+
+Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
+ const std::shared_ptr<gl::SharedSurface>& front,
+ const Maybe<Range<uint8_t>> maybeDest, const Maybe<size_t> destStride) {
+ const auto& size = front->mDesc.size;
+ if (!maybeDest) return Some(*uvec2::FromSize(size));
+
+ // -
+
+ front->WaitForBufferOwnership();
+ front->LockProd();
+ front->ProducerReadAcquire();
+ auto reset = MakeScopeExit([&] {
+ front->ProducerReadRelease();
+ front->UnlockProd();
+ });
+
+ // -
+
+ return SnapshotInto(front->mFb ? front->mFb->mFB : 0, size, *maybeDest,
+ destStride);
+}
+
+Maybe<uvec2> WebGLContext::SnapshotInto(GLuint srcFb, const gfx::IntSize& size,
+ const Range<uint8_t>& dest,
+ const Maybe<size_t> destStride) {
+ const auto minStride = CheckedInt<size_t>(size.width) * 4;
+ if (!minStride.isValid()) {
+ gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width;
+ return {};
+ }
+ size_t stride = destStride.valueOr(minStride.value());
+ if (stride < minStride.value() || (stride % 4) != 0) {
+ gfxCriticalError() << "SnapshotInto: invalid stride, width:" << size.width
+ << ", stride:" << stride;
+ return {};
+ }
+
+ gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
+ if (IsWebGL2()) {
+ gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH,
+ stride > minStride.value() ? stride / 4 : 0);
+ gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
+ gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
+ }
+
+ // -
+
+ const auto readFbWas = mBoundReadFramebuffer;
+ const auto pboWas = mBoundPixelPackBuffer;
+
+ GLenum fbTarget = LOCAL_GL_READ_FRAMEBUFFER;
+ if (!IsWebGL2()) {
+ fbTarget = LOCAL_GL_FRAMEBUFFER;
+ }
+ auto reset2 = MakeScopeExit([&] {
+ DoBindFB(readFbWas, fbTarget);
+ if (pboWas) {
+ BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
+ }
+ });
+
+ gl->fBindFramebuffer(fbTarget, srcFb);
+ if (pboWas) {
+ BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
+ }
+
+ // -
+
+ const auto srcByteCount = CheckedInt<size_t>(stride) * size.height;
+ if (!srcByteCount.isValid()) {
+ gfxCriticalError() << "SnapshotInto: invalid srcByteCount, width:"
+ << size.width << ", height:" << size.height;
+ return {};
+ }
+ const auto dstByteCount = dest.length();
+ if (srcByteCount.value() > dstByteCount) {
+ gfxCriticalError() << "SnapshotInto: srcByteCount:" << srcByteCount.value()
+ << " > dstByteCount:" << dstByteCount;
+ return {};
+ }
+ uint8_t* dstPtr = dest.begin().get();
+ gl->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA,
+ LOCAL_GL_UNSIGNED_BYTE, dstPtr);
+
+ if (!IsWebGL2() && stride > minStride.value() && size.height > 1) {
+ // WebGL 1 doesn't support PACK_ROW_LENGTH. Instead, we read the data tight
+ // into the front of the buffer, and use memmove (since the source and dest
+ // may overlap) starting from the back to move it to the correct stride
+ // offsets. We don't move the first row as it is already in the right place.
+ uint8_t* destRow = dstPtr + stride * (size.height - 1);
+ const uint8_t* srcRow = dstPtr + minStride.value() * (size.height - 1);
+ while (destRow > dstPtr) {
+ memmove(destRow, srcRow, minStride.value());
+ destRow -= stride;
+ srcRow -= minStride.value();
+ }
+ }
+
+ return Some(*uvec2::FromSize(size));
+}
+
+void WebGLContext::ClearVRSwapChain() { mWebVRSwapChain.ClearPool(); }
+
+// ------------------------
+
+RefPtr<gfx::DataSourceSurface> GetTempSurface(const gfx::IntSize& aSize,
+ gfx::SurfaceFormat& aFormat) {
+ uint32_t stride =
+ gfx::GetAlignedStride<8>(aSize.width, BytesPerPixel(aFormat));
+ return gfx::Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat,
+ stride);
+}
+
+void WebGLContext::DummyReadFramebufferOperation() {
+ if (!mBoundReadFramebuffer) return; // Infallible.
+
+ const auto status = mBoundReadFramebuffer->CheckFramebufferStatus();
+ if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
+ ErrorInvalidFramebufferOperation("Framebuffer must be complete.");
+ }
+}
+
+bool WebGLContext::Has64BitTimestamps() const {
+ // 'sync' provides glGetInteger64v either by supporting ARB_sync, GL3+, or
+ // GLES3+.
+ return gl->IsSupported(gl::GLFeature::sync);
+}
+
+static bool CheckContextLost(gl::GLContext* gl, bool* const out_isGuilty) {
+ MOZ_ASSERT(gl);
+
+ const auto resetStatus = gl->fGetGraphicsResetStatus();
+ if (resetStatus == LOCAL_GL_NO_ERROR) {
+ *out_isGuilty = false;
+ return false;
+ }
+
+ // Assume guilty unless we find otherwise!
+ bool isGuilty = true;
+ switch (resetStatus) {
+ case LOCAL_GL_INNOCENT_CONTEXT_RESET_ARB:
+ case LOCAL_GL_PURGED_CONTEXT_RESET_NV:
+ // Either nothing wrong, or not our fault.
+ isGuilty = false;
+ break;
+ case LOCAL_GL_GUILTY_CONTEXT_RESET_ARB:
+ NS_WARNING(
+ "WebGL content on the page definitely caused the graphics"
+ " card to reset.");
+ break;
+ case LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB:
+ NS_WARNING(
+ "WebGL content on the page might have caused the graphics"
+ " card to reset");
+ // If we can't tell, assume not-guilty.
+ // Todo: Implement max number of "unknown" resets per document or time.
+ isGuilty = false;
+ break;
+ default:
+ gfxCriticalError() << "Unexpected glGetGraphicsResetStatus: "
+ << gfx::hexa(resetStatus);
+ break;
+ }
+
+ if (isGuilty) {
+ NS_WARNING(
+ "WebGL context on this page is considered guilty, and will"
+ " not be restored.");
+ }
+
+ *out_isGuilty = isGuilty;
+ return true;
+}
+
+void WebGLContext::RunContextLossTimer() { mContextLossHandler.RunTimer(); }
+
+// We use this timer for many things. Here are the things that it is activated
+// for:
+// 1) If a script is using the MOZ_WEBGL_lose_context extension.
+// 2) If we are using EGL and _NOT ANGLE_, we query periodically to see if the
+// CONTEXT_LOST_WEBGL error has been triggered.
+// 3) If we are using ANGLE, or anything that supports ARB_robustness, query the
+// GPU periodically to see if the reset status bit has been set.
+// In all of these situations, we use this timer to send the script context lost
+// and restored events asynchronously. For example, if it triggers a context
+// loss, the webglcontextlost event will be sent to it the next time the
+// robustness timer fires.
+// Note that this timer mechanism is not used unless one of these 3 criteria are
+// met.
+// At a bare minimum, from context lost to context restores, it would take 3
+// full timer iterations: detection, webglcontextlost, webglcontextrestored.
+void WebGLContext::CheckForContextLoss() {
+ bool isGuilty = true;
+ const auto isContextLost = CheckContextLost(gl, &isGuilty);
+ if (!isContextLost) return;
+
+ mWebGLError = LOCAL_GL_CONTEXT_LOST;
+
+ auto reason = webgl::ContextLossReason::None;
+ if (isGuilty) {
+ reason = webgl::ContextLossReason::Guilty;
+ }
+ LoseContext(reason);
+}
+
+void WebGLContext::HandlePendingContextLoss() {
+ mIsContextLost = true;
+ mHost->OnContextLoss(mPendingContextLossReason);
+}
+
+void WebGLContext::LoseContextLruLocked(const webgl::ContextLossReason reason) {
+ printf_stderr("WebGL(%p)::LoseContext(%u)\n", this,
+ static_cast<uint32_t>(reason));
+ mLruPosition.ResetLocked();
+ mPendingContextLossReason = reason;
+ mPendingContextLoss = true;
+}
+
+void WebGLContext::LoseContext(const webgl::ContextLossReason reason) {
+ StaticMutexAutoLock lock(sLruMutex);
+ LoseContextLruLocked(reason);
+ HandlePendingContextLoss();
+}
+
+void WebGLContext::DidRefresh() {
+ if (gl) {
+ gl->FlushIfHeavyGLCallsSinceLastFlush();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+uvec2 WebGLContext::DrawingBufferSize() {
+ const FuncScope funcScope(*this, "width/height");
+ if (IsContextLost()) return {};
+
+ if (!EnsureDefaultFB()) return {};
+
+ return *uvec2::FromSize(mDefaultFB->mSize);
+}
+
+bool WebGLContext::ValidateAndInitFB(const WebGLFramebuffer* const fb,
+ const GLenum incompleteFbError) {
+ if (fb) return fb->ValidateAndInitAttachments(incompleteFbError);
+
+ if (!EnsureDefaultFB()) return false;
+
+ if (mDefaultFB_IsInvalid) {
+ // Clear it!
+ gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
+ const webgl::ScopedPrepForResourceClear scopedPrep(*this);
+ if (!mOptions.alpha) {
+ gl->fClearColor(0, 0, 0, 1);
+ }
+ const GLbitfield bits = LOCAL_GL_COLOR_BUFFER_BIT |
+ LOCAL_GL_DEPTH_BUFFER_BIT |
+ LOCAL_GL_STENCIL_BUFFER_BIT;
+ gl->fClear(bits);
+
+ mDefaultFB_IsInvalid = false;
+ }
+ return true;
+}
+
+void WebGLContext::DoBindFB(const WebGLFramebuffer* const fb,
+ const GLenum target) const {
+ const GLenum driverFB = fb ? fb->mGLName : mDefaultFB->mFB;
+ gl->fBindFramebuffer(target, driverFB);
+}
+
+bool WebGLContext::BindCurFBForDraw() {
+ const auto& fb = mBoundDrawFramebuffer;
+ if (!ValidateAndInitFB(fb)) return false;
+
+ DoBindFB(fb);
+ return true;
+}
+
+bool WebGLContext::BindCurFBForColorRead(
+ const webgl::FormatUsageInfo** const out_format, uint32_t* const out_width,
+ uint32_t* const out_height, const GLenum incompleteFbError) {
+ const auto& fb = mBoundReadFramebuffer;
+
+ if (fb) {
+ if (!ValidateAndInitFB(fb, incompleteFbError)) return false;
+ if (!fb->ValidateForColorRead(out_format, out_width, out_height))
+ return false;
+
+ gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb->mGLName);
+ return true;
+ }
+
+ if (!BindDefaultFBForRead()) return false;
+
+ if (mDefaultFB_ReadBuffer == LOCAL_GL_NONE) {
+ ErrorInvalidOperation(
+ "Can't read from backbuffer when readBuffer mode is NONE.");
+ return false;
+ }
+
+ auto effFormat = mOptions.alpha ? webgl::EffectiveFormat::RGBA8
+ : webgl::EffectiveFormat::RGB8;
+
+ *out_format = mFormatUsage->GetUsage(effFormat);
+ MOZ_ASSERT(*out_format);
+
+ *out_width = mDefaultFB->mSize.width;
+ *out_height = mDefaultFB->mSize.height;
+ return true;
+}
+
+bool WebGLContext::BindDefaultFBForRead() {
+ if (!ValidateAndInitFB(nullptr)) return false;
+
+ if (!mDefaultFB->mSamples) {
+ gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
+ return true;
+ }
+
+ if (!mResolvedDefaultFB) {
+ mResolvedDefaultFB =
+ gl::MozFramebuffer::Create(gl, mDefaultFB->mSize, 0, false);
+ if (!mResolvedDefaultFB) {
+ gfxCriticalNote << FuncName() << ": Failed to create mResolvedDefaultFB.";
+ return false;
+ }
+ }
+
+ gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
+ BlitBackbufferToCurDriverFB();
+
+ gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mResolvedDefaultFB->mFB);
+ return true;
+}
+
+void WebGLContext::DoColorMask(Maybe<GLuint> i, const uint8_t bitmask) const {
+ if (!IsExtensionEnabled(WebGLExtensionID::OES_draw_buffers_indexed)) {
+ i = Nothing();
+ }
+ const auto bs = std::bitset<4>(bitmask);
+ if (i) {
+ gl->fColorMaski(*i, bs[0], bs[1], bs[2], bs[3]);
+ } else {
+ gl->fColorMask(bs[0], bs[1], bs[2], bs[3]);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+ScopedDrawCallWrapper::ScopedDrawCallWrapper(WebGLContext& webgl)
+ : mWebGL(webgl) {
+ uint8_t driverColorMask0 = mWebGL.mColorWriteMask0;
+ bool driverDepthTest = mWebGL.mDepthTestEnabled;
+ bool driverStencilTest = mWebGL.mStencilTestEnabled;
+ const auto& fb = mWebGL.mBoundDrawFramebuffer;
+ if (!fb) {
+ if (mWebGL.mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE) {
+ driverColorMask0 = 0; // Is this well-optimized enough for depth-first
+ // rendering?
+ } else {
+ driverColorMask0 &= ~(uint8_t(mWebGL.mNeedsFakeNoAlpha) << 3);
+ }
+ driverDepthTest &= !mWebGL.mNeedsFakeNoDepth;
+ driverStencilTest &= !mWebGL.mNeedsFakeNoStencil;
+ } else {
+ if (mWebGL.mNeedsFakeNoStencil_UserFBs &&
+ fb->DepthAttachment().HasAttachment() &&
+ !fb->StencilAttachment().HasAttachment()) {
+ driverStencilTest = false;
+ }
+ }
+
+ const auto& gl = mWebGL.gl;
+ mWebGL.DoColorMask(Some(0), driverColorMask0);
+ if (mWebGL.mDriverDepthTest != driverDepthTest) {
+ // "When disabled, the depth comparison and subsequent possible updates to
+ // the
+ // depth buffer value are bypassed and the fragment is passed to the next
+ // operation." [GLES 3.0.5, p177]
+ mWebGL.mDriverDepthTest = driverDepthTest;
+ gl->SetEnabled(LOCAL_GL_DEPTH_TEST, mWebGL.mDriverDepthTest);
+ }
+ if (mWebGL.mDriverStencilTest != driverStencilTest) {
+ // "When disabled, the stencil test and associated modifications are not
+ // made, and
+ // the fragment is always passed." [GLES 3.0.5, p175]
+ mWebGL.mDriverStencilTest = driverStencilTest;
+ gl->SetEnabled(LOCAL_GL_STENCIL_TEST, mWebGL.mDriverStencilTest);
+ }
+}
+
+ScopedDrawCallWrapper::~ScopedDrawCallWrapper() {
+ if (mWebGL.mBoundDrawFramebuffer) return;
+
+ mWebGL.mResolvedDefaultFB = nullptr;
+ mWebGL.mShouldPresent = true;
+}
+
+// -
+
+void WebGLContext::ScissorRect::Apply(gl::GLContext& gl) const {
+ gl.fScissor(x, y, w, h);
+}
+
+////////////////////////////////////////
+
+IndexedBufferBinding::IndexedBufferBinding() : mRangeStart(0), mRangeSize(0) {}
+
+uint64_t IndexedBufferBinding::ByteCount() const {
+ if (!mBufferBinding) return 0;
+
+ uint64_t bufferSize = mBufferBinding->ByteLength();
+ if (!mRangeSize) // BindBufferBase
+ return bufferSize;
+
+ if (mRangeStart >= bufferSize) return 0;
+ bufferSize -= mRangeStart;
+
+ return std::min(bufferSize, mRangeSize);
+}
+
+////////////////////////////////////////
+
+ScopedFBRebinder::~ScopedFBRebinder() {
+ const auto fnName = [&](WebGLFramebuffer* fb) {
+ return fb ? fb->mGLName : 0;
+ };
+
+ const auto& gl = mWebGL->gl;
+ if (mWebGL->IsWebGL2()) {
+ gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
+ fnName(mWebGL->mBoundDrawFramebuffer));
+ gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
+ fnName(mWebGL->mBoundReadFramebuffer));
+ } else {
+ MOZ_ASSERT(mWebGL->mBoundDrawFramebuffer == mWebGL->mBoundReadFramebuffer);
+ gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER,
+ fnName(mWebGL->mBoundDrawFramebuffer));
+ }
+}
+
+////////////////////
+
+void DoBindBuffer(gl::GLContext& gl, const GLenum target,
+ const WebGLBuffer* const buffer) {
+ gl.fBindBuffer(target, buffer ? buffer->mGLName : 0);
+}
+
+////////////////////////////////////////
+
+bool Intersect(const int32_t srcSize, const int32_t read0,
+ const int32_t readSize, int32_t* const out_intRead0,
+ int32_t* const out_intWrite0, int32_t* const out_intSize) {
+ MOZ_ASSERT(srcSize >= 0);
+ MOZ_ASSERT(readSize >= 0);
+ const auto read1 = int64_t(read0) + readSize;
+
+ int32_t intRead0 = read0; // Clearly doesn't need validation.
+ int64_t intWrite0 = 0;
+ int64_t intSize = readSize;
+
+ if (read1 <= 0 || read0 >= srcSize) {
+ // Disjoint ranges.
+ intSize = 0;
+ } else {
+ if (read0 < 0) {
+ const auto diff = int64_t(0) - read0;
+ MOZ_ASSERT(diff >= 0);
+ intRead0 = 0;
+ intWrite0 = diff;
+ intSize -= diff;
+ }
+ if (read1 > srcSize) {
+ const auto diff = int64_t(read1) - srcSize;
+ MOZ_ASSERT(diff >= 0);
+ intSize -= diff;
+ }
+
+ if (!CheckedInt<int32_t>(intWrite0).isValid() ||
+ !CheckedInt<int32_t>(intSize).isValid()) {
+ return false;
+ }
+ }
+
+ *out_intRead0 = intRead0;
+ *out_intWrite0 = intWrite0;
+ *out_intSize = intSize;
+ return true;
+}
+
+// --
+
+uint64_t AvailGroups(const uint64_t totalAvailItems,
+ const uint64_t firstItemOffset, const uint32_t groupSize,
+ const uint32_t groupStride) {
+ MOZ_ASSERT(groupSize && groupStride);
+ MOZ_ASSERT(groupSize <= groupStride);
+
+ if (totalAvailItems <= firstItemOffset) return 0;
+ const size_t availItems = totalAvailItems - firstItemOffset;
+
+ size_t availGroups = availItems / groupStride;
+ const size_t tailItems = availItems % groupStride;
+ if (tailItems >= groupSize) {
+ availGroups += 1;
+ }
+ return availGroups;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const char* WebGLContext::FuncName() const {
+ const char* ret;
+ if (MOZ_LIKELY(mFuncScope)) {
+ ret = mFuncScope->mFuncName;
+ } else {
+ NS_WARNING("FuncScope not on stack!");
+ ret = "<unknown function>";
+ }
+ return ret;
+}
+
+// -
+
+WebGLContext::FuncScope::FuncScope(const WebGLContext& webgl,
+ const char* const funcName)
+ : mWebGL(webgl), mFuncName(bool(mWebGL.mFuncScope) ? nullptr : funcName) {
+ if (!mFuncName) return;
+ mWebGL.mFuncScope = this;
+}
+
+WebGLContext::FuncScope::~FuncScope() {
+ if (mBindFailureGuard) {
+ gfxCriticalError() << "mBindFailureGuard failure: Early exit from "
+ << mWebGL.FuncName();
+ }
+
+ if (!mFuncName) return;
+ mWebGL.mFuncScope = nullptr;
+}
+
+// --
+
+bool ClientWebGLContext::IsXRCompatible() const { return mXRCompatible; }
+
+already_AddRefed<dom::Promise> ClientWebGLContext::MakeXRCompatible(
+ ErrorResult& aRv) {
+ const FuncScope funcScope(*this, "MakeXRCompatible");
+ nsCOMPtr<nsIGlobalObject> global = GetParentObject();
+ if (!global) {
+ aRv.ThrowInvalidAccessError(
+ "Using a WebGL context that is not attached to either a canvas or an "
+ "OffscreenCanvas");
+ return nullptr;
+ }
+ RefPtr<dom::Promise> promise = dom::Promise::Create(global, aRv);
+ NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+ if (IsContextLost()) {
+ promise->MaybeRejectWithInvalidStateError(
+ "Can not make context XR compatible when context is already lost.");
+ return promise.forget();
+ }
+
+ // TODO: Bug 1580258 - WebGLContext.MakeXRCompatible needs to switch to
+ // the device connected to the XR hardware
+ // This should update `options` and lose+restore the context.
+ mXRCompatible = true;
+ promise->MaybeResolveWithUndefined();
+ return promise.forget();
+}
+
+// --
+
+webgl::AvailabilityRunnable& ClientWebGLContext::EnsureAvailabilityRunnable()
+ const {
+ if (!mAvailabilityRunnable) {
+ mAvailabilityRunnable = new webgl::AvailabilityRunnable(this);
+ auto forgettable = mAvailabilityRunnable;
+ NS_DispatchToCurrentThread(forgettable.forget());
+ }
+ return *mAvailabilityRunnable;
+}
+
+webgl::AvailabilityRunnable::AvailabilityRunnable(
+ const ClientWebGLContext* const webgl)
+ : DiscardableRunnable("webgl::AvailabilityRunnable"), mWebGL(webgl) {}
+
+webgl::AvailabilityRunnable::~AvailabilityRunnable() {
+ MOZ_ASSERT(mQueries.empty());
+ MOZ_ASSERT(mSyncs.empty());
+}
+
+nsresult webgl::AvailabilityRunnable::Run() {
+ for (const auto& cur : mQueries) {
+ if (!cur) continue;
+ cur->mCanBeAvailable = true;
+ }
+ mQueries.clear();
+
+ for (const auto& cur : mSyncs) {
+ if (!cur) continue;
+ cur->mCanBeAvailable = true;
+ }
+ mSyncs.clear();
+
+ if (mWebGL) {
+ mWebGL->mAvailabilityRunnable = nullptr;
+ }
+ return NS_OK;
+}
+
+// -
+
+void WebGLContext::GenerateErrorImpl(const GLenum errOrWarning,
+ const std::string& text) const {
+ auto err = errOrWarning;
+ bool isPerfWarning = false;
+ if (err == webgl::kErrorPerfWarning) {
+ err = 0;
+ isPerfWarning = true;
+ }
+
+ if (err && mFuncScope && mFuncScope->mBindFailureGuard) {
+ gfxCriticalError() << "mBindFailureGuard failure: Generating error "
+ << EnumString(err) << ": " << text;
+ }
+
+ /* ES2 section 2.5 "GL Errors" states that implementations can have
+ * multiple 'flags', as errors might be caught in different parts of
+ * a distributed implementation.
+ * We're signing up as a distributed implementation here, with
+ * separate flags for WebGL and the underlying GLContext.
+ */
+ if (!mWebGLError) mWebGLError = err;
+
+ if (!mHost) return; // Impossible?
+
+ // -
+
+ const auto ShouldWarn = [&]() {
+ if (isPerfWarning) {
+ return ShouldGeneratePerfWarnings();
+ }
+ return ShouldGenerateWarnings();
+ };
+ if (!ShouldWarn()) return;
+
+ // -
+
+ auto* pNumWarnings = &mWarningCount;
+ const char* warningsType = "warnings";
+ if (isPerfWarning) {
+ pNumWarnings = &mNumPerfWarnings;
+ warningsType = "perf warnings";
+ }
+
+ if (isPerfWarning) {
+ const auto perfText = std::string("WebGL perf warning: ") + text;
+ mHost->JsWarning(perfText);
+ } else {
+ mHost->JsWarning(text);
+ }
+ *pNumWarnings += 1;
+
+ if (!ShouldWarn()) {
+ const auto& msg = nsPrintfCString(
+ "After reporting %i, no further %s will be reported for this WebGL "
+ "context.",
+ int(*pNumWarnings), warningsType);
+ mHost->JsWarning(ToString(msg));
+ }
+}
+
+// -
+
+Maybe<std::string> WebGLContext::GetString(const GLenum pname) const {
+ const WebGLContext::FuncScope funcScope(*this, "getParameter");
+ if (IsContextLost()) return {};
+
+ const auto FromRaw = [](const char* const raw) -> Maybe<std::string> {
+ if (!raw) return {};
+ return Some(std::string(raw));
+ };
+
+ switch (pname) {
+ case LOCAL_GL_EXTENSIONS: {
+ if (!gl->IsCoreProfile()) {
+ const auto rawExt = (const char*)gl->fGetString(LOCAL_GL_EXTENSIONS);
+ return FromRaw(rawExt);
+ }
+ std::string ret;
+ const auto& numExts = gl->GetIntAs<GLuint>(LOCAL_GL_NUM_EXTENSIONS);
+ for (GLuint i = 0; i < numExts; i++) {
+ const auto rawExt =
+ (const char*)gl->fGetStringi(LOCAL_GL_EXTENSIONS, i);
+ if (!rawExt) continue;
+
+ if (i > 0) {
+ ret += " ";
+ }
+ ret += rawExt;
+ }
+ return Some(std::move(ret));
+ }
+
+ case LOCAL_GL_RENDERER:
+ case LOCAL_GL_VENDOR:
+ case LOCAL_GL_VERSION: {
+ const auto raw = (const char*)gl->fGetString(pname);
+ return FromRaw(raw);
+ }
+
+ case dom::MOZ_debug_Binding::WSI_INFO: {
+ nsCString info;
+ gl->GetWSIInfo(&info);
+ return Some(std::string(info.BeginReading()));
+ }
+
+ default:
+ ErrorInvalidEnumArg("pname", pname);
+ return {};
+ }
+}
+
+// ---------------------------------
+
+Maybe<webgl::IndexedName> webgl::ParseIndexed(const std::string& str) {
+ static const std::regex kRegex("(.*)\\[([0-9]+)\\]");
+
+ std::smatch match;
+ if (!std::regex_match(str, match, kRegex)) return {};
+
+ const auto index = std::stoull(match[2]);
+ return Some(webgl::IndexedName{match[1], index});
+}
+
+// ExplodeName("foo.bar[3].x") -> ["foo", ".", "bar", "[", "3", "]", ".", "x"]
+static std::vector<std::string> ExplodeName(const std::string& str) {
+ std::vector<std::string> ret;
+
+ static const std::regex kSep("[.[\\]]");
+
+ auto itr = std::regex_token_iterator<decltype(str.begin())>(
+ str.begin(), str.end(), kSep, {-1, 0});
+ const auto end = decltype(itr)();
+
+ for (; itr != end; ++itr) {
+ const auto& part = itr->str();
+ if (part.size()) {
+ ret.push_back(part);
+ }
+ }
+ return ret;
+}
+
+//-
+
+// #define DUMP_MakeLinkResult
+
+webgl::LinkActiveInfo GetLinkActiveInfo(
+ gl::GLContext& gl, const GLuint prog, const bool webgl2,
+ const std::unordered_map<std::string, std::string>& nameUnmap) {
+ webgl::LinkActiveInfo ret;
+ [&]() {
+ const auto fnGetProgramui = [&](const GLenum pname) {
+ GLint ret = 0;
+ gl.fGetProgramiv(prog, pname, &ret);
+ return static_cast<uint32_t>(ret);
+ };
+
+ std::vector<char> stringBuffer(1);
+ const auto fnEnsureCapacity = [&](const GLenum pname) {
+ const auto maxWithNull = fnGetProgramui(pname);
+ if (maxWithNull > stringBuffer.size()) {
+ stringBuffer.resize(maxWithNull);
+ }
+ };
+
+ fnEnsureCapacity(LOCAL_GL_ACTIVE_ATTRIBUTE_MAX_LENGTH);
+ fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_MAX_LENGTH);
+ if (webgl2) {
+ fnEnsureCapacity(LOCAL_GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH);
+ fnEnsureCapacity(LOCAL_GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH);
+ }
+
+ // -
+
+ const auto fnUnmapName = [&](const std::string& mappedName) {
+ const auto parts = ExplodeName(mappedName);
+
+ std::ostringstream ret;
+ for (const auto& part : parts) {
+ const auto maybe = MaybeFind(nameUnmap, part);
+ if (maybe) {
+ ret << *maybe;
+ } else {
+ ret << part;
+ }
+ }
+ return ret.str();
+ };
+
+ // -
+
+ {
+ const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_ATTRIBUTES);
+ ret.activeAttribs.reserve(count);
+ for (const auto i : IntegerRange(count)) {
+ GLsizei lengthWithoutNull = 0;
+ GLint elemCount = 0; // `size`
+ GLenum elemType = 0; // `type`
+ gl.fGetActiveAttrib(prog, i, stringBuffer.size(), &lengthWithoutNull,
+ &elemCount, &elemType, stringBuffer.data());
+ if (!elemType) {
+ const auto error = gl.fGetError();
+ if (error != LOCAL_GL_CONTEXT_LOST) {
+ gfxCriticalError() << "Failed to do glGetActiveAttrib: " << error;
+ }
+ return;
+ }
+ const auto mappedName =
+ std::string(stringBuffer.data(), lengthWithoutNull);
+ const auto userName = fnUnmapName(mappedName);
+
+ auto loc = gl.fGetAttribLocation(prog, mappedName.c_str());
+ if (mappedName.find("gl_") == 0) {
+ // Bug 1328559: Appears problematic on ANGLE and OSX, but not Linux or
+ // Win+GL.
+ loc = -1;
+ }
+
+#ifdef DUMP_MakeLinkResult
+ printf_stderr("[attrib %u/%u] @%i %s->%s\n", i, count, loc,
+ userName.c_str(), mappedName.c_str());
+#endif
+ webgl::ActiveAttribInfo info;
+ info.elemType = elemType;
+ info.elemCount = elemCount;
+ info.name = userName;
+ info.location = loc;
+ info.baseType = webgl::ToAttribBaseType(info.elemType);
+ ret.activeAttribs.push_back(std::move(info));
+ }
+ }
+
+ // -
+
+ {
+ const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORMS);
+ ret.activeUniforms.reserve(count);
+
+ std::vector<GLint> blockIndexList(count, -1);
+ std::vector<GLint> blockOffsetList(count, -1);
+ std::vector<GLint> blockArrayStrideList(count, -1);
+ std::vector<GLint> blockMatrixStrideList(count, -1);
+ std::vector<GLint> blockIsRowMajorList(count, 0);
+
+ if (webgl2 && count) {
+ std::vector<GLuint> activeIndices;
+ activeIndices.reserve(count);
+ for (const auto i : IntegerRange(count)) {
+ activeIndices.push_back(i);
+ }
+
+ gl.fGetActiveUniformsiv(
+ prog, activeIndices.size(), activeIndices.data(),
+ LOCAL_GL_UNIFORM_BLOCK_INDEX, blockIndexList.data());
+
+ gl.fGetActiveUniformsiv(prog, activeIndices.size(),
+ activeIndices.data(), LOCAL_GL_UNIFORM_OFFSET,
+ blockOffsetList.data());
+
+ gl.fGetActiveUniformsiv(
+ prog, activeIndices.size(), activeIndices.data(),
+ LOCAL_GL_UNIFORM_ARRAY_STRIDE, blockArrayStrideList.data());
+
+ gl.fGetActiveUniformsiv(
+ prog, activeIndices.size(), activeIndices.data(),
+ LOCAL_GL_UNIFORM_MATRIX_STRIDE, blockMatrixStrideList.data());
+
+ gl.fGetActiveUniformsiv(
+ prog, activeIndices.size(), activeIndices.data(),
+ LOCAL_GL_UNIFORM_IS_ROW_MAJOR, blockIsRowMajorList.data());
+ }
+
+ for (const auto i : IntegerRange(count)) {
+ GLsizei lengthWithoutNull = 0;
+ GLint elemCount = 0; // `size`
+ GLenum elemType = 0; // `type`
+ gl.fGetActiveUniform(prog, i, stringBuffer.size(), &lengthWithoutNull,
+ &elemCount, &elemType, stringBuffer.data());
+ if (!elemType) {
+ const auto error = gl.fGetError();
+ if (error != LOCAL_GL_CONTEXT_LOST) {
+ gfxCriticalError() << "Failed to do glGetActiveUniform: " << error;
+ }
+ return;
+ }
+ auto mappedName = std::string(stringBuffer.data(), lengthWithoutNull);
+
+ // Get true name
+
+ auto baseMappedName = mappedName;
+
+ const bool isArray = [&]() {
+ const auto maybe = webgl::ParseIndexed(mappedName);
+ if (maybe) {
+ MOZ_ASSERT(maybe->index == 0);
+ baseMappedName = std::move(maybe->name);
+ return true;
+ }
+ return false;
+ }();
+
+ const auto userName = fnUnmapName(mappedName);
+ if (StartsWith(userName, "webgl_")) continue;
+
+ // -
+
+ webgl::ActiveUniformInfo info;
+ info.elemType = elemType;
+ info.elemCount = static_cast<uint32_t>(elemCount);
+ info.name = userName;
+ info.block_index = blockIndexList[i];
+ info.block_offset = blockOffsetList[i];
+ info.block_arrayStride = blockArrayStrideList[i];
+ info.block_matrixStride = blockMatrixStrideList[i];
+ info.block_isRowMajor = bool(blockIsRowMajorList[i]);
+
+#ifdef DUMP_MakeLinkResult
+ printf_stderr("[uniform %u/%u] %s->%s\n", i + 1, count,
+ userName.c_str(), mappedName.c_str());
+#endif
+
+ // Get uniform locations
+ {
+ auto locName = baseMappedName;
+ const auto baseLength = locName.size();
+ for (const auto i : IntegerRange(info.elemCount)) {
+ if (isArray) {
+ locName.erase(
+ baseLength); // Erase previous [N], but retain capacity.
+ locName += '[';
+ locName += std::to_string(i);
+ locName += ']';
+ }
+ const auto loc = gl.fGetUniformLocation(prog, locName.c_str());
+ if (loc != -1) {
+ info.locByIndex[i] = static_cast<uint32_t>(loc);
+#ifdef DUMP_MakeLinkResult
+ printf_stderr(" [%u] @%i\n", i, loc);
+#endif
+ }
+ }
+ } // anon
+
+ ret.activeUniforms.push_back(std::move(info));
+ } // for i
+ } // anon
+
+ if (webgl2) {
+ // -------------------------------------
+ // active uniform blocks
+ {
+ const auto count = fnGetProgramui(LOCAL_GL_ACTIVE_UNIFORM_BLOCKS);
+ ret.activeUniformBlocks.reserve(count);
+
+ for (const auto i : IntegerRange(count)) {
+ GLsizei lengthWithoutNull = 0;
+ gl.fGetActiveUniformBlockName(prog, i, stringBuffer.size(),
+ &lengthWithoutNull,
+ stringBuffer.data());
+ const auto mappedName =
+ std::string(stringBuffer.data(), lengthWithoutNull);
+ const auto userName = fnUnmapName(mappedName);
+
+ // -
+
+ auto info = webgl::ActiveUniformBlockInfo{userName};
+ GLint val = 0;
+
+ gl.fGetActiveUniformBlockiv(prog, i, LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE,
+ &val);
+ info.dataSize = static_cast<uint32_t>(val);
+
+ gl.fGetActiveUniformBlockiv(
+ prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &val);
+ info.activeUniformIndices.resize(val);
+ gl.fGetActiveUniformBlockiv(
+ prog, i, LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES,
+ reinterpret_cast<GLint*>(info.activeUniformIndices.data()));
+
+ gl.fGetActiveUniformBlockiv(
+ prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER,
+ &val);
+ info.referencedByVertexShader = bool(val);
+
+ gl.fGetActiveUniformBlockiv(
+ prog, i, LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER,
+ &val);
+ info.referencedByFragmentShader = bool(val);
+
+ ret.activeUniformBlocks.push_back(std::move(info));
+ } // for i
+ } // anon
+
+ // -------------------------------------
+ // active tf varyings
+ {
+ const auto count = fnGetProgramui(LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS);
+ ret.activeTfVaryings.reserve(count);
+
+ for (const auto i : IntegerRange(count)) {
+ GLsizei lengthWithoutNull = 0;
+ GLsizei elemCount = 0; // `size`
+ GLenum elemType = 0; // `type`
+ gl.fGetTransformFeedbackVarying(prog, i, stringBuffer.size(),
+ &lengthWithoutNull, &elemCount,
+ &elemType, stringBuffer.data());
+ const auto mappedName =
+ std::string(stringBuffer.data(), lengthWithoutNull);
+ const auto userName = fnUnmapName(mappedName);
+
+ ret.activeTfVaryings.push_back(
+ {elemType, static_cast<uint32_t>(elemCount), userName});
+ }
+ }
+ } // if webgl2
+ }();
+ return ret;
+}
+
+nsCString ToCString(const std::string& s) {
+ return nsCString(s.data(), s.size());
+}
+
+webgl::CompileResult WebGLContext::GetCompileResult(
+ const WebGLShader& shader) const {
+ webgl::CompileResult ret;
+ [&]() {
+ ret.pending = false;
+ const auto& info = shader.CompileResults();
+ if (!info) return;
+ if (!info->mValid) {
+ ret.log = info->mInfoLog.c_str();
+ return;
+ }
+ // TODO: These could be large and should be made fallible.
+ ret.translatedSource = ToCString(info->mObjectCode);
+ ret.log = ToCString(shader.CompileLog());
+ if (!shader.IsCompiled()) return;
+ ret.success = true;
+ }();
+ return ret;
+}
+
+webgl::LinkResult WebGLContext::GetLinkResult(const WebGLProgram& prog) const {
+ webgl::LinkResult ret;
+ [&]() {
+ ret.pending = false; // Link status polling not yet implemented.
+ ret.log = ToCString(prog.LinkLog());
+ const auto& info = prog.LinkInfo();
+ if (!info) return;
+ ret.success = true;
+ ret.active = info->active;
+ ret.tfBufferMode = info->transformFeedbackBufferMode;
+ }();
+ return ret;
+}
+
+// -
+
+GLint WebGLContext::GetFragDataLocation(const WebGLProgram& prog,
+ const std::string& userName) const {
+ const auto err = CheckGLSLVariableName(IsWebGL2(), userName);
+ if (err) {
+ GenerateError(err->type, "%s", err->info.c_str());
+ return -1;
+ }
+
+ const auto& info = prog.LinkInfo();
+ if (!info) return -1;
+ const auto& nameMap = info->nameMap;
+
+ const auto parts = ExplodeName(userName);
+
+ std::ostringstream ret;
+ for (const auto& part : parts) {
+ const auto maybe = MaybeFind(nameMap, part);
+ if (maybe) {
+ ret << *maybe;
+ } else {
+ ret << part;
+ }
+ }
+ const auto mappedName = ret.str();
+
+ if (gl->WorkAroundDriverBugs() && gl->IsMesa()) {
+ // Mesa incorrectly generates INVALID_OPERATION for gl_ prefixes here.
+ if (mappedName.find("gl_") == 0) {
+ return -1;
+ }
+ }
+
+ return gl->fGetFragDataLocation(prog.mGLName, mappedName.c_str());
+}
+
+// -
+
+WebGLContextBoundObject::WebGLContextBoundObject(WebGLContext* webgl)
+ : mContext(webgl) {}
+
+// -
+
+Result<webgl::ExplicitPixelPackingState, std::string>
+webgl::ExplicitPixelPackingState::ForUseWith(
+ const webgl::PixelPackingState& stateOrZero, const GLenum target,
+ const uvec3& subrectSize, const webgl::PackingInfo& pi,
+ const Maybe<size_t> bytesPerRowStrideOverride) {
+ auto state = stateOrZero;
+
+ if (!IsTexTarget3D(target)) {
+ state.skipImages = 0;
+ state.imageHeight = 0;
+ }
+ if (!state.rowLength) {
+ state.rowLength = subrectSize.x;
+ }
+ if (!state.imageHeight) {
+ state.imageHeight = subrectSize.y;
+ }
+
+ // -
+
+ const auto mpii = PackingInfoInfo::For(pi);
+ if (!mpii) {
+ const auto text =
+ nsPrintfCString("Invalid pi: { 0x%x, 0x%x}", pi.format, pi.type);
+ return Err(mozilla::ToString(text));
+ }
+ const auto pii = *mpii;
+ const auto bytesPerPixel = pii.BytesPerPixel();
+
+ const auto ElemsPerRowStride = [&]() {
+ // GLES 3.0.6 p116:
+ // p: `Elem*` pointer to the first element of the first row
+ // N: row number, starting at 0
+ // l: groups (pixels) per row
+ // n: elements per group (pixel) in [1,2,3,4]
+ // s: bytes per element in [1,2,4,8]
+ // a: UNPACK_ALIGNMENT in [1,2,4,8]
+ // Pointer to first element of Nth row: p + N*k
+ // k(s>=a): n*l
+ // k(s<a): a/s * ceil(s*n*l/a)
+ const auto n__elemsPerPixel = pii.elementsPerPixel;
+ const auto l__pixelsPerRow = state.rowLength;
+ const auto a__alignment = state.alignmentInTypeElems;
+ const auto s__bytesPerElem = pii.bytesPerElement;
+
+ const auto nl = CheckedInt<size_t>(n__elemsPerPixel) * l__pixelsPerRow;
+ auto k__elemsPerRowStride = nl;
+ if (s__bytesPerElem < a__alignment) {
+ // k = a/s * ceil(s*n*l/a)
+ k__elemsPerRowStride =
+ a__alignment / s__bytesPerElem *
+ ((nl * s__bytesPerElem + a__alignment - 1) / a__alignment);
+ }
+ return k__elemsPerRowStride;
+ };
+
+ // -
+
+ if (bytesPerRowStrideOverride) { // E.g. HTMLImageElement
+ const size_t bytesPerRowStrideRequired = *bytesPerRowStrideOverride;
+ // We have to reverse-engineer an ALIGNMENT and ROW_LENGTH for this.
+
+ // GL does this in elems not bytes, so we should too.
+ MOZ_RELEASE_ASSERT(bytesPerRowStrideRequired % pii.bytesPerElement == 0);
+ const auto elemsPerRowStrideRequired =
+ bytesPerRowStrideRequired / pii.bytesPerElement;
+
+ state.rowLength = bytesPerRowStrideRequired / bytesPerPixel;
+ state.alignmentInTypeElems = 8;
+ while (true) {
+ const auto elemPerRowStride = ElemsPerRowStride();
+ if (elemPerRowStride.isValid() &&
+ elemPerRowStride.value() == elemsPerRowStrideRequired) {
+ break;
+ }
+ state.alignmentInTypeElems /= 2;
+ if (!state.alignmentInTypeElems) {
+ const auto text = nsPrintfCString(
+ "No valid alignment found: pi: { 0x%x, 0x%x},"
+ " bytesPerRowStrideRequired: %zu",
+ pi.format, pi.type, bytesPerRowStrideRequired);
+ return Err(mozilla::ToString(text));
+ }
+ }
+ }
+
+ // -
+
+ const auto usedPixelsPerRow =
+ CheckedInt<size_t>(state.skipPixels) + subrectSize.x;
+ if (!usedPixelsPerRow.isValid() ||
+ usedPixelsPerRow.value() > state.rowLength) {
+ return Err("UNPACK_SKIP_PIXELS + width > UNPACK_ROW_LENGTH.");
+ }
+
+ if (subrectSize.y > state.imageHeight) {
+ return Err("height > UNPACK_IMAGE_HEIGHT.");
+ }
+ // The spec doesn't bound SKIP_ROWS + height <= IMAGE_HEIGHT, unfortunately.
+
+ // -
+
+ auto metrics = Metrics{};
+
+ metrics.usedSize = subrectSize;
+ metrics.bytesPerPixel = BytesPerPixel(pi);
+
+ // -
+
+ const auto elemsPerRowStride = ElemsPerRowStride();
+ const auto bytesPerRowStride = pii.bytesPerElement * elemsPerRowStride;
+ if (!bytesPerRowStride.isValid()) {
+ return Err("ROW_LENGTH or width too large for packing.");
+ }
+ metrics.bytesPerRowStride = bytesPerRowStride.value();
+
+ // -
+
+ const auto firstImageTotalRows =
+ CheckedInt<size_t>(state.skipRows) + metrics.usedSize.y;
+ const auto totalImages =
+ CheckedInt<size_t>(state.skipImages) + metrics.usedSize.z;
+ auto totalRows = CheckedInt<size_t>(0);
+ if (metrics.usedSize.y && metrics.usedSize.z) {
+ totalRows = firstImageTotalRows + state.imageHeight * (totalImages - 1);
+ }
+ if (!totalRows.isValid()) {
+ return Err(
+ "SKIP_ROWS, height, IMAGE_HEIGHT, SKIP_IMAGES, or depth too large for "
+ "packing.");
+ }
+ metrics.totalRows = totalRows.value();
+
+ // -
+
+ const auto totalBytesStrided = totalRows * metrics.bytesPerRowStride;
+ if (!totalBytesStrided.isValid()) {
+ return Err("Total byte count too large for packing.");
+ }
+ metrics.totalBytesStrided = totalBytesStrided.value();
+
+ metrics.totalBytesUsed = metrics.totalBytesStrided;
+ if (metrics.usedSize.x && metrics.usedSize.y && metrics.usedSize.z) {
+ const auto usedBytesPerRow =
+ usedPixelsPerRow.value() * metrics.bytesPerPixel;
+ metrics.totalBytesUsed -= metrics.bytesPerRowStride;
+ metrics.totalBytesUsed += usedBytesPerRow;
+ }
+
+ // -
+
+ return {{state, metrics}};
+}
+
+} // namespace mozilla