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