/* -*- 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 #include "AccessCheck.h" #include "CompositableHost.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/EnumeratedArrayCycleCollection.h" #include "mozilla/EnumeratedRange.h" #include "mozilla/Preferences.h" #include "mozilla/ProcessPriorityManager.h" #include "mozilla/ScopeExit.h" #include "mozilla/Services.h" #include "mozilla/StaticPrefs_webgl.h" #include "mozilla/Telemetry.h" #include "nsContentUtils.h" #include "nsDisplayList.h" #include "nsError.h" #include "nsIClassInfoImpl.h" #include "nsIGfxInfo.h" #include "nsIWidget.h" #include "nsServiceManagerUtils.h" #include "SharedSurfaceGL.h" #include "SVGObserverUtils.h" #include "prenv.h" #include "ScopedGLHelpers.h" #include "VRManagerChild.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 "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 { 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; } 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 &= (xrCompatible == r.xrCompatible); eq &= (powerPreference == r.powerPreference); return eq; } static std::list sWebglLru; WebGLContext::LruPosition::LruPosition() : mItr(sWebglLru.end()) {} // NOLINT WebGLContext::LruPosition::LruPosition(WebGLContext& context) : mItr(sWebglLru.insert(sWebglLru.end(), &context)) {} void WebGLContext::LruPosition::reset() { const auto end = sWebglLru.end(); if (mItr != end) { sWebglLru.erase(mItr); mItr = 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), 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, ""); if (mOptions.antialias && !StaticPrefs::webgl_msaa_force()) { const nsCOMPtr gfxInfo = services::GetGfxInfo(); nsCString blocklistId; if (IsFeatureInBlacklist(gfxInfo, nsIGfxInfo::FEATURE_WEBGL_MSAA, &blocklistId)) { GenerateWarning( "getContext: Disallowing antialiased backbuffers due to " "blacklisting. (%s)", blocklistId.BeginReading()); mOptions.antialias = false; } } } WebGLContext::~WebGLContext() { DestroyResourcesAndContext(); } 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; 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(); } ////// 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::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) LoseContext(); } // // nsICanvasRenderingContextInternal // 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) { const FuncScope funcScope(*this, ""); // 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 (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; } } #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(*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; } // If we've already drawn, we should commit the current buffer. PresentScreenBuffer(); if (IsContextLost()) { GenerateWarning("WebGL context was lost due to swap failure."); return; } // Kill our current default fb(s), for later lazy allocation. mRequestedSize = requestedSize; mDefaultFB = nullptr; mResetLayer = true; // New size means new Layer. } UniquePtr WebGLContext::CreateFormatUsage( gl::GLContext* gl) const { return webgl::FormatUsageAuthority::CreateForWebGL1(gl); } /*static*/ RefPtr WebGLContext::Create(HostWebGLContext& host, const webgl::InitContextDesc& desc, webgl::InitContextResult* const out) { nsCString failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_UNKOWN"); const bool forceEnabled = StaticPrefs::webgl_force_enabled(); ScopedGfxFeatureReporter reporter("WebGL", forceEnabled); auto res = [&]() -> Result, 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 = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_SAFEMODE"); } else { failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_DISABLED"); } return Err("WebGL is currently disabled."); } if (desc.options.failIfMajorPerformanceCaveat) { nsCOMPtr gfxInfo = services::GetGfxInfo(); if (!HasAcceleratedLayers(gfxInfo)) { failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_PERF_CAVEAT"); return Err( "failIfMajorPerformanceCaveat: Compositor is not" " hardware-accelerated."); } } // Alright, now let's start trying. RefPtr webgl; if (desc.isWebgl2) { webgl = new WebGL2Context(host, desc); } else { webgl = new WebGLContext(host, desc); } MOZ_ASSERT(!webgl->gl); std::vector 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, 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"); return Err(text.BeginReading()); } MOZ_ASSERT(webgl->gl); if (desc.options.failIfMajorPerformanceCaveat) { if (webgl->gl->IsWARP()) { failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_PERF_WARP"); return Err( "failIfMajorPerformanceCaveat: Driver is not" " hardware-accelerated."); } #ifdef XP_WIN if (webgl->gl->GetContextType() == gl::GLContextType::WGL && !gl::sWGLLib.HasDXInterop2()) { failureId = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_DXGL_INTEROP2"); return Err("Caveat: 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 = NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_BACKBUFFER"); return Err("Initializing WebGL backbuffer failed."); } return webgl; }(); if (res.isOk()) { failureId = NS_LITERAL_CSTRING("SUCCESS"); } 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()); } out->options = webgl->mOptions; out->limits = *webgl->mLimits; 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; 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& aCompositableHost) { mCompositableHost = aCompositableHost; } void WebGLContext::LoseLruContextIfLimitExceeded() { 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. BumpLru(); { size_t forPrincipal = 0; for (const auto& context : sWebglLru) { 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 : sWebglLru) { if (context->mPrincipalKey == mPrincipalKey) { MOZ_ASSERT(context != this); context->LoseContext(webgl::ContextLossReason::None); forPrincipal -= 1; break; } } } } auto total = sWebglLru.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 = sWebglLru.front(); MOZ_ASSERT(context != this); context->LoseContext(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(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() { 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"); BumpLru(); } void WebGLContext::BlitBackbufferToCurDriverFB() const { DoColorMask(0x0f); if (mScissorTestEnabled) { gl->fDisable(LOCAL_GL_SCISSOR_TEST); } [&]() { const auto& size = mDefaultFB->mSize; if (gl->IsSupported(gl::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( gl::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); } } // TODO: (JG) I think this should be removed, the Client should manage // TextureClients, and imperitively tell the Host what to render to. Maybe WebGLContext::InitializeCanvasRenderer( layers::LayersBackend backend) { if (!gl) { return Nothing(); } ICRData ret; ret.size = {DrawingBufferSize().x, DrawingBufferSize().y}; ret.hasAlpha = mOptions.alpha; ret.isPremultAlpha = IsPremultAlpha(); auto flags = layers::TextureFlags::ORIGIN_BOTTOM_LEFT; if ((!IsPremultAlpha()) && mOptions.alpha) { flags |= layers::TextureFlags::NON_PREMULTIPLIED; } // NB: This is weak. Creating TextureClient objects in the host-side // WebGLContext class... but these are different concepts of host/client. // Host/ClientWebGLContext represent cross-process communication but // TextureHost/Client represent synchronous texture access, which can // be uniprocess and, for us, is. Also note that TextureClient couldn't // be in the content process like ClientWebGLContext since TextureClient // uses a GL context. UniquePtr factory = gl::GLScreenBuffer::CreateFactory( gl, gl->Caps(), nullptr, backend, gl->IsANGLE(), flags); mBackend = backend; if (!factory) { // Absolutely must have a factory here, so create a basic one factory = MakeUnique(gl, gl->Caps(), flags); mBackend = layers::LayersBackend::LAYERS_BASIC; } gl->Screen()->Morph(std::move(factory)); bool needsResize = false; #if defined(MOZ_WIDGET_ANDROID) // If drawing buffer size and screen size are equal, the first back buffer // will still be the one created with SurfaceFactory_Basic factory. // We resize here to ensure that GLScreenBuffer back buffer // is created using the newly attached factory. // See bug #1617751 needsResize = true; #endif if (needsResize) { const auto& size = DrawingBufferSize(); gl->Screen()->Resize({size.x, size.y}); } mVRReady = true; return Some(ret); } // - template constexpr auto MakeArray(Args... args) -> std::array { return {{static_cast(args)...}}; } // - // For an overview of how WebGL compositing works, see: // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing bool WebGLContext::PresentScreenBuffer(gl::GLScreenBuffer* const targetScreen) { const FuncScope funcScope(*this, ""); if (IsContextLost()) return false; mDrawCallsSinceLastFlush = 0; if (!mShouldPresent) return false; 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."); LoseContext(); 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."); LoseContext(); return false; } if (!mOptions.preserveDrawingBuffer) { if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) { gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB); constexpr auto attachments = MakeArray( LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT); gl->fInvalidateFramebuffer(LOCAL_GL_FRAMEBUFFER, attachments.size(), attachments.data()); } mDefaultFB_IsInvalid = true; } mResolvedDefaultFB = nullptr; mShouldPresent = false; OnEndOfFrame(); return true; } RefPtr 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 WriteFrontToFile(gl::GLContext* gl, gl::GLScreenBuffer* screen, const char* fname, bool needsPremult) { auto frontbuffer = screen->Front()->Surf(); const auto& readSize = frontbuffer->mSize; auto format = frontbuffer->mHasAlpha ? gfx::SurfaceFormat::B8G8R8A8 : gfx::SurfaceFormat::B8G8R8X8; RefPtr resultSurf = GetTempSurface(readSize, format); if (NS_WARN_IF(!resultSurf)) { MOZ_ASSERT_UNREACHABLE("FAIL"); return; } if (!gl->Readback(frontbuffer, resultSurf)) { NS_WARNING("Failed to read back canvas surface."); MOZ_ASSERT_UNREACHABLE("FAIL"); return; } if (needsPremult) { gfxUtils::PremultiplyDataSurface(resultSurf, resultSurf); } MOZ_ASSERT(resultSurf); gfxUtils::WriteAsPNG(resultSurf, fname); } bool WebGLContext::Present() { if (!PresentScreenBuffer()) { return false; } if (XRE_IsContentProcess()) { // That's all! return true; } // Set the CompositableHost to use the front buffer as the display, auto flags = layers::TextureFlags::ORIGIN_BOTTOM_LEFT; if ((!IsPremultAlpha()) && mOptions.alpha) { flags |= layers::TextureFlags::NON_PREMULTIPLIED; } const auto& screen = gl->Screen(); if (!screen->Front()->Surf()) { GenerateWarning( "Present failed due to missing front buffer. Losing context."); LoseContext(); return false; } if (mBackend == layers::LayersBackend::LAYERS_NONE) { GenerateWarning( "Present was not given a valid compositor layer type. Losing context."); LoseContext(); return false; } // TODO: I probably need to hold onto screen->Front()->Surf() somehow layers::SurfaceDescriptor surfaceDescriptor; screen->Front()->Surf()->ToSurfaceDescriptor(&surfaceDescriptor); if (!mCompositableHost) { return false; } wr::MaybeExternalImageId noExternalImageId = Nothing(); RefPtr host = layers::TextureHost::Create( surfaceDescriptor, null_t(), nullptr, mBackend, flags, noExternalImageId); if (!host) { GenerateWarning("Present failed to create TextuteHost. Losing context."); LoseContext(); return false; } AutoTArray textures; const auto t = textures.AppendElement(); t->mTexture = host; t->mTimeStamp = TimeStamp::Now(); t->mPictureRect = nsIntRect(nsIntPoint(0, 0), nsIntSize(host->GetSize())); t->mFrameID = 0; t->mProducerID = 0; mCompositableHost->UseTextureHost(textures); return true; } 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::LoseContext(const webgl::ContextLossReason reason) { printf_stderr("WebGL(%p)::LoseContext(%u)\n", this, static_cast(reason)); mIsContextLost = true; mHost->OnContextLoss(reason); } 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(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.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.mUnpackAlignment != 4) gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4); if (mWebGL->IsWebGL2()) { if (mWebGL->mPixelStore.mUnpackRowLength != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH , 0); if (mWebGL->mPixelStore.mUnpackImageHeight != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, 0); if (mWebGL->mPixelStore.mUnpackSkipPixels != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS , 0); if (mWebGL->mPixelStore.mUnpackSkipRows != 0) gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS , 0); if (mWebGL->mPixelStore.mUnpackSkipImages != 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.mUnpackAlignment); if (mWebGL->IsWebGL2()) { gl->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH , mWebGL->mPixelStore.mUnpackRowLength ); gl->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, mWebGL->mPixelStore.mUnpackImageHeight); gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS , mWebGL->mPixelStore.mUnpackSkipPixels ); gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS , mWebGL->mPixelStore.mUnpackSkipRows ); gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES , mWebGL->mPixelStore.mUnpackSkipImages ); 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)); } } //////////////////// 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(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.mUnpackRowLength; const auto& maybeImageHeight = mPixelStore.mUnpackImageHeight; const auto usedPixelsPerRow = CheckedUint32(mPixelStore.mUnpackSkipPixels) + width; const auto stridePixelsPerRow = (maybeRowLength ? CheckedUint32(maybeRowLength) : usedPixelsPerRow); const auto usedRowsPerImage = CheckedUint32(mPixelStore.mUnpackSkipRows) + height; const auto strideRowsPerImage = (maybeImageHeight ? CheckedUint32(maybeImageHeight) : usedRowsPerImage); const uint32_t skipImages = (isFunc3D ? mPixelStore.mUnpackSkipImages : 0); const CheckedUint32 usedImages = CheckedUint32(skipImages) + depth; //////////////// CheckedUint32 strideBytesPerRow = bytesPerPixel * stridePixelsPerRow; strideBytesPerRow = RoundUpToMultipleOf(strideBytesPerRow, mPixelStore.mUnpackAlignment); 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; } void WebGLContext::ClearVRFrame() { #if defined(MOZ_WIDGET_ANDROID) mVRScreen = nullptr; #endif } RefPtr WebGLContext::GetVRFrame() { if (!gl) return nullptr; EnsureVRReady(); UniquePtr* maybeVrScreen = nullptr; #if defined(MOZ_WIDGET_ANDROID) maybeVrScreen = &mVRScreen; #endif RefPtr sharedSurface; if (maybeVrScreen) { auto& vrScreen = *maybeVrScreen; // Create a custom GLScreenBuffer for VR. if (!vrScreen) { auto caps = gl->Screen()->mCaps; vrScreen = gl::GLScreenBuffer::Create(gl, gfx::IntSize(1, 1), caps); RefPtr imageBridge = layers::ImageBridgeChild::GetSingleton(); if (imageBridge) { layers::TextureFlags flags = layers::TextureFlags::ORIGIN_BOTTOM_LEFT; UniquePtr factory = gl::GLScreenBuffer::CreateFactory(gl, caps, imageBridge.get(), flags); vrScreen->Morph(std::move(factory)); } } MOZ_ASSERT(vrScreen); // Swap buffers as though composition has occurred. // We will then share the resulting front buffer to be submitted to the VR // compositor. PresentScreenBuffer(vrScreen.get()); if (IsContextLost()) return nullptr; sharedSurface = vrScreen->Front(); if (!sharedSurface || !sharedSurface->Surf() || !sharedSurface->Surf()->IsBufferAvailable()) 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(); } else { /** * Swap buffers as though composition has occurred. * We will then share the resulting front buffer to be submitted to the VR * compositor. */ PresentScreenBuffer(); gl::GLScreenBuffer* screen = gl->Screen(); if (!screen) return nullptr; sharedSurface = screen->Front(); if (!sharedSurface) return nullptr; } return sharedSurface; } 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 caps = gl->Screen()->mCaps; auto flags = layers::TextureFlags::ORIGIN_BOTTOM_LEFT; if (!IsPremultAlpha() && mOptions.alpha) { flags |= layers::TextureFlags::NON_PREMULTIPLIED; } RefPtr imageBridge = layers::ImageBridgeChild::GetSingleton(); if (!imageBridge) { return; } auto factory = gl::GLScreenBuffer::CreateFactory(gl, caps, imageBridge.get(), flags); gl->Screen()->Morph(std::move(factory)); bool needsResize = false; #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. needsResize = true; #endif if (needsResize) { const auto& size = DrawingBufferSize(); gl->Screen()->Resize({size.x, size.y}); } mVRReady = true; } //////////////////////////////////////////////////////////////////////////////// const char* WebGLContext::FuncName() const { const char* ret; if (MOZ_LIKELY(mFuncScope)) { ret = mFuncScope->mFuncName; } else { MOZ_ASSERT(false, "FuncScope not on stack!"); ret = ""; } 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 { if (!mNotLost) return false; const auto& options = mNotLost->info.options; return options.xrCompatible; } already_AddRefed ClientWebGLContext::MakeXRCompatible( ErrorResult& aRv) { const FuncScope funcScope(*this, "MakeXRCompatible"); nsCOMPtr global; // TODO: Bug 1596921 // Should use nsICanvasRenderingContextInternal::GetParentObject // once it has been updated to work in the offscreencanvas case if (mCanvasElement) { global = GetOwnerDoc()->GetScopeObject(); } else if (mOffscreenCanvas) { global = mOffscreenCanvas->GetOwnerGlobal(); } if (!global) { aRv.ThrowInvalidAccessError( "Using a WebGL context that is not attached to either a canvas or an " "OffscreenCanvas"); return nullptr; } RefPtr 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. promise->MaybeResolveWithUndefined(); return promise.forget(); } // -- webgl::AvailabilityRunnable* WebGLContext::EnsureAvailabilityRunnable() { if (!mAvailabilityRunnable) { RefPtr runnable = new webgl::AvailabilityRunnable(this); 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; } // - void WebGLContext::GenerateErrorImpl(const GLenum err, const std::string& text) const { if (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? if (!ShouldGenerateWarnings()) return; mHost->JsWarning(text); mWarningCount += 1; if (!ShouldGenerateWarnings()) { auto info = std::string( "WebGL: No further warnings will be reported for this WebGL " "context. (already reported "); info += std::to_string(mWarningCount); info += " warnings)"; mHost->JsWarning(info); } } // - Maybe WebGLContext::GetString(const GLenum pname) const { const WebGLContext::FuncScope funcScope(*this, "getParameter"); if (IsContextLost()) return {}; switch (pname) { case LOCAL_GL_EXTENSIONS: { if (!gl->IsCoreProfile()) { const auto rawExt = (const char*)gl->fGetString(LOCAL_GL_EXTENSIONS); return Some(std::string(rawExt)); } std::string ret; const auto& numExts = gl->GetIntAs(LOCAL_GL_NUM_EXTENSIONS); for (GLuint i = 0; i < numExts; i++) { const auto rawExt = (const char*)gl->fGetStringi(LOCAL_GL_EXTENSIONS, i); 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 Some(std::string(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::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 ExplodeName(const std::string& str) { std::vector ret; static const std::regex kSep("[.[\\]]"); auto itr = std::regex_token_iterator( 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& nameUnmap) { webgl::LinkActiveInfo ret; [&]() { const auto fnGetProgramui = [&](const GLenum pname) { GLint ret = 0; gl.fGetProgramiv(prog, pname, &ret); return static_cast(ret); }; std::vector 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 blockIndexList(count, -1); std::vector blockOffsetList(count, -1); std::vector blockArrayStrideList(count, -1); std::vector blockMatrixStrideList(count, -1); std::vector blockIsRowMajorList(count, 0); if (webgl2 && count) { std::vector 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); // - webgl::ActiveUniformInfo info; info.elemType = elemType; info.elemCount = static_cast(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(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(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(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(elemCount), userName}); } } } // if webgl2 }(); return ret; } 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; return; } ret.translatedSource = info->mObjectCode; ret.log = 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 = 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(); return gl->fGetFragDataLocation(prog.mGLName, mappedName.c_str()); } // - WebGLContextBoundObject::WebGLContextBoundObject(WebGLContext* webgl) : mContext(webgl) {} } // namespace mozilla