/* -*- 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 #include #include "AccessCheck.h" #include "gfxConfig.h" #include "gfxContext.h" #include "gfxCrashReporterUtils.h" #include "gfxEnv.h" #include "gfxPattern.h" #include "gfxUtils.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 "Layers.h" #include "LayerUserData.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/HTMLVideoElement.h" #include "mozilla/dom/ImageData.h" #include "mozilla/dom/WebGLContextEvent.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/EnumeratedArrayCycleCollection.h" #include "mozilla/Preferences.h" #include "mozilla/ProcessPriorityManager.h" #include "mozilla/ScopeExit.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs.h" #include "mozilla/Telemetry.h" #include "nsContentUtils.h" #include "nsDisplayList.h" #include "nsError.h" #include "nsIClassInfoImpl.h" #include "nsIConsoleService.h" #include "nsIGfxInfo.h" #include "nsIObserverService.h" #include "nsIVariant.h" #include "nsIWidget.h" #include "nsIXPConnect.h" #include "nsServiceManagerUtils.h" #include "SVGObserverUtils.h" #include "prenv.h" #include "ScopedGLHelpers.h" #include "VRManagerChild.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 "WebGL1Context.h" #include "WebGLActiveInfo.h" #include "WebGLBuffer.h" #include "WebGLContextLossHandler.h" #include "WebGLContextUtils.h" #include "WebGLExtensions.h" #include "WebGLFormats.h" #include "WebGLFramebuffer.h" #include "WebGLMemoryTracker.h" #include "WebGLObjectModel.h" #include "WebGLProgram.h" #include "WebGLQuery.h" #include "WebGLSampler.h" #include "WebGLShader.h" #include "WebGLSync.h" #include "WebGLTransformFeedback.h" #include "WebGLVertexArray.h" #include "WebGLVertexAttribData.h" #ifdef MOZ_WIDGET_COCOA # include "nsCocoaFeatures.h" #endif #ifdef XP_WIN # include "WGLLibrary.h" #endif // Generated #include "mozilla/dom/WebGLRenderingContextBinding.h" namespace mozilla { using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::gl; using namespace mozilla::layers; WebGLContextOptions::WebGLContextOptions() { // Set default alpha state based on preference. alpha = !StaticPrefs::webgl_default_no_alpha(); antialias = StaticPrefs::webgl_default_antialias(); } bool WebGLContextOptions::operator==(const WebGLContextOptions& r) const { bool eq = true; eq &= (alpha == r.alpha); eq &= (depth == r.depth); eq &= (stencil == r.stencil); eq &= (premultipliedAlpha == r.premultipliedAlpha); eq &= (antialias == r.antialias); eq &= (preserveDrawingBuffer == r.preserveDrawingBuffer); eq &= (failIfMajorPerformanceCaveat == r.failIfMajorPerformanceCaveat); eq &= (powerPreference == r.powerPreference); return eq; } WebGLContext::WebGLContext() : gl(mGL_OnlyClearInDestroyResourcesAndContext) // const reference , mMaxPerfWarnings(StaticPrefs::webgl_perf_max_warnings()), mNumPerfWarnings(0), mMaxAcceptableFBStatusInvals( StaticPrefs::webgl_perf_max_acceptable_fb_status_invals()), mDataAllocGLCallCount(0), mEmptyTFO(0), mContextLossHandler(this), mNeedsFakeNoAlpha(false), mNeedsFakeNoDepth(false), mNeedsFakeNoStencil(false), mAllowFBInvalidation(StaticPrefs::webgl_allow_fb_invalidation()), mMsaaSamples((uint8_t)StaticPrefs::webgl_msaa_samples()) { mGeneration = 0; mInvalidated = false; mCapturedFrameInvalidated = false; mShouldPresent = true; mResetLayer = true; mOptionsFrozen = false; mDisableExtensions = false; mIsMesa = false; mWebGLError = 0; mVRReady = false; mViewportX = 0; mViewportY = 0; mViewportWidth = 0; mViewportHeight = 0; mDitherEnabled = 1; mRasterizerDiscardEnabled = 0; // OpenGL ES 3.0 spec p244 mScissorTestEnabled = 0; mStencilTestEnabled = 0; if (NS_IsMainThread()) { // XXX mtseng: bug 709490, not thread safe WebGLMemoryTracker::AddWebGLContext(this); } mAllowContextRestore = true; mLastLossWasSimulated = false; mLoseContextOnMemoryPressure = false; mCanLoseContextInForeground = true; mAlreadyGeneratedWarnings = 0; mAlreadyWarnedAboutFakeVertexAttrib0 = false; mAlreadyWarnedAboutViewportLargerThanDest = false; mMaxWarnings = StaticPrefs::webgl_max_warnings_per_context(); if (mMaxWarnings < -1) { GenerateWarning( "webgl.max-warnings-per-context size is too large (seems like a " "negative value wrapped)"); mMaxWarnings = 0; } mLastUseIndex = 0; mDisableFragHighP = false; mDrawCallsSinceLastFlush = 0; } WebGLContext::~WebGLContext() { RemovePostRefreshObserver(); DestroyResourcesAndContext(); if (NS_IsMainThread()) { // XXX mtseng: bug 709490, not thread safe WebGLMemoryTracker::RemoveWebGLContext(this); } } template void ClearLinkedList(LinkedList& list) { while (!list.isEmpty()) { list.getLast()->DeleteOnce(); } } void WebGLContext::DestroyResourcesAndContext() { 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; mBoundRenderbuffer = nullptr; mBoundVertexArray = nullptr; mDefaultVertexArray = nullptr; mBoundTransformFeedback = nullptr; mDefaultTransformFeedback = nullptr; #if defined(MOZ_WIDGET_ANDROID) mVRScreen = nullptr; #endif mQuerySlot_SamplesPassed = nullptr; mQuerySlot_TFPrimsWritten = nullptr; mQuerySlot_TimeElapsed = nullptr; mIndexedUniformBufferBindings.clear(); if (mAvailabilityRunnable) { mAvailabilityRunnable->Run(); } ////// ClearLinkedList(mBuffers); ClearLinkedList(mFramebuffers); ClearLinkedList(mPrograms); ClearLinkedList(mQueries); ClearLinkedList(mRenderbuffers); ClearLinkedList(mSamplers); ClearLinkedList(mShaders); ClearLinkedList(mSyncs); ClearLinkedList(mTextures); ClearLinkedList(mTransformFeedbacks); ClearLinkedList(mVertexArrays); ////// 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 (!IsExtensionEnabled(extension) || (extension == WebGLExtensionID::WEBGL_lose_context)) continue; mExtensions[extension]->MarkLost(); mExtensions[extension] = nullptr; } // We just got rid of everything, so the context had better // have been going away. if (GLContext::ShouldSpew()) { printf_stderr("--- WebGL context destroyed: %p\n", gl.get()); } MOZ_ASSERT(gl); gl->MarkDestroyed(); mGL_OnlyClearInDestroyResourcesAndContext = nullptr; MOZ_ASSERT(!gl); mDynDGpuManager = nullptr; } void WebGLContext::Invalidate() { if (!mCanvasElement) return; mCapturedFrameInvalidated = true; if (mInvalidated) return; SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement); mInvalidated = true; mCanvasElement->InvalidateCanvasContent(nullptr); } void WebGLContext::OnMemoryPressure() { bool shouldLoseContext = mLoseContextOnMemoryPressure; if (!mCanLoseContextInForeground && ProcessPriorityManager::CurrentProcessIsForeground()) { shouldLoseContext = false; } if (shouldLoseContext) ForceLoseContext(); } // // nsICanvasRenderingContextInternal // static bool IsFeatureInBlacklist(const nsCOMPtr& gfxInfo, int32_t feature, nsCString* const out_blacklistId) { int32_t status; if (!NS_SUCCEEDED(gfxUtils::ThreadSafeGetFeatureStatus( gfxInfo, feature, *out_blacklistId, &status))) { return false; } return status != nsIGfxInfo::FEATURE_STATUS_OK; } NS_IMETHODIMP WebGLContext::SetContextOptions(JSContext* cx, JS::Handle options, ErrorResult& aRvForDictionaryInit) { const FuncScope funcScope(*this, "getContext"); (void)IsContextLost(); // Ignore this. if (options.isNullOrUndefined() && mOptionsFrozen) return NS_OK; WebGLContextAttributes attributes; if (!attributes.Init(cx, options)) { aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED); return NS_ERROR_UNEXPECTED; } WebGLContextOptions newOpts; newOpts.stencil = attributes.mStencil; newOpts.depth = attributes.mDepth; newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha; newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer; newOpts.failIfMajorPerformanceCaveat = attributes.mFailIfMajorPerformanceCaveat; newOpts.powerPreference = attributes.mPowerPreference; if (attributes.mAlpha.WasPassed()) { newOpts.alpha = attributes.mAlpha.Value(); } if (attributes.mAntialias.WasPassed()) { newOpts.antialias = attributes.mAntialias.Value(); } // Don't do antialiasing if we've disabled MSAA. if (!mMsaaSamples) { newOpts.antialias = false; } if (newOpts.antialias && !StaticPrefs::webgl_msaa_force()) { const nsCOMPtr gfxInfo = services::GetGfxInfo(); nsCString blocklistId; if (IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_MSAA, &blocklistId)) { GenerateWarning( "Disallowing antialiased backbuffers due to blacklisting."); newOpts.antialias = false; } } #if 0 GenerateWarning("aaHint: %d stencil: %d depth: %d alpha: %d premult: %d preserve: %d\n", newOpts.antialias ? 1 : 0, newOpts.stencil ? 1 : 0, newOpts.depth ? 1 : 0, newOpts.alpha ? 1 : 0, newOpts.premultipliedAlpha ? 1 : 0, newOpts.preserveDrawingBuffer ? 1 : 0); #endif if (mOptionsFrozen && !(newOpts == mOptions)) { // Error if the options are already frozen, and the ones that were asked for // aren't the same as what they were originally. return NS_ERROR_FAILURE; } mOptions = newOpts; return NS_OK; } static bool HasAcceleratedLayers(const nsCOMPtr& gfxInfo) { int32_t status; nsCString discardFailureId; gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_DIRECT3D_9_LAYERS, discardFailureId, &status); if (status) return true; gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_DIRECT3D_10_LAYERS, discardFailureId, &status); if (status) return true; gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_DIRECT3D_10_1_LAYERS, discardFailureId, &status); if (status) return true; gfxUtils::ThreadSafeGetFeatureStatus(gfxInfo, nsIGfxInfo::FEATURE_DIRECT3D_11_LAYERS, discardFailureId, &status); if (status) return true; gfxUtils::ThreadSafeGetFeatureStatus( gfxInfo, nsIGfxInfo::FEATURE_OPENGL_LAYERS, discardFailureId, &status); if (status) return true; return false; } // -- bool WebGLContext::CreateAndInitGL( bool forceEnabled, std::vector* const out_failReasons) { // Can't use WebGL in headless mode. if (gfxPlatform::IsHeadless()) { FailureReason reason; reason.info = "Can't use WebGL in headless mode (https://bugzil.la/1375585)."; out_failReasons->push_back(reason); GenerateWarning("%s", reason.info.BeginReading()); return false; } // WebGL can't be used when recording/replaying. if (recordreplay::IsRecordingOrReplaying()) { FailureReason reason; reason.info = "Can't use WebGL when recording or replaying " "(https://bugzil.la/1506467)."; out_failReasons->push_back(reason); GenerateWarning("%s", reason.info.BeginReading()); return false; } // WebGL2 is separately blocked: if (IsWebGL2() && !forceEnabled) { const nsCOMPtr gfxInfo = services::GetGfxInfo(); const auto feature = nsIGfxInfo::FEATURE_WEBGL2; FailureReason reason; if (IsFeatureInBlacklist(gfxInfo, feature, &reason.key)) { reason.info = "Refused to create WebGL2 context because of blacklist" " entry: "; reason.info.Append(reason.key); out_failReasons->push_back(reason); GenerateWarning("%s", reason.info.BeginReading()); return false; } } gl::CreateContextFlags flags = (gl::CreateContextFlags::NO_VALIDATION | gl::CreateContextFlags::PREFER_ROBUSTNESS); bool tryNativeGL = true; bool tryANGLE = false; if (forceEnabled) { flags |= gl::CreateContextFlags::FORCE_ENABLE_HARDWARE; } if (IsWebGL2()) { flags |= gl::CreateContextFlags::PREFER_ES3; } else 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 (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) { powerPref = dom::WebGLPowerPreference::Low_power; } if (StaticPrefs::webgl_default_low_power() && powerPref == dom::WebGLPowerPreference::Default) { powerPref = dom::WebGLPowerPreference::Low_power; } bool highPower; switch (powerPref) { case dom::WebGLPowerPreference::Low_power: highPower = false; break; case dom::WebGLPowerPreference::High_performance: highPower = true; break; // Eventually add a heuristic, but for now default to high-performance. // We can even make it dynamic by holding on to a // ForceDiscreteGPUHelperCGL iff we decide it's a high-performance // application: // - Non-trivial canvas size // - Many draw calls // - Same origin with root page (try to stem bleeding from WebGL // ads/trackers) default: highPower = false; mDynDGpuManager = webgl::DynDGpuManager::Get(); if (!mDynDGpuManager) { highPower = true; } break; } if (highPower) { flags |= gl::CreateContextFlags::HIGH_POWER; } } #ifdef XP_MACOSX const nsCOMPtr gfxInfo = services::GetGfxInfo(); nsString vendorID, deviceID; // Avoid crash for Intel HD Graphics 3000 on OSX. (Bug 1413269) gfxInfo->GetAdapterVendorID(vendorID); gfxInfo->GetAdapterDeviceID(deviceID); if (vendorID.EqualsLiteral("0x8086") && (deviceID.EqualsLiteral("0x0116") || deviceID.EqualsLiteral("0x0126"))) { flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE; } #endif // -- const auto surfaceCaps = [&]() { auto ret = gl::SurfaceCaps::ForRGBA(); ret.premultAlpha = mOptions.premultipliedAlpha; ret.preserve = mOptions.preserveDrawingBuffer; if (!mOptions.alpha) { ret.premultAlpha = true; } return ret; }(); // -- const bool useEGL = PR_GetEnv("MOZ_WEBGL_FORCE_EGL"); #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) { const nsCOMPtr gfxInfo = services::GetGfxInfo(); const auto feature = nsIGfxInfo::FEATURE_WEBGL_OPENGL; FailureReason reason; if (IsFeatureInBlacklist(gfxInfo, feature, &reason.key)) { reason.info = "Refused to create native OpenGL context because of blacklist" " entry: "; reason.info.Append(reason.key); out_failReasons->push_back(reason); GenerateWarning("%s", reason.info.BeginReading()); tryNativeGL = false; } } // -- typedef decltype( gl::GLContextProviderEGL::CreateOffscreen) fnCreateOffscreenT; const auto fnCreate = [&](fnCreateOffscreenT* const pfnCreateOffscreen, const char* const info) { const gfx::IntSize dummySize(1, 1); nsCString failureId; const RefPtr gl = pfnCreateOffscreen(dummySize, surfaceCaps, flags, &failureId); if (!gl) { out_failReasons->push_back(WebGLContext::FailureReason(failureId, info)); } return gl; }; const auto newGL = [&]() -> RefPtr { if (tryNativeGL) { if (useEGL) return fnCreate(&gl::GLContextProviderEGL::CreateOffscreen, "useEGL"); const auto ret = fnCreate(&gl::GLContextProvider::CreateOffscreen, "tryNativeGL"); if (ret) return ret; } if (tryANGLE) { // Force enable alpha channel to make sure ANGLE use correct framebuffer // format MOZ_ASSERT(surfaceCaps.alpha); return fnCreate(&gl::GLContextProviderEGL::CreateOffscreen, "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; } return true; } // Fallback for resizes: bool WebGLContext::EnsureDefaultFB() { if (mDefaultFB) { MOZ_ASSERT(mDefaultFB->mSize == mRequestedSize); return true; } const bool depthStencil = mOptions.depth || mOptions.stencil; auto attemptSize = mRequestedSize; 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 = MozFramebuffer::Create(gl, attemptSize, mMsaaSamples, depthStencil); if (mDefaultFB) return; if (mOptionsFrozen) return; } MOZ_ASSERT(!mDefaultFB); mDefaultFB = MozFramebuffer::Create(gl, attemptSize, 0, depthStencil); }(); if (mDefaultFB) break; attemptSize.width /= 2; attemptSize.height /= 2; } if (!mDefaultFB) { GenerateWarning("Backbuffer resize failed. Losing context."); ForceLoseContext(); return false; } mDefaultFB_IsInvalid = true; if (mDefaultFB->mSize != mRequestedSize) { GenerateWarning( "Requested size %dx%d was too large, but resize" " to %dx%d succeeded.", mRequestedSize.width, mRequestedSize.height, mDefaultFB->mSize.width, mDefaultFB->mSize.height); } mRequestedSize = mDefaultFB->mSize; return true; } void WebGLContext::ThrowEvent_WebGLContextCreationError( const nsACString& text) { RefPtr target = mCanvasElement; if (!target && mOffscreenCanvas) { target = mOffscreenCanvas; } else if (!target) { GenerateWarning("Failed to create WebGL context: %s", text.BeginReading()); return; } const auto kEventName = NS_LITERAL_STRING("webglcontextcreationerror"); WebGLContextEventInit eventInit; // eventInit.mCancelable = true; // The spec says this, but it's silly. eventInit.mStatusMessage = NS_ConvertASCIItoUTF16(text); const RefPtr event = WebGLContextEvent::Constructor(target, kEventName, eventInit); event->SetTrusted(true); target->DispatchEvent(*event); ////// GenerateWarning("Failed to create WebGL context: %s", text.BeginReading()); } NS_IMETHODIMP WebGLContext::SetDimensions(int32_t signedWidth, int32_t signedHeight) { const FuncScope funcScope(*this, ""); (void)IsContextLost(); // We handle this ourselves. if (signedWidth < 0 || signedHeight < 0) { if (!gl) { Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_SIZE")); } GenerateWarning( "Canvas size is too large (seems like a negative value wrapped)"); return NS_ERROR_OUT_OF_MEMORY; } uint32_t width = signedWidth; uint32_t height = signedHeight; // Early success return cases // May have a OffscreenCanvas instead of an HTMLCanvasElement if (GetCanvas()) GetCanvas()->InvalidateCanvas(); // Zero-sized surfaces can cause problems. if (width == 0) width = 1; if (height == 0) height = 1; // If we already have a gl context, then we just need to resize it if (gl) { if (uint32_t(mRequestedSize.width) == width && uint32_t(mRequestedSize.height) == height) { return NS_OK; } if (IsContextLost()) return NS_OK; // If we've already drawn, we should commit the current buffer. PresentScreenBuffer(gl->Screen()); if (IsContextLost()) { GenerateWarning("WebGL context was lost due to swap failure."); return NS_OK; } // Kill our current default fb(s), for later lazy allocation. mRequestedSize = {width, height}; mDefaultFB = nullptr; mResetLayer = true; return NS_OK; } nsCString failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_UNKOWN"); auto autoTelemetry = mozilla::MakeScopeExit([&] { Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, failureId); }); // End of early return cases. // At this point we know that we're not just resizing an existing context, // we are initializing a new context. // if we exceeded either the global or the per-principal limit for WebGL // contexts, lose the oldest-used context now to free resources. Note that we // can't do that in the WebGLContext constructor as we don't have a canvas // element yet there. Here is the right place to do so, as we are about to // create the OpenGL context and that is what can fail if we already have too // many. LoseOldestWebGLContextIfLimitExceeded(); // We're going to create an entirely new context. If our // generation is not 0 right now (that is, if this isn't the first // context we're creating), we may have to dispatch a context lost // event. // If incrementing the generation would cause overflow, // don't allow it. Allowing this would allow us to use // resource handles created from older context generations. if (!(mGeneration + 1).isValid()) { // exit without changing the value of mGeneration failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_TOO_MANY"); const nsLiteralCString text("Too many WebGL contexts created this run."); ThrowEvent_WebGLContextCreationError(text); return NS_ERROR_FAILURE; } // increment the generation number - Do this early because later // in CreateOffscreenGL(), "default" objects are created that will // pick up the old generation. ++mGeneration; 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 = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_SAFEMODE"); } else { failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_DISABLED"); } const nsLiteralCString text("WebGL is currently disabled."); ThrowEvent_WebGLContextCreationError(text); return NS_ERROR_FAILURE; } if (StaticPrefs::webgl_disable_fail_if_major_performance_caveat()) { mOptions.failIfMajorPerformanceCaveat = false; } if (mOptions.failIfMajorPerformanceCaveat) { nsCOMPtr gfxInfo = services::GetGfxInfo(); if (!HasAcceleratedLayers(gfxInfo)) { failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_PERF_CAVEAT"); const nsLiteralCString text( "failIfMajorPerformanceCaveat: Compositor is not" " hardware-accelerated."); ThrowEvent_WebGLContextCreationError(text); return NS_ERROR_FAILURE; } } // Alright, now let's start trying. bool forceEnabled = StaticPrefs::webgl_force_enabled(); ScopedGfxFeatureReporter reporter("WebGL", forceEnabled); MOZ_ASSERT(!gl); std::vector failReasons; if (!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, NS_LITERAL_CSTRING("FEATURE_FAILURE_REASON_UNKNOWN")); } else { Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, cur.key); } text.AppendLiteral("\n* "); text.Append(cur.info); } failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_REASON"); ThrowEvent_WebGLContextCreationError(text); return NS_ERROR_FAILURE; } MOZ_ASSERT(gl); if (mOptions.failIfMajorPerformanceCaveat) { if (gl->IsWARP()) { DestroyResourcesAndContext(); MOZ_ASSERT(!gl); failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_PERF_WARP"); const nsLiteralCString text( "failIfMajorPerformanceCaveat: Driver is not" " hardware-accelerated."); ThrowEvent_WebGLContextCreationError(text); return NS_ERROR_FAILURE; } #ifdef XP_WIN if (gl->GetContextType() == gl::GLContextType::WGL && !gl::sWGLLib.HasDXInterop2()) { DestroyResourcesAndContext(); MOZ_ASSERT(!gl); failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_DXGL_INTEROP2"); const nsLiteralCString text("Caveat: WGL without DXGLInterop2."); ThrowEvent_WebGLContextCreationError(text); return NS_ERROR_FAILURE; } #endif } MOZ_ASSERT(!mDefaultFB); mRequestedSize = {width, height}; if (!EnsureDefaultFB()) { MOZ_ASSERT(!gl); failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_BACKBUFFER"); const nsLiteralCString text("Initializing WebGL backbuffer failed."); ThrowEvent_WebGLContextCreationError(text); return NS_ERROR_FAILURE; } if (GLContext::ShouldSpew()) { printf_stderr("--- WebGL context created: %p\n", gl.get()); } // Update our internal stuff: 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() == GLVendor::Intel) { mNeedsFakeNoStencil_UserFBs = true; } #endif mResetLayer = true; mOptionsFrozen = true; ////// // Initial setup. gl->mImplicitMakeCurrent = 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; ////// reporter.SetSuccessful(); failureId = NS_LITERAL_CSTRING("SUCCESS"); gl->ResetSyncCallCount("WebGLContext Initialization"); return NS_OK; } void WebGLContext::LoseOldestWebGLContextIfLimitExceeded() { const auto maxWebGLContexts = StaticPrefs::webgl_max_contexts(); auto maxWebGLContextsPerPrincipal = StaticPrefs::webgl_max_contexts_per_principal(); // maxWebGLContextsPerPrincipal must be less than maxWebGLContexts MOZ_ASSERT(maxWebGLContextsPerPrincipal <= maxWebGLContexts); maxWebGLContextsPerPrincipal = std::min(maxWebGLContextsPerPrincipal, maxWebGLContexts); if (!NS_IsMainThread()) { // XXX mtseng: bug 709490, WebGLMemoryTracker is not thread safe. return; } // 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. UpdateLastUseIndex(); WebGLMemoryTracker::ContextsArrayType& contexts = WebGLMemoryTracker::Contexts(); // quick exit path, should cover a majority of cases if (contexts.Length() <= maxWebGLContextsPerPrincipal) return; // note that here by "context" we mean "non-lost context". See the check for // IsContextLost() below. Indeed, the point of this function is to maybe lose // some currently non-lost context. uint64_t oldestIndex = UINT64_MAX; uint64_t oldestIndexThisPrincipal = UINT64_MAX; const WebGLContext* oldestContext = nullptr; const WebGLContext* oldestContextThisPrincipal = nullptr; size_t numContexts = 0; size_t numContextsThisPrincipal = 0; for (size_t i = 0; i < contexts.Length(); ++i) { // don't want to lose ourselves. if (contexts[i] == this) continue; if (!contexts[i]->gl) continue; if (!contexts[i]->GetCanvas()) { // Zombie context: the canvas is already destroyed, but something else // (typically the compositor) is still holding on to the context. // Killing zombies is a no-brainer. const_cast(contexts[i])->LoseContext(); continue; } numContexts++; if (contexts[i]->mLastUseIndex < oldestIndex) { oldestIndex = contexts[i]->mLastUseIndex; oldestContext = contexts[i]; } nsIPrincipal* ourPrincipal = GetCanvas()->NodePrincipal(); nsIPrincipal* theirPrincipal = contexts[i]->GetCanvas()->NodePrincipal(); bool samePrincipal; nsresult rv = ourPrincipal->Equals(theirPrincipal, &samePrincipal); if (NS_SUCCEEDED(rv) && samePrincipal) { numContextsThisPrincipal++; if (contexts[i]->mLastUseIndex < oldestIndexThisPrincipal) { oldestIndexThisPrincipal = contexts[i]->mLastUseIndex; oldestContextThisPrincipal = contexts[i]; } } } if (numContextsThisPrincipal > maxWebGLContextsPerPrincipal) { GenerateWarning( "Exceeded %u live WebGL contexts for this principal, losing the " "least recently used one.", maxWebGLContextsPerPrincipal); MOZ_ASSERT(oldestContextThisPrincipal); // if we reach this point, this // can't be null const_cast(oldestContextThisPrincipal)->LoseContext(); } else if (numContexts > maxWebGLContexts) { GenerateWarning( "Exceeded %u live WebGL contexts, losing the least " "recently used one.", maxWebGLContexts); MOZ_ASSERT(oldestContext); // if we reach this point, this can't be null const_cast(oldestContext)->LoseContext(); } } UniquePtr WebGLContext::GetImageBuffer(int32_t* out_format) { *out_format = 0; // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied gfxAlphaType any; RefPtr snapshot = GetSurfaceSnapshot(&any); if (!snapshot) return nullptr; RefPtr dataSurface = snapshot->GetDataSurface(); return gfxUtils::GetImageBuffer(dataSurface, mOptions.premultipliedAlpha, out_format); } NS_IMETHODIMP WebGLContext::GetInputStream(const char* mimeType, const nsAString& encoderOptions, nsIInputStream** out_stream) { NS_ASSERTION(gl, "GetInputStream on invalid context?"); if (!gl) return NS_ERROR_FAILURE; // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied gfxAlphaType any; RefPtr snapshot = GetSurfaceSnapshot(&any); if (!snapshot) return NS_ERROR_FAILURE; RefPtr dataSurface = snapshot->GetDataSurface(); return gfxUtils::GetInputStream(dataSurface, mOptions.premultipliedAlpha, mimeType, encoderOptions, out_stream); } void WebGLContext::UpdateLastUseIndex() { static CheckedInt sIndex = 0; sIndex++; // should never happen with 64-bit; trying to handle this would be riskier // than not handling it as the handler code would never get exercised. if (!sIndex.isValid()) MOZ_CRASH("Can't believe it's been 2^64 transactions already!"); mLastUseIndex = sIndex.value(); } static uint8_t gWebGLLayerUserData; class WebGLContextUserData : public LayerUserData { public: explicit WebGLContextUserData(HTMLCanvasElement* canvas) : mCanvas(canvas) {} /* PreTransactionCallback gets called by the Layers code every time the * WebGL canvas is going to be composited. */ static void PreTransactionCallback(void* data) { WebGLContext* webgl = static_cast(data); // Prepare the context for composition webgl->BeginComposition(); } /** DidTransactionCallback gets called by the Layers code everytime the WebGL * canvas gets composite, so it really is the right place to put actions that * have to be performed upon compositing */ static void DidTransactionCallback(void* data) { WebGLContext* webgl = static_cast(data); // Clean up the context after composition webgl->EndComposition(); } private: RefPtr mCanvas; }; already_AddRefed WebGLContext::GetCanvasLayer( nsDisplayListBuilder* builder, Layer* oldLayer, LayerManager* manager) { if (!mResetLayer && oldLayer && oldLayer->HasUserData(&gWebGLLayerUserData)) { RefPtr ret = oldLayer; return ret.forget(); } RefPtr canvasLayer = manager->CreateCanvasLayer(); if (!canvasLayer) { NS_WARNING("CreateCanvasLayer returned null!"); return nullptr; } WebGLContextUserData* userData = nullptr; if (builder->IsPaintingToWindow() && mCanvasElement) { userData = new WebGLContextUserData(mCanvasElement); } canvasLayer->SetUserData(&gWebGLLayerUserData, userData); CanvasRenderer* canvasRenderer = canvasLayer->CreateOrGetCanvasRenderer(); if (!InitializeCanvasRenderer(builder, canvasRenderer)) return nullptr; if (!gl) { NS_WARNING("GLContext is null!"); return nullptr; } uint32_t flags = gl->Caps().alpha ? 0 : Layer::CONTENT_OPAQUE; canvasLayer->SetContentFlags(flags); mResetLayer = false; return canvasLayer.forget(); } bool WebGLContext::UpdateWebRenderCanvasData(nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) { CanvasRenderer* renderer = aCanvasData->GetCanvasRenderer(); if (!mResetLayer && renderer) { return true; } renderer = aCanvasData->CreateCanvasRenderer(); if (!InitializeCanvasRenderer(aBuilder, renderer)) { // Clear CanvasRenderer of WebRenderCanvasData aCanvasData->ClearCanvasRenderer(); return false; } MOZ_ASSERT(renderer); mResetLayer = false; return true; } bool WebGLContext::InitializeCanvasRenderer(nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) { const FuncScope funcScope(*this, ""); if (IsContextLost()) return false; CanvasInitializeData data; if (aBuilder->IsPaintingToWindow() && mCanvasElement) { // Make the layer tell us whenever a transaction finishes (including // the current transaction), so we can clear our invalidation state and // start invalidating again. We need to do this for the layer that is // being painted to a window (there shouldn't be more than one at a time, // and if there is, flushing the invalidation state more often than // necessary is harmless). // The layer will be destroyed when we tear down the presentation // (at the latest), at which time this userData will be destroyed, // releasing the reference to the element. // The userData will receive DidTransactionCallbacks, which flush the // the invalidation state to indicate that the canvas is up to date. data.mPreTransCallback = WebGLContextUserData::PreTransactionCallback; data.mPreTransCallbackData = this; data.mDidTransCallback = WebGLContextUserData::DidTransactionCallback; data.mDidTransCallbackData = this; } data.mSize = DrawingBufferSize(); data.mHasAlpha = mOptions.alpha; data.mIsGLAlphaPremult = IsPremultAlpha() || !data.mHasAlpha; data.mGLContext = gl; aRenderer->Initialize(data); aRenderer->SetDirty(); mVRReady = true; return true; } layers::LayersBackend WebGLContext::GetCompositorBackendType() const { if (mCanvasElement) { return mCanvasElement->GetCompositorBackendType(); } else if (mOffscreenCanvas) { return mOffscreenCanvas->GetCompositorBackendType(); } return LayersBackend::LAYERS_NONE; } Document* WebGLContext::GetOwnerDoc() const { MOZ_ASSERT(mCanvasElement); if (!mCanvasElement) { return nullptr; } return mCanvasElement->OwnerDoc(); } void WebGLContext::Commit() { if (mOffscreenCanvas) { mOffscreenCanvas->CommitFrameToCompositor(); } } void WebGLContext::GetCanvas( Nullable& retval) { if (mCanvasElement) { MOZ_RELEASE_ASSERT(!mOffscreenCanvas, "GFX: Canvas is offscreen."); if (mCanvasElement->IsInNativeAnonymousSubtree()) { retval.SetNull(); } else { retval.SetValue().SetAsHTMLCanvasElement() = mCanvasElement; } } else if (mOffscreenCanvas) { retval.SetValue().SetAsOffscreenCanvas() = mOffscreenCanvas; } else { retval.SetNull(); } } void WebGLContext::GetContextAttributes( dom::Nullable& retval) { retval.SetNull(); const FuncScope funcScope(*this, "getContextAttributes"); if (IsContextLost()) return; dom::WebGLContextAttributes& result = retval.SetValue(); result.mAlpha.Construct(mOptions.alpha); result.mDepth = mOptions.depth; result.mStencil = mOptions.stencil; result.mAntialias.Construct(mOptions.antialias); result.mPremultipliedAlpha = mOptions.premultipliedAlpha; result.mPreserveDrawingBuffer = mOptions.preserveDrawingBuffer; result.mFailIfMajorPerformanceCaveat = mOptions.failIfMajorPerformanceCaveat; result.mPowerPreference = mOptions.powerPreference; } // - 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(0x0f); 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); } // DoColorMask() is lazy. 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() const { 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"); } void WebGLContext::BlitBackbufferToCurDriverFB() const { DoColorMask(0x0f); if (mScissorTestEnabled) { gl->fDisable(LOCAL_GL_SCISSOR_TEST); } [&]() { const auto& size = mDefaultFB->mSize; if (gl->IsSupported(GLFeature::framebuffer_blit)) { gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB); 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(GLContext::APPLE_framebuffer_multisample)) { gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mDefaultFB->mFB); gl->fResolveMultisampleFramebufferAPPLE(); return; } gl->BlitHelper()->DrawBlitTextureToFramebuffer(mDefaultFB->ColorTex(), size, size); }(); if (mScissorTestEnabled) { gl->fEnable(LOCAL_GL_SCISSOR_TEST); } } // For an overview of how WebGL compositing works, see: // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing bool WebGLContext::PresentScreenBuffer(GLScreenBuffer* const targetScreen) { const FuncScope funcScope(*this, ""); if (IsContextLost()) return false; if (!mShouldPresent) return false; ReportActivity(); if (!ValidateAndInitFB(nullptr)) return false; const auto& screen = targetScreen ? targetScreen : gl->Screen(); if ((!screen->IsReadBufferReady() || screen->Size() != mDefaultFB->mSize) && !screen->Resize(mDefaultFB->mSize)) { GenerateWarning("screen->Resize failed. Losing context."); ForceLoseContext(); return false; } gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); BlitBackbufferToCurDriverFB(); #ifdef DEBUG if (!mOptions.alpha) { gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0); uint32_t pixel = 3; gl->fReadPixels(0, 0, 1, 1, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, &pixel); MOZ_ASSERT((pixel & 0xff000000) == 0xff000000); } #endif if (!screen->PublishFrame(screen->Size())) { GenerateWarning("PublishFrame failed. Losing context."); ForceLoseContext(); return false; } if (!mOptions.preserveDrawingBuffer) { if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) { gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB); const GLenum attachments[] = {LOCAL_GL_COLOR_ATTACHMENT0}; gl->fInvalidateFramebuffer(LOCAL_GL_FRAMEBUFFER, 1, attachments); } mDefaultFB_IsInvalid = true; } mResolvedDefaultFB = nullptr; mShouldPresent = false; OnEndOfFrame(); return true; } // Prepare the context for capture before compositing void WebGLContext::BeginComposition(GLScreenBuffer* const screen) { // Present our screenbuffer, if needed. PresentScreenBuffer(screen); mDrawCallsSinceLastFlush = 0; } // Clean up the context after captured for compositing void WebGLContext::EndComposition() { // Mark ourselves as no longer invalidated. MarkContextClean(); UpdateLastUseIndex(); } 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(GLFeature::sync); } static bool CheckContextLost(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(); } class UpdateContextLossStatusTask : public CancelableRunnable { RefPtr mWebGL; public: explicit UpdateContextLossStatusTask(WebGLContext* webgl) : CancelableRunnable("UpdateContextLossStatusTask"), mWebGL(webgl) {} NS_IMETHOD Run() override { if (mWebGL) mWebGL->UpdateContextLossStatus(); return NS_OK; } nsresult Cancel() override { mWebGL = nullptr; return NS_OK; } }; void WebGLContext::EnqueueUpdateContextLossStatus() { nsCOMPtr task = new UpdateContextLossStatusTask(this); NS_DispatchToCurrentThread(task); } // 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::UpdateContextLossStatus() { if (!mCanvasElement && !mOffscreenCanvas) { // the canvas is gone. That happens when the page was closed before we got // this timer event. In this case, there's nothing to do here, just don't // crash. return; } if (mContextStatus == ContextStatus::NotLost) { // We don't know that we're lost, but we might be, so we need to // check. If we're guilty, don't allow restores, though. bool isGuilty = true; MOZ_ASSERT(gl); // Shouldn't be missing gl if we're NotLost. bool isContextLost = CheckContextLost(gl, &isGuilty); if (isContextLost) { if (isGuilty) mAllowContextRestore = false; ForceLoseContext(); } // Fall through. } if (mContextStatus == ContextStatus::LostAwaitingEvent) { // The context has been lost and we haven't yet triggered the // callback, so do that now. const auto kEventName = NS_LITERAL_STRING("webglcontextlost"); const auto kCanBubble = CanBubble::eYes; const auto kIsCancelable = Cancelable::eYes; bool useDefaultHandler; if (mCanvasElement) { nsContentUtils::DispatchTrustedEvent( mCanvasElement->OwnerDoc(), static_cast(mCanvasElement), kEventName, kCanBubble, kIsCancelable, &useDefaultHandler); } else { // OffscreenCanvas case RefPtr event = new Event(mOffscreenCanvas, nullptr, nullptr); event->InitEvent(kEventName, kCanBubble, kIsCancelable); event->SetTrusted(true); useDefaultHandler = mOffscreenCanvas->DispatchEvent( *event, CallerType::System, IgnoreErrors()); } // We sent the callback, so we're just 'regular lost' now. mContextStatus = ContextStatus::Lost; // If we're told to use the default handler, it means the script // didn't bother to handle the event. In this case, we shouldn't // auto-restore the context. if (useDefaultHandler) mAllowContextRestore = false; // Fall through. } if (mContextStatus == ContextStatus::Lost) { // Context is lost, and we've already sent the callback. We // should try to restore the context if we're both allowed to, // and supposed to. // Are we allowed to restore the context? if (!mAllowContextRestore) return; // If we're only simulated-lost, we shouldn't auto-restore, and // instead we should wait for restoreContext() to be called. if (mLastLossWasSimulated) return; ForceRestoreContext(); return; } if (mContextStatus == ContextStatus::LostAwaitingRestore) { // Context is lost, but we should try to restore it. if (mAllowContextRestore) { if (NS_FAILED( SetDimensions(mRequestedSize.width, mRequestedSize.height))) { // Assume broken forever. mAllowContextRestore = false; } } if (!mAllowContextRestore) { // We might decide this after thinking we'd be OK restoring // the context, so downgrade. mContextStatus = ContextStatus::Lost; return; } // Revival! mContextStatus = ContextStatus::NotLost; if (mCanvasElement) { nsContentUtils::DispatchTrustedEvent( mCanvasElement->OwnerDoc(), static_cast(mCanvasElement), NS_LITERAL_STRING("webglcontextrestored"), CanBubble::eYes, Cancelable::eYes); } else { RefPtr event = new Event(mOffscreenCanvas, nullptr, nullptr); event->InitEvent(NS_LITERAL_STRING("webglcontextrestored"), CanBubble::eYes, Cancelable::eYes); event->SetTrusted(true); mOffscreenCanvas->DispatchEvent(*event); } return; } } void WebGLContext::ForceLoseContext(bool simulateLosing) { printf_stderr("WebGL(%p)::ForceLoseContext\n", this); MOZ_ASSERT(gl); mContextStatus = ContextStatus::LostAwaitingEvent; mWebGLError = LOCAL_GL_CONTEXT_LOST_WEBGL; // Burn it all! DestroyResourcesAndContext(); mLastLossWasSimulated = simulateLosing; // Queue up a task, since we know the status changed. EnqueueUpdateContextLossStatus(); } void WebGLContext::ForceRestoreContext() { printf_stderr("WebGL(%p)::ForceRestoreContext\n", this); mContextStatus = ContextStatus::LostAwaitingRestore; mAllowContextRestore = true; // Hey, you did say 'force'. // Queue up a task, since we know the status changed. EnqueueUpdateContextLossStatus(); } already_AddRefed WebGLContext::GetSurfaceSnapshot( gfxAlphaType* const out_alphaType) { const FuncScope funcScope(*this, ""); if (IsContextLost()) return nullptr; if (!BindDefaultFBForRead()) return nullptr; const auto surfFormat = mOptions.alpha ? SurfaceFormat::B8G8R8A8 : SurfaceFormat::B8G8R8X8; const auto& size = mDefaultFB->mSize; RefPtr surf; surf = Factory::CreateDataSourceSurfaceWithStride(size, surfFormat, size.width * 4); if (NS_WARN_IF(!surf)) return nullptr; ReadPixelsIntoDataSurface(gl, surf); gfxAlphaType alphaType; if (!mOptions.alpha) { alphaType = gfxAlphaType::Opaque; } else if (mOptions.premultipliedAlpha) { alphaType = gfxAlphaType::Premult; } else { alphaType = gfxAlphaType::NonPremult; } if (out_alphaType) { *out_alphaType = alphaType; } else { // Expects Opaque or Premult if (alphaType == gfxAlphaType::NonPremult) { gfxUtils::PremultiplyDataSurface(surf, surf); } } RefPtr dt = Factory::CreateDrawTarget( gfxPlatform::GetPlatform()->GetSoftwareBackend(), size, SurfaceFormat::B8G8R8A8); if (!dt) return nullptr; dt->SetTransform(Matrix::Translation(0.0, size.height).PreScale(1.0, -1.0)); const gfx::Rect rect{0, 0, float(size.width), float(size.height)}; dt->DrawSurface(surf, rect, rect, DrawSurfaceOptions(), DrawOptions(1.0f, CompositionOp::OP_SOURCE)); return dt->Snapshot(); } void WebGLContext::DidRefresh() { if (gl) { gl->FlushIfHeavyGLCallsSinceLastFlush(); } } //////////////////////////////////////////////////////////////////////////////// gfx::IntSize WebGLContext::DrawingBufferSize() { const gfx::IntSize zeros{0, 0}; if (IsContextLost()) return zeros; if (!EnsureDefaultFB()) return zeros; return 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 = 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(const uint8_t bitmask) const { if (mDriverColorMask != bitmask) { mDriverColorMask = bitmask; gl->fColorMask( bool(mDriverColorMask & (1 << 0)), bool(mDriverColorMask & (1 << 1)), bool(mDriverColorMask & (1 << 2)), bool(mDriverColorMask & (1 << 3))); } } //////////////////////////////////////////////////////////////////////////////// ScopedDrawCallWrapper::ScopedDrawCallWrapper(WebGLContext& webgl) : mWebGL(webgl) { uint8_t driverColorMask = mWebGL.mColorWriteMask; bool driverDepthTest = mWebGL.mDepthTestEnabled; bool driverStencilTest = mWebGL.mStencilTestEnabled; const auto& fb = mWebGL.mBoundDrawFramebuffer; if (!fb) { if (mWebGL.mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE) { driverColorMask = 0; // Is this well-optimized enough for depth-first // rendering? } else { driverColorMask &= ~(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(driverColorMask); 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.Invalidate(); 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); } //////////////////////////////////////// ScopedUnpackReset::ScopedUnpackReset(const WebGLContext* const webgl) : mWebGL(webgl) { const auto& gl = mWebGL->gl; // clang-format off if (mWebGL->mPixelStore_UnpackAlignment != 4) gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); if (mWebGL->IsWebGL2()) { if (mWebGL->mPixelStore_UnpackRowLength != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH , 0); if (mWebGL->mPixelStore_UnpackImageHeight != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, 0); if (mWebGL->mPixelStore_UnpackSkipPixels != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS , 0); if (mWebGL->mPixelStore_UnpackSkipRows != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS , 0); if (mWebGL->mPixelStore_UnpackSkipImages != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES , 0); if (mWebGL->mBoundPixelUnpackBuffer) gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, 0); } // clang-format on } ScopedUnpackReset::~ScopedUnpackReset() { const auto& gl = mWebGL->gl; // clang-format off gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, mWebGL->mPixelStore_UnpackAlignment); if (mWebGL->IsWebGL2()) { gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH , mWebGL->mPixelStore_UnpackRowLength ); gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, mWebGL->mPixelStore_UnpackImageHeight); gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS , mWebGL->mPixelStore_UnpackSkipPixels ); gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS , mWebGL->mPixelStore_UnpackSkipRows ); gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES , mWebGL->mPixelStore_UnpackSkipImages ); GLuint pbo = 0; if (mWebGL->mBoundPixelUnpackBuffer) { pbo = mWebGL->mBoundPixelUnpackBuffer->mGLName; } gl->fBindBuffer(LOCAL_GL_PIXEL_UNPACK_BUFFER, pbo); } // clang-format on } //////////////////// 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)); } } //////////////////// static GLenum IsVirtualBufferTarget(GLenum target) { return target != LOCAL_GL_ELEMENT_ARRAY_BUFFER; } ScopedLazyBind::ScopedLazyBind(gl::GLContext* const gl, const GLenum target, const WebGLBuffer* const buf) : mGL(gl), mTarget(IsVirtualBufferTarget(target) ? target : 0) { if (mTarget) { mGL->fBindBuffer(mTarget, buf ? buf->mGLName : 0); } } ScopedLazyBind::~ScopedLazyBind() { if (mTarget) { mGL->fBindBuffer(mTarget, 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(intWrite0).isValid() || !CheckedInt(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; } //////////////////////////////////////////////////////////////////////////////// CheckedUint32 WebGLContext::GetUnpackSize(bool isFunc3D, uint32_t width, uint32_t height, uint32_t depth, uint8_t bytesPerPixel) { if (!width || !height || !depth) return 0; //////////////// const auto& maybeRowLength = mPixelStore_UnpackRowLength; const auto& maybeImageHeight = mPixelStore_UnpackImageHeight; const auto usedPixelsPerRow = CheckedUint32(mPixelStore_UnpackSkipPixels) + width; const auto stridePixelsPerRow = (maybeRowLength ? CheckedUint32(maybeRowLength) : usedPixelsPerRow); const auto usedRowsPerImage = CheckedUint32(mPixelStore_UnpackSkipRows) + height; const auto strideRowsPerImage = (maybeImageHeight ? CheckedUint32(maybeImageHeight) : usedRowsPerImage); const uint32_t skipImages = (isFunc3D ? mPixelStore_UnpackSkipImages : 0); const CheckedUint32 usedImages = CheckedUint32(skipImages) + depth; //////////////// CheckedUint32 strideBytesPerRow = bytesPerPixel * stridePixelsPerRow; strideBytesPerRow = RoundUpToMultipleOf(strideBytesPerRow, mPixelStore_UnpackAlignment); const CheckedUint32 strideBytesPerImage = strideBytesPerRow * strideRowsPerImage; //////////////// CheckedUint32 usedBytesPerRow = bytesPerPixel * usedPixelsPerRow; // Don't round this to the alignment, since alignment here is really just used // for establishing stride, particularly in WebGL 1, where you can't set // ROW_LENGTH. CheckedUint32 totalBytes = strideBytesPerImage * (usedImages - 1); totalBytes += strideBytesPerRow * (usedRowsPerImage - 1); totalBytes += usedBytesPerRow; return totalBytes; } #if defined(MOZ_WIDGET_ANDROID) already_AddRefed WebGLContext::GetVRFrame() { if (!gl) return nullptr; EnsureVRReady(); // Create a custom GLScreenBuffer for VR. if (!mVRScreen) { auto caps = gl->Screen()->mCaps; mVRScreen = GLScreenBuffer::Create(gl, gfx::IntSize(1, 1), caps); RefPtr imageBridge = ImageBridgeChild::GetSingleton(); if (imageBridge) { TextureFlags flags = TextureFlags::ORIGIN_BOTTOM_LEFT; UniquePtr factory = gl::GLScreenBuffer::CreateFactory(gl, caps, imageBridge.get(), flags); mVRScreen->Morph(std::move(factory)); } } // Swap buffers as though composition has occurred. // We will then share the resulting front buffer to be submitted to the VR // compositor. BeginComposition(mVRScreen.get()); EndComposition(); if (IsContextLost()) return nullptr; RefPtr sharedSurface = mVRScreen->Front(); if (!sharedSurface || !sharedSurface->Surf()) return nullptr; // Make sure that the WebGL buffer is committed to the attached SurfaceTexture // on Android. sharedSurface->Surf()->ProducerAcquire(); sharedSurface->Surf()->Commit(); sharedSurface->Surf()->ProducerRelease(); return sharedSurface.forget(); } #else already_AddRefed WebGLContext::GetVRFrame() { if (!gl) return nullptr; EnsureVRReady(); /** * Swap buffers as though composition has occurred. * We will then share the resulting front buffer to be submitted to the VR * compositor. */ BeginComposition(); EndComposition(); gl::GLScreenBuffer* screen = gl->Screen(); if (!screen) return nullptr; RefPtr sharedSurface = screen->Front(); if (!sharedSurface) return nullptr; return sharedSurface.forget(); } #endif // ifdefined(MOZ_WIDGET_ANDROID) void WebGLContext::EnsureVRReady() { if (mVRReady) { return; } // Make not composited canvases work with WebVR. See bug #1492554 // WebGLContext::InitializeCanvasRenderer is only called when the 2D // compositor renders a WebGL canvas for the first time. This causes canvases // not added to the DOM not to work properly with WebVR. Here we mimic what // InitializeCanvasRenderer does internally as a workaround. const auto imageBridge = ImageBridgeChild::GetSingleton(); if (imageBridge) { const auto caps = gl->Screen()->mCaps; auto flags = TextureFlags::ORIGIN_BOTTOM_LEFT; if (!IsPremultAlpha() && mOptions.alpha) { flags |= TextureFlags::NON_PREMULTIPLIED; } auto factory = gl::GLScreenBuffer::CreateFactory(gl, caps, imageBridge.get(), flags); gl->Screen()->Morph(std::move(factory)); #if defined(MOZ_WIDGET_ANDROID) // On Android we are using a different GLScreenBuffer for WebVR, so we need // a resize here because PresentScreenBuffer() may not be called for the // gl->Screen() after we set the new factory. gl->Screen()->Resize(DrawingBufferSize()); #endif mVRReady = true; } } //////////////////////////////////////////////////////////////////////////////// static inline size_t SizeOfViewElem(const dom::ArrayBufferView& view) { const auto& elemType = view.Type(); if (elemType == js::Scalar::MaxTypedArrayViewType) // DataViews. return 1; return js::Scalar::byteSize(elemType); } bool WebGLContext::ValidateArrayBufferView(const dom::ArrayBufferView& view, GLuint elemOffset, GLuint elemCountOverride, const GLenum errorEnum, uint8_t** const out_bytes, size_t* const out_byteLen) const { view.ComputeLengthAndData(); uint8_t* const bytes = view.DataAllowShared(); const size_t byteLen = view.LengthAllowShared(); const auto& elemSize = SizeOfViewElem(view); size_t elemCount = byteLen / elemSize; if (elemOffset > elemCount) { GenerateError(errorEnum, "Invalid offset into ArrayBufferView."); return false; } elemCount -= elemOffset; if (elemCountOverride) { if (elemCountOverride > elemCount) { GenerateError(errorEnum, "Invalid sub-length for ArrayBufferView."); return false; } elemCount = elemCountOverride; } *out_bytes = bytes + (elemOffset * elemSize); *out_byteLen = elemCount * elemSize; return true; } //// void WebGLContext::UpdateMaxDrawBuffers() { mGLMaxColorAttachments = gl->GetIntAs(LOCAL_GL_MAX_COLOR_ATTACHMENTS); mGLMaxDrawBuffers = gl->GetIntAs(LOCAL_GL_MAX_DRAW_BUFFERS); // WEBGL_draw_buffers: // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater // than or // equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter." mGLMaxDrawBuffers = std::min(mGLMaxDrawBuffers, mGLMaxColorAttachments); } // -- const char* WebGLContext::FuncName() const { const char* ret; if (MOZ_LIKELY(mFuncScope)) { ret = mFuncScope->mFuncName; } else { MOZ_ASSERT(false); ret = ""; } return ret; } // - WebGLContext::FuncScope::FuncScope(const WebGLContext& webgl, const char* const funcName) : mWebGL(webgl), mFuncName(bool(mWebGL.mFuncScope) ? nullptr : funcName) { if (MOZ_UNLIKELY(!mFuncName)) { #ifdef DEBUG mStillNeedsToCheckContextLost = false; #endif return; } mWebGL.mFuncScope = this; } WebGLContext::FuncScope::~FuncScope() { if (MOZ_UNLIKELY(!mFuncName)) return; MOZ_ASSERT(!mStillNeedsToCheckContextLost); mWebGL.mFuncScope = nullptr; } bool WebGLContext::IsContextLost() const { if (MOZ_LIKELY(mFuncScope)) { mFuncScope->OnCheckContextLost(); } return mContextStatus != ContextStatus::NotLost; } // -- bool WebGLContext::ValidateIsObject( const WebGLDeletableObject* const object) const { if (IsContextLost()) return false; if (!object) return false; if (!object->IsCompatibleWithContext(this)) return false; if (object->IsDeleted()) return false; return true; } bool WebGLContext::ValidateDeleteObject( const WebGLDeletableObject* const object) { if (IsContextLost()) return false; if (!object) return false; if (!ValidateObjectAllowDeleted("obj", *object)) return false; if (object->IsDeleteRequested()) return false; return true; } bool WebGLContext::ShouldResistFingerprinting() const { if (NS_IsMainThread()) { if (mCanvasElement) { // If we're constructed from a canvas element return nsContentUtils::ShouldResistFingerprinting(GetOwnerDoc()); } if (mOffscreenCanvas->GetOwnerGlobal()) { // If we're constructed from an offscreen canvas return nsContentUtils::ShouldResistFingerprinting( mOffscreenCanvas->GetOwnerGlobal()->PrincipalOrNull()); } // Last resort, just check the global preference return nsContentUtils::ShouldResistFingerprinting(); } dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(workerPrivate); return nsContentUtils::ShouldResistFingerprinting(workerPrivate); } // -- webgl::AvailabilityRunnable* WebGLContext::EnsureAvailabilityRunnable() { if (!mAvailabilityRunnable) { RefPtr runnable = new webgl::AvailabilityRunnable(this); Document* document = GetOwnerDoc(); if (document) { document->Dispatch(TaskCategory::Other, runnable.forget()); } else { NS_DispatchToCurrentThread(runnable.forget()); } } return mAvailabilityRunnable; } webgl::AvailabilityRunnable::AvailabilityRunnable(WebGLContext* const webgl) : Runnable("webgl::AvailabilityRunnable"), mWebGL(webgl) { mWebGL->mAvailabilityRunnable = this; } webgl::AvailabilityRunnable::~AvailabilityRunnable() { MOZ_ASSERT(mQueries.empty()); MOZ_ASSERT(mSyncs.empty()); } nsresult webgl::AvailabilityRunnable::Run() { for (const auto& cur : mQueries) { cur->mCanBeAvailable = true; } mQueries.clear(); for (const auto& cur : mSyncs) { cur->mCanBeAvailable = true; } mSyncs.clear(); mWebGL->mAvailabilityRunnable = nullptr; return NS_OK; } // --------------- namespace webgl { /*static*/ std::shared_ptr DynDGpuManager::Get() { #ifndef XP_MACOSX if (true) return nullptr; #endif static std::weak_ptr sCurrent; auto ret = sCurrent.lock(); if (!ret) { ret.reset(new DynDGpuManager); sCurrent = ret; } return ret; } DynDGpuManager::DynDGpuManager() : mMutex("DynDGpuManager") {} DynDGpuManager::~DynDGpuManager() = default; void DynDGpuManager::SetState(const MutexAutoLock&, const State newState) { if (gfxEnv::GpuSwitchingSpew()) { printf_stderr( "[MOZ_GPU_SWITCHING_SPEW] DynDGpuManager::SetState(%u -> %u)\n", uint32_t(mState), uint32_t(newState)); } if (newState == State::Active) { if (!mDGpuContext) { const auto flags = gl::CreateContextFlags::HIGH_POWER; nsCString failureId; mDGpuContext = gl::GLContextProvider::CreateHeadless(flags, &failureId); } } else { mDGpuContext = nullptr; } mState = newState; } void DynDGpuManager::ReportActivity( const std::shared_ptr& strong) { MOZ_ASSERT(strong.get() == this); const MutexAutoLock lock(mMutex); if (mActivityThisTick) return; mActivityThisTick = true; // Promote! switch (mState) { case State::Inactive: SetState(lock, State::Primed); DispatchTick(strong); // Initial tick break; case State::Primed: SetState(lock, State::Active); break; case State::Active: if (!mDGpuContext) { SetState(lock, State::Active); } break; } } void DynDGpuManager::Tick(const std::shared_ptr& strong) { MOZ_ASSERT(strong.get() == this); const MutexAutoLock lock(mMutex); MOZ_ASSERT(mState != State::Inactive); if (!mActivityThisTick) { SetState(lock, State::Inactive); return; } mActivityThisTick = false; // reset DispatchTick(strong); } void DynDGpuManager::DispatchTick( const std::shared_ptr& strong) { MOZ_ASSERT(strong.get() == this); const auto fnTick = [strong]() { strong->Tick(strong); }; already_AddRefed event = NS_NewRunnableFunction("DynDGpuManager fnWeakTick", fnTick); NS_DelayedDispatchToCurrentThread(std::move(event), TICK_MS); } } // namespace webgl //////////////////////////////////////////////////////////////////////////////// // XPCOM goop void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback, const std::vector& field, const char* name, uint32_t flags) { for (const auto& cur : field) { ImplCycleCollectionTraverse(callback, cur.mBufferBinding, name, flags); } } void ImplCycleCollectionUnlink(std::vector& field) { field.clear(); } //// NS_IMPL_CYCLE_COLLECTING_ADDREF(WebGLContext) NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext) NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE( WebGLContext, mCanvasElement, mOffscreenCanvas, mExtensions, mBound2DTextures, mBoundCubeMapTextures, mBound3DTextures, mBound2DArrayTextures, mBoundSamplers, mBoundArrayBuffer, mBoundCopyReadBuffer, mBoundCopyWriteBuffer, mBoundPixelPackBuffer, mBoundPixelUnpackBuffer, mBoundTransformFeedback, mBoundTransformFeedbackBuffer, mBoundUniformBuffer, mCurrentProgram, mBoundDrawFramebuffer, mBoundReadFramebuffer, mBoundRenderbuffer, mBoundVertexArray, mDefaultVertexArray, mQuerySlot_SamplesPassed, mQuerySlot_TFPrimsWritten, mQuerySlot_TimeElapsed) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebGLContext) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) // If the exact way we cast to nsISupports here ever changes, fix our // ToSupports() method. NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsICanvasRenderingContextInternal) NS_INTERFACE_MAP_END } // namespace mozilla