/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WebGLContext.h"
#include <algorithm>
#include <bitset>
#include <queue>
#include <regex>
#include "AccessCheck.h"
#include "CompositableHost.h"
#include "gfxConfig.h"
#include "gfxContext.h"
#include "gfxCrashReporterUtils.h"
#include "gfxEnv.h"
#include "gfxPattern.h"
#include "MozFramebuffer.h"
#include "GLBlitHelper.h"
#include "GLContext.h"
#include "GLContextProvider.h"
#include "GLReadTexImageHelper.h"
#include "GLScreenBuffer.h"
#include "ImageContainer.h"
#include "ImageEncoder.h"
#include "LayerUserData.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/ContentChild.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/glean/DomCanvasMetrics.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/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 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;
MOZ_RUNINIT std::list<WebGLContext*> WebGLContext::sLru;
WebGLContext::LruPosition::LruPosition() {
StaticMutexAutoLock lock(sLruMutex);
mItr = sLru.end();
}
// NOLINT
WebGLContext::LruPosition::LruPosition(WebGLContext& context) {
StaticMutexAutoLock lock(sLruMutex);
mItr = sLru.insert(sLru.end(), &context);
}
void WebGLContext::LruPosition::AssignLocked(WebGLContext& aContext) {
ResetLocked();
mItr = sLru.insert(sLru.end(), &aContext);
}
void WebGLContext::LruPosition::ResetLocked() {
const auto end = sLru.end();
if (mItr != end) {
sLru.erase(mItr);
mItr = end;
}
}
void WebGLContext::LruPosition::Reset() {
StaticMutexAutoLock lock(sLruMutex);
ResetLocked();
}
bool WebGLContext::LruPosition::IsInsertedLocked()
const {
return mItr != sLru.end();
}
WebGLContext::WebGLContext(HostWebGLContext* host,
const webgl::InitContextDesc& desc)
: gl(mGL_OnlyClearInDestroyResourcesAndContext),
// const reference
mHost(host),
mResistFingerprinting(desc.resistFingerprinting),
mOptions(desc.options),
mPrincipalKey(desc.principalKey),
mContextLossHandler(
this),
mRequestedSize(desc.size) {
if (host) {
host->mContext =
this;
}
const FuncScope funcScope(*
this,
"");
WebGLMemoryTracker::EnsureRegistered();
}
WebGLContext::~WebGLContext() { DestroyResourcesAndContext(); }
void WebGLContext::DestroyResourcesAndContext() {
if (mRemoteTextureOwner) {
// Clean up any remote textures registered for framebuffer swap chains.
mRemoteTextureOwner->UnregisterAllTextureOwners();
mRemoteTextureOwner = nullptr;
}
if (!gl)
return;
mDefaultFB = nullptr;
mResolvedDefaultFB = nullptr;
mBound2DTextures.Clear();
mBoundCubeMapTextures.Clear();
mBound3DTextures.Clear();
mBound2DArrayTextures.Clear();
mBoundSamplers.Clear();
mBoundArrayBuffer = nullptr;
mBoundCopyReadBuffer = nullptr;
mBoundCopyWriteBuffer = nullptr;
mBoundPixelPackBuffer = nullptr;
mBoundPixelUnpackBuffer = nullptr;
mBoundTransformFeedbackBuffer = nullptr;
mBoundUniformBuffer = nullptr;
mCurrentProgram = nullptr;
mActiveProgramLinkInfo = nullptr;
mBoundDrawFramebuffer = nullptr;
mBoundReadFramebuffer = nullptr;
mBoundVertexArray = nullptr;
mDefaultVertexArray = nullptr;
mBoundTransformFeedback = nullptr;
mDefaultTransformFeedback = nullptr;
mQuerySlot_SamplesPassed = nullptr;
mQuerySlot_TFPrimsWritten = nullptr;
mQuerySlot_TimeElapsed = nullptr;
mIndexedUniformBufferBindings.clear();
//////
if (mEmptyTFO) {
gl->fDeleteTransformFeedbacks(1, &mEmptyTFO);
mEmptyTFO = 0;
}
//////
if (mFakeVertexAttrib0BufferObject) {
gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject);
mFakeVertexAttrib0BufferObject = 0;
}
// disable all extensions except "WEBGL_lose_context". see bug #927969
// spec: http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
for (size_t i = 0; i < size_t(WebGLExtensionID::Max); ++i) {
WebGLExtensionID extension = WebGLExtensionID(i);
if (extension == WebGLExtensionID::WEBGL_lose_context)
continue;
mExtensions[extension] = nullptr;
}
// We just got rid of everything, so the context had better
// have been going away.
if (gl::GLContext::ShouldSpew()) {
printf_stderr(
"--- WebGL context destroyed: %p\n", gl.get());
}
MOZ_ASSERT(gl);
gl->MarkDestroyed();
mGL_OnlyClearInDestroyResourcesAndContext = nullptr;
MOZ_ASSERT(!gl);
}
void ClientWebGLContext::MarkCanvasDirty() {
if (!mCanvasElement && !mOffscreenCanvas)
return;
mFrameCaptureState = FrameCaptureState::DIRTY;
if (mIsCanvasDirty)
return;
mIsCanvasDirty =
true;
if (mCanvasElement) {
SVGObserverUtils::InvalidateDirectRenderingObservers(mCanvasElement);
mCanvasElement->InvalidateCanvasContent(nullptr);
}
else if (mOffscreenCanvas) {
mOffscreenCanvas->QueueCommitToCompositor();
}
}
void WebGLContext::OnMemoryPressure() {
bool shouldLoseContext = mLoseContextOnMemoryPressure;
if (!mCanLoseContextInForeground &&
ProcessPriorityManager::CurrentProcessIsForeground()) {
shouldLoseContext =
false;
}
if (shouldLoseContext) LoseContext();
}
// --
bool WebGLContext::CreateAndInitGL(
bool forceEnabled, std::vector<FailureReason>*
const out_failReasons) {
const FuncScope funcScope(*
this,
"");
// WebGL2 is separately blocked:
if (IsWebGL2() && !forceEnabled) {
FailureReason reason;
if (!gfx::gfxVars::AllowWebgl2()) {
reason.info =
"AllowWebgl2:false restricts context creation on this system.";
out_failReasons->push_back(reason);
GenerateWarning(
"%s", reason.info.BeginReading());
return false;
}
}
auto flags = gl::CreateContextFlags::PREFER_ROBUSTNESS;
if (StaticPrefs::webgl_gl_khr_no_error()) {
flags |= gl::CreateContextFlags::NO_VALIDATION;
}
// -
if (StaticPrefs::webgl_forbid_hardware()) {
flags |= gl::CreateContextFlags::FORBID_HARDWARE;
}
if (StaticPrefs::webgl_forbid_software()) {
flags |= gl::CreateContextFlags::FORBID_SOFTWARE;
}
if (mOptions.forceSoftwareRendering) {
flags |= gl::CreateContextFlags::FORBID_HARDWARE;
flags &= ~gl::CreateContextFlags::FORBID_SOFTWARE;
}
if (forceEnabled) {
flags &= ~gl::CreateContextFlags::FORBID_HARDWARE;
flags &= ~gl::CreateContextFlags::FORBID_SOFTWARE;
}
if ((flags & gl::CreateContextFlags::FORBID_HARDWARE) &&
(flags & gl::CreateContextFlags::FORBID_SOFTWARE)) {
FailureReason reason;
reason.info =
"Both hardware and software were forbidden by config.";
out_failReasons->push_back(reason);
GenerateWarning(
"%s", reason.info.BeginReading());
return false;
}
// -
if (StaticPrefs::webgl_cgl_multithreaded()) {
flags |= gl::CreateContextFlags::PREFER_MULTITHREADED;
}
if (IsWebGL2()) {
flags |= gl::CreateContextFlags::PREFER_ES3;
}
else {
if (StaticPrefs::webgl_1_request_es2()) {
// Request and prefer ES2 context for WebGL1.
flags |= gl::CreateContextFlags::PREFER_EXACT_VERSION;
}
if (!StaticPrefs::webgl_1_allow_core_profiles()) {
flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
}
}
{
auto powerPref = mOptions.powerPreference;
// If "Use hardware acceleration when available" option is disabled:
if (!gfx::gfxConfig::IsEnabled(gfx::Feature::HW_COMPOSITING)) {
powerPref = dom::WebGLPowerPreference::Low_power;
}
const auto overrideVal = StaticPrefs::webgl_power_preference_override();
if (overrideVal > 0) {
powerPref = dom::WebGLPowerPreference::High_performance;
}
else if (overrideVal < 0) {
powerPref = dom::WebGLPowerPreference::Low_power;
}
if (powerPref == dom::WebGLPowerPreference::High_performance) {
flags |= gl::CreateContextFlags::HIGH_POWER;
}
}
if (!gfx::gfxVars::WebglAllowCoreProfile()) {
flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
}
// --
const bool useEGL = PR_GetEnv(
"MOZ_WEBGL_FORCE_EGL");
bool tryNativeGL =
true;
bool tryANGLE =
false;
#ifdef XP_WIN
tryNativeGL =
false;
tryANGLE =
true;
if (StaticPrefs::webgl_disable_wgl()) {
tryNativeGL =
false;
}
if (StaticPrefs::webgl_disable_angle() ||
PR_GetEnv(
"MOZ_WEBGL_FORCE_OPENGL") || useEGL) {
tryNativeGL =
true;
tryANGLE =
false;
}
#endif
if (tryNativeGL && !forceEnabled) {
FailureReason reason;
if (!gfx::gfxVars::WebglAllowWindowsNativeGl()) {
reason.info =
"WebglAllowWindowsNativeGl:false restricts context creation on this "
"system.";
out_failReasons->push_back(reason);
GenerateWarning(
"%s", reason.info.BeginReading());
tryNativeGL =
false;
}
}
// --
using fnCreateT = decltype(gl::GLContextProviderEGL::CreateHeadless);
const auto fnCreate = [&](fnCreateT*
const pfnCreate,
const char*
const info) {
nsCString failureId;
const RefPtr<gl::GLContext> gl = pfnCreate({flags}, &failureId);
if (!gl) {
out_failReasons->push_back(WebGLContext::FailureReason(failureId, info));
}
return gl;
};
const auto newGL = [&]() -> RefPtr<gl::GLContext> {
if (tryNativeGL) {
if (useEGL)
return fnCreate(&gl::GLContextProviderEGL::CreateHeadless,
"useEGL");
const auto ret =
fnCreate(&gl::GLContextProvider::CreateHeadless,
"tryNativeGL");
if (ret)
return ret;
}
if (tryANGLE) {
return fnCreate(&gl::GLContextProviderEGL::CreateHeadless,
"tryANGLE");
}
return nullptr;
}();
if (!newGL) {
out_failReasons->push_back(
FailureReason(
"FEATURE_FAILURE_WEBGL_EXHAUSTED_DRIVERS",
"Exhausted GL driver options."));
return false;
}
// --
FailureReason reason;
mGL_OnlyClearInDestroyResourcesAndContext = newGL;
MOZ_RELEASE_ASSERT(gl);
if (!InitAndValidateGL(&reason)) {
DestroyResourcesAndContext();
MOZ_RELEASE_ASSERT(!gl);
// The fail reason here should be specific enough for now.
out_failReasons->push_back(reason);
return false;
}
const auto val = StaticPrefs::webgl_debug_incomplete_tex_color();
if (val) {
mIncompleteTexOverride.reset(
new gl::Texture(*gl));
const gl::ScopedBindTexture autoBind(gl, mIncompleteTexOverride->name);
const auto heapVal = std::make_unique<uint32_t>(val);
gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, 1, 1, 0,
LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, heapVal.get());
}
return true;
}
// Fallback for resizes:
bool WebGLContext::EnsureDefaultFB() {
if (mDefaultFB) {
MOZ_ASSERT(*uvec2::FromSize(mDefaultFB->mSize) == mRequestedSize);
return true;
}
const bool depthStencil = mOptions.depth || mOptions.stencil;
auto attemptSize = gfx::IntSize{mRequestedSize.x, mRequestedSize.y};
while (attemptSize.width || attemptSize.height) {
attemptSize.width = std::max(attemptSize.width, 1);
attemptSize.height = std::max(attemptSize.height, 1);
[&]() {
if (mOptions.antialias) {
MOZ_ASSERT(!mDefaultFB);
mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, mMsaaSamples,
depthStencil);
if (mDefaultFB)
return;
if (mOptionsFrozen)
return;
}
MOZ_ASSERT(!mDefaultFB);
mDefaultFB = gl::MozFramebuffer::Create(gl, attemptSize, 0, depthStencil);
}();
if (mDefaultFB)
break;
attemptSize.width /= 2;
attemptSize.height /= 2;
}
if (!mDefaultFB) {
GenerateWarning(
"Backbuffer resize failed. Losing context.");
LoseContext();
return false;
}
mDefaultFB_IsInvalid =
true;
const auto actualSize = *uvec2::FromSize(mDefaultFB->mSize);
if (actualSize != mRequestedSize) {
GenerateWarning(
"Requested size %ux%u was too large, but resize"
" to %ux%u succeeded.",
mRequestedSize.x, mRequestedSize.y, actualSize.x, actualSize.y);
}
mRequestedSize = actualSize;
return true;
}
void WebGLContext::Resize(uvec2 requestedSize) {
// Zero-sized surfaces can cause problems.
if (!requestedSize.x) {
requestedSize.x = 1;
}
if (!requestedSize.y) {
requestedSize.y = 1;
}
// Kill our current default fb(s), for later lazy allocation.
mRequestedSize = requestedSize;
mDefaultFB = nullptr;
mResetLayer =
true;
// New size means new Layer.
}
std::unique_ptr<webgl::FormatUsageAuthority> WebGLContext::CreateFormatUsage(
gl::GLContext* gl)
const {
return webgl::FormatUsageAuthority::CreateForWebGL1(gl);
}
/*static*/
RefPtr<WebGLContext> WebGLContext::Create(HostWebGLContext* host,
const webgl::InitContextDesc& desc,
webgl::InitContextResult*
const out) {
AUTO_PROFILER_LABEL(
"WebGLContext::Create", GRAPHICS);
nsCString failureId =
"FEATURE_FAILURE_WEBGL_UNKOWN"_ns;
const bool forceEnabled = StaticPrefs::webgl_force_enabled();
ScopedGfxFeatureReporter reporter(
"WebGL", forceEnabled);
auto res = [&]() -> Result<RefPtr<WebGLContext>, std::string> {
bool disabled = StaticPrefs::webgl_disabled();
// TODO: When we have software webgl support we should use that instead.
disabled |= gfxPlatform::InSafeMode();
if (disabled) {
if (gfxPlatform::InSafeMode()) {
failureId =
"FEATURE_FAILURE_WEBGL_SAFEMODE"_ns;
}
else {
failureId =
"FEATURE_FAILURE_WEBGL_DISABLED"_ns;
}
return Err(
"WebGL is currently disabled.");
}
// Alright, now let's start trying.
RefPtr<WebGLContext> webgl;
if (desc.isWebgl2) {
webgl =
new WebGL2Context(host, desc);
}
else {
webgl =
new WebGLContext(host, desc);
}
MOZ_ASSERT(!webgl->gl);
std::vector<FailureReason> failReasons;
if (!webgl->CreateAndInitGL(forceEnabled, &failReasons)) {
nsCString text(
"WebGL creation failed: ");
for (
const auto& cur : failReasons) {
// Don't try to accumulate using an empty key if |cur.key| is empty.
if (cur.key.IsEmpty()) {
glean::canvas::webgl_failure_id
.Get(
"FEATURE_FAILURE_REASON_UNKNOWN"_ns)
.Add(1);
}
else {
glean::canvas::webgl_failure_id.Get(cur.key).Add(1);
}
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;
}
glean::canvas::webgl_failure_id.Get(failureId).Add(1);
if (!res.isOk()) {
out->error = res.unwrapErr();
return nullptr;
}
const auto webgl = res.unwrap();
// Update our internal stuff:
webgl->FinishInit();
reporter.SetSuccessful();
if (gl::GLContext::ShouldSpew()) {
printf_stderr(
"--- WebGL context created: %p\n", webgl.get());
}
// -
const auto UploadableSdTypes = [&]() {
webgl::EnumMask<layers::SurfaceDescriptor::Type> types;
types[layers::SurfaceDescriptor::TSurfaceDescriptorBuffer] =
true;
// This is conditional on not using the Compositor thread because we may
// need to synchronize with the RDD process over the PVideoBridge protocol
// to wait for the texture to be available in the compositor process. We
// cannot block on the Compositor thread, so in that configuration, we would
// prefer to do the readback from the RDD which is guaranteed to work, and
// only block the owning thread for WebGL.
const bool offCompositorThread = gfx::gfxVars::UseCanvasRenderThread() ||
!gfx::gfxVars::SupportsThreadsafeGL();
types[layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo] =
offCompositorThread;
// Similarly to the PVideoBridge protocol, we may need to synchronize with
// the content process over the PCompositorManager protocol to wait for the
// shared surface to be available in the compositor process, and we cannot
// block on the Compositor thread.
types[layers::SurfaceDescriptor::TSurfaceDescriptorExternalImage] =
offCompositorThread;
if (webgl->gl->IsANGLE()) {
types[layers::SurfaceDescriptor::TSurfaceDescriptorD3D10] =
true;
types[layers::SurfaceDescriptor::TSurfaceDescriptorDXGIYCbCr] =
true;
}
if (kIsMacOS) {
types[layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface] =
true;
}
if (kIsAndroid) {
types[layers::SurfaceDescriptor::TSurfaceTextureDescriptor] =
true;
}
if (kIsLinux) {
types[layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf] =
true;
}
return types;
};
// -
out->options = webgl->mOptions;
out->limits = *webgl->mLimits;
out->uploadableSdTypes = UploadableSdTypes();
out->vendor = webgl->gl->Vendor();
out->optionalRenderableFormatBits = webgl->mOptionalRenderableFormatBits;
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;
}
}
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);
{
const auto& isEnabledMap = webgl::MakeIsEnabledMap(IsWebGL2());
for (
const auto& pair : isEnabledMap) {
mIsEnabledMapKeys.insert(pair.first);
}
}
//////
// Check everything
AssertCachedBindings();
AssertCachedGlobalState();
mShouldPresent =
true;
//////
// mIsRgb8Renderable
{
const auto tex = gl::ScopedTexture(gl);
const auto fb = gl::ScopedFramebuffer(gl);
gl->fBindTexture(LOCAL_GL_TEXTURE_2D, tex);
gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, fb);
gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_COLOR_ATTACHMENT0,
LOCAL_GL_TEXTURE_2D, tex, 0);
const auto IsRenderable = [&](
const GLint internalFormat,
const GLenum unpackFormat) {
gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, internalFormat, 1, 1, 0,
unpackFormat, LOCAL_GL_UNSIGNED_BYTE, nullptr);
const auto status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
return (status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
};
if (IsRenderable(LOCAL_GL_RGB, LOCAL_GL_RGB)) {
mOptionalRenderableFormatBits |=
webgl::OptionalRenderableFormatBits::RGB8;
}
if (gl->IsSupported(gl::GLFeature::sRGB)) {
struct {
GLint internal;
GLenum unpack;
} formats = {LOCAL_GL_SRGB8, LOCAL_GL_RGB};
const bool isEs2 = (gl->IsGLES() && gl->Version() < 300);
if (isEs2) {
formats = {LOCAL_GL_SRGB, LOCAL_GL_SRGB};
}
if (IsRenderable(formats.internal, formats.unpack)) {
mOptionalRenderableFormatBits |=
webgl::OptionalRenderableFormatBits::SRGB8;
}
}
}
//////
gl->ResetSyncCallCount(
"WebGLContext Initialization");
LoseLruContextIfLimitExceeded();
}
void WebGLContext::SetCompositableHost(
RefPtr<layers::CompositableHost>& aCompositableHost) {
mCompositableHost = aCompositableHost;
}
void WebGLContext::BumpLruLocked() {
if (!mIsContextLost && !mPendingContextLoss) {
mLruPosition.AssignLocked(*
this);
}
else {
MOZ_ASSERT(!mLruPosition.IsInsertedLocked());
}
}
void WebGLContext::BumpLru() {
StaticMutexAutoLock lock(sLruMutex);
BumpLruLocked();
}
void WebGLContext::LoseLruContextIfLimitExceeded() {
StaticMutexAutoLock lock(sLruMutex);
const auto maxContexts = std::max(1u, StaticPrefs::webgl_max_contexts());
const auto maxContextsPerPrincipal =
std::max(1u, StaticPrefs::webgl_max_contexts_per_principal());
// it's important to update the index on a new context before losing old
// contexts, otherwise new unused contexts would all have index 0 and we
// couldn't distinguish older ones when choosing which one to lose first.
BumpLruLocked();
{
size_t forPrincipal = 0;
for (
const auto& context : sLru) {
if (context->mPrincipalKey == mPrincipalKey) {
forPrincipal += 1;
}
}
while (forPrincipal > maxContextsPerPrincipal) {
const auto text = nsPrintfCString(
"Exceeded %u live WebGL contexts for this principal, losing the "
"least recently used one.",
maxContextsPerPrincipal);
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);
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;
PollPendingSyncs();
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);
}
const auto cleanup = MakeScopeExit([&]() {
if (mScissorTestEnabled) {
gl->fEnable(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);
}
// -
template <
typename T,
typename... Args>
constexpr
auto MakeArray(Args... args) -> std::array<T,
sizeof...(Args)> {
return {{
static_cast<T>(args)...}};
}
inline gfx::ColorSpace2 ToColorSpace2ForOutput(
const std::optional<dom::PredefinedColorSpace> chosenCspace) {
const auto cmsMode = GfxColorManagementMode();
switch (cmsMode) {
case CMSMode::Off:
return gfx::ColorSpace2::Display;
case CMSMode::TaggedOnly:
if (!chosenCspace) {
return gfx::ColorSpace2::Display;
}
break;
case CMSMode::All:
if (!chosenCspace) {
return gfx::ColorSpace2::SRGB;
}
break;
}
return gfx::ToColorSpace2(*chosenCspace);
}
// -
template <
class T>
GLuint GLNameOrZero(
const T& t) {
if (t)
return t->mGLName;
return 0;
}
// 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 size = mDefaultFB->mSize;
const auto error = [&]() -> std::optional<std::string> {
const auto canvasCspace = ToColorSpace2ForOutput(mDrawingBufferColorSpace);
auto presenter = swapChain.Acquire(size, canvasCspace);
if (!presenter) {
return "Swap chain surface creation failed.";
}
const auto outputCspace = presenter->BackBuffer()->mDesc.colorSpace;
const auto destFb = presenter->Fb();
// -
bool colorManage = (canvasCspace != gfx::ColorSpace2::Display);
if (canvasCspace == outputCspace) {
colorManage =
false;
}
if (!gl->IsSupported(gl::GLFeature::texture_3D)) {
NS_WARNING(
"Missing GLFeature::texture_3D => colorManage = false.");
colorManage =
false;
}
auto colorLut = std::shared_ptr<gl::Texture>{};
if (colorManage) {
MOZ_ASSERT(canvasCspace != gfx::ColorSpace2::Display);
colorLut = gl->BlitHelper()->GetColorLutTex(gl::GLBlitHelper::ColorLutKey{
.src = canvasCspace, .dst = outputCspace});
if (!colorLut) {
NS_WARNING(
"GetColorLutTex() -> nullptr => colorManage = false.");
colorManage =
false;
}
}
if (!colorManage) {
gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, destFb);
BlitBackbufferToCurDriverFB();
return {};
}
// -
const auto canvasFb = GetDefaultFBForRead({.endOfFrame =
true});
if (!canvasFb) {
return "[WebGLContext::PresentInto] BindDefaultFBForRead failed.";
}
const auto& blitter = gl->BlitHelper()->GetDrawBlitProg({
.fragHeader = gl::kFragHeader_Tex2D,
.fragParts = {gl::kFragSample_OnePlane, gl::kFragConvert_ColorLut3d},
});
constexpr uint8_t texUnit_src = 0;
constexpr uint8_t texUnit_lut = 1;
gl->BindSamplerTexture(texUnit_src, SamplerLinear(), LOCAL_GL_TEXTURE_2D,
canvasFb->ColorTex());
gl->BindSamplerTexture(texUnit_lut, SamplerLinear(), LOCAL_GL_TEXTURE_3D,
colorLut->name);
const auto texCleanup = MakeScopeExit([&]() {
gl->BindSamplerTexture(
texUnit_src, GLNameOrZero(mBoundSamplers[texUnit_src]),
LOCAL_GL_TEXTURE_2D, GLNameOrZero(mBound2DTextures[texUnit_src]));
gl->BindSamplerTexture(
texUnit_lut, GLNameOrZero(mBoundSamplers[texUnit_lut]),
LOCAL_GL_TEXTURE_3D, GLNameOrZero(mBound3DTextures[texUnit_lut]));
gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture);
});
gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, destFb);
gl->fUseProgram(blitter.mProg);
const auto cleanupProg = MakeScopeExit(
[&]() { gl->fUseProgram(GLNameOrZero(mCurrentProgram)); });
gl->fUniform1i(blitter.mLoc_uColorLut, texUnit_lut);
blitter.Draw({
.texMatrix0 = gl::Mat3::I(),
.yFlip =
false,
.destSize = size,
.destRect = {},
});
gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, canvasFb->mFB);
return {};
}();
if (error) {
GenerateWarning(
"%s", error->c_str());
LoseContext();
return false;
}
if (!mOptions.preserveDrawingBuffer) {
gl->InvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER);
mDefaultFB_IsInvalid =
true;
}
return true;
}
bool WebGLContext::PresentIntoXR(gl::SwapChain& swapChain,
const gl::MozFramebuffer& fb) {
OnEndOfFrame();
const auto colorSpace = ToColorSpace2ForOutput(mDrawingBufferColorSpace);
auto presenter = swapChain.Acquire(fb.mSize, colorSpace);
if (!presenter) {
GenerateWarning(
"Swap chain surface creation failed.");
LoseContext();
return false;
}
const auto destFb = presenter->Fb();
gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
BlitBackbufferToCurDriverFB(nullptr, &fb);
// https://immersive-web.github.io/webxr/#opaque-framebuffer
// Opaque framebuffers will always be cleared regardless of the
// associated WebGL context’s preserveDrawingBuffer value.
if (gl->IsSupported(gl::GLFeature::invalidate_framebuffer)) {
gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb.mFB);
constexpr
auto attachments = MakeArray<GLenum>(
LOCAL_GL_COLOR_ATTACHMENT0, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
gl->fInvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, attachments.size(),
attachments.data());
}
return true;
}
// Initialize a swap chain's surface factory given the desired surface type.
void InitSwapChain(gl::GLContext& gl, gl::SwapChain& swapChain,
const layers::TextureType consumerType,
bool useAsync) {
if (!swapChain.mFactory) {
auto typedFactory = gl::SurfaceFactory::Create(&gl, consumerType);
if (typedFactory) {
swapChain.mFactory = std::move(typedFactory);
}
}
if (!swapChain.mFactory) {
NS_WARNING(
"Failed to make an ideal SurfaceFactory.");
swapChain.mFactory = MakeUnique<gl::SurfaceFactory_Basic>(gl);
}
MOZ_ASSERT(swapChain.mFactory);
if (useAsync) {
// RemoteTextureMap will handle recycling any surfaces, so don't rely on the
// SwapChain's internal pooling.
swapChain.DisablePool();
}
}
void WebGLContext::Present(WebGLFramebuffer*
const xrFb,
const layers::TextureType consumerType,
const bool webvr,
const webgl::SwapChainOptions& options) {
const FuncScope funcScope(*
this,
"");
if (IsContextLost()) {
EnsureContextLostRemoteTextureOwner(options);
return;
}
auto swapChain = GetSwapChain(xrFb, webvr);
const gl::MozFramebuffer* maybeFB = nullptr;
if (xrFb) {
maybeFB = xrFb->mOpaque.get();
}
else {
mResolvedDefaultFB = nullptr;
}
bool useAsync = options.remoteTextureOwnerId.IsValid() &&
options.remoteTextureId.IsValid();
InitSwapChain(*gl, *swapChain, consumerType, useAsync);
bool valid =
maybeFB ? PresentIntoXR(*swapChain, *maybeFB) : PresentInto(*swapChain);
if (!valid) {
EnsureContextLostRemoteTextureOwner(options);
return;
}
if (useAsync) {
PushRemoteTexture(nullptr, *swapChain, swapChain->FrontBuffer(), options);
}
}
void WebGLContext::WaitForTxn(layers::RemoteTextureOwnerId ownerId,
layers::RemoteTextureTxnType txnType,
layers::RemoteTextureTxnId txnId) {
if (!ownerId.IsValid() || !txnType || !txnId) {
return;
}
if (mRemoteTextureOwner && mRemoteTextureOwner->IsRegistered(ownerId)) {
mRemoteTextureOwner->WaitForTxn(ownerId, txnType, txnId);
}
}
bool WebGLContext::CopyToSwapChain(
WebGLFramebuffer*
const srcFb,
const layers::TextureType consumerType,
const webgl::SwapChainOptions& options,
layers::RemoteTextureOwnerClient* ownerClient) {
const FuncScope funcScope(*
this,
"");
if (IsContextLost()) {
return false;
}
OnEndOfFrame();
if (!srcFb) {
return false;
}
const auto* info = srcFb->GetCompletenessInfo();
if (!info) {
return false;
}
gfx::IntSize size(info->width, info->height);
bool useAsync = options.remoteTextureOwnerId.IsValid() &&
options.remoteTextureId.IsValid();
InitSwapChain(*gl, srcFb->mSwapChain, consumerType, useAsync);
// 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) {
return PushRemoteTexture(srcFb, srcFb->mSwapChain, nullptr, options,
ownerClient);
}
{
// TODO: ColorSpace will need to be part of SwapChainOptions for DTWebgl.
const auto colorSpace = ToColorSpace2ForOutput(mDrawingBufferColorSpace);
auto presenter = srcFb->mSwapChain.Acquire(size, colorSpace);
if (!presenter) {
GenerateWarning(
"Swap chain surface creation failed.");
LoseContext();
return false;
}
const ScopedFBRebinder saveFB(
this);
const auto destFb = presenter->Fb();
gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, destFb);
BlitBackbufferToCurDriverFB(srcFb, nullptr, options.bgra);
}
if (useAsync) {
return PushRemoteTexture(srcFb, srcFb->mSwapChain,
srcFb->mSwapChain.FrontBuffer(), options,
ownerClient);
}
return true;
}
bool WebGLContext::PushRemoteTexture(
WebGLFramebuffer* fb, gl::SwapChain& swapChain,
std::shared_ptr<gl::SharedSurface> surf,
const webgl::SwapChainOptions& options,
layers::RemoteTextureOwnerClient* ownerClient) {
const auto onFailure = [&]() ->
bool {
GenerateWarning(
"Remote texture creation failed.");
LoseContext();
if (ownerClient && ownerClient == mRemoteTextureOwner) {
ownerClient->PushDummyTexture(options.remoteTextureId,
options.remoteTextureOwnerId);
}
return false;
};
if (!ownerClient) {
if (!mRemoteTextureOwner) {
// Ensure we have a remote texture owner client for WebGLParent.
const auto* outOfProcess =
mHost ? mHost->mOwnerData.outOfProcess : nullptr;
if (!outOfProcess) {
return onFailure();
}
auto pid = outOfProcess->OtherPid();
mRemoteTextureOwner = MakeRefPtr<layers::RemoteTextureOwnerClient>(pid);
}
ownerClient = mRemoteTextureOwner;
}
layers::RemoteTextureOwnerId ownerId = options.remoteTextureOwnerId;
layers::RemoteTextureId textureId = options.remoteTextureId;
if (!ownerClient->IsRegistered(ownerId)) {
// Register a texture owner to represent the swap chain.
RefPtr<layers::RemoteTextureOwnerClient> textureOwner = ownerClient;
auto destroyedCallback = [textureOwner, ownerId]() {
textureOwner->UnregisterTextureOwner(ownerId);
};
swapChain.SetDestroyedCallback(destroyedCallback);
ownerClient->RegisterTextureOwner(ownerId,
/* aSharedRecycling */ !!fb);
}
MOZ_ASSERT(fb || surf);
gfx::IntSize size;
if (surf) {
size = surf->mDesc.size;
}
else {
const auto* info = fb->GetCompletenessInfo();
MOZ_ASSERT(info);
size = gfx::IntSize(info->width, info->height);
}
const auto surfaceFormat = mOptions.alpha ? gfx::SurfaceFormat::B8G8R8A8
: gfx::SurfaceFormat::B8G8R8X8;
Maybe<layers::SurfaceDescriptor> desc;
if (surf) {
desc = surf->ToSurfaceDescriptor();
}
if (!desc) {
if (surf && surf->mDesc.type != gl::SharedSurfaceType::Basic) {
return onFailure();
}
// If we can't serialize to a surface descriptor, then we need to create
// a buffer to read back into that will become the remote texture.
auto data = ownerClient->CreateOrRecycleBufferTextureData(
size, surfaceFormat, ownerId);
if (!data) {
gfxCriticalNoteOnce <<
"Failed to allocate BufferTextureData";
return onFailure();
}
layers::MappedTextureData mappedData;
if (!data->BorrowMappedData(mappedData)) {
return onFailure();
}
Range<uint8_t> range = {mappedData.data,
data->AsBufferTextureData()->GetBufferSize()};
// If we have a surface representing the front buffer, then try to snapshot
// that. Otherwise, when there is no surface, we read back directly from the
// WebGL framebuffer.
auto valid =
surf ? FrontBufferSnapshotInto(surf, Some(range),
Some(mappedData.stride))
: SnapshotInto(fb->mGLName, size, range, Some(mappedData.stride));
if (!valid) {
return onFailure();
}
if (!options.bgra) {
// If the buffer is already BGRA, we don't need to swizzle. However, if it
// is RGBA, then a swizzle to BGRA is required.
bool rv = gfx::SwizzleData(mappedData.data, mappedData.stride,
gfx::SurfaceFormat::R8G8B8A8, mappedData.data,
mappedData.stride,
gfx::SurfaceFormat::B8G8R8A8, mappedData.size);
MOZ_RELEASE_ASSERT(rv,
"SwizzleData failed!");
}
ownerClient->PushTexture(textureId, ownerId, std::move(data));
return true;
}
// SharedSurfaces of SurfaceDescriptorD3D10 and SurfaceDescriptorMacIOSurface
// need to be kept alive. They will be recycled by
// RemoteTextureOwnerClient::GetRecycledSharedSurface() when their usages are
// ended.
std::shared_ptr<gl::SharedSurface> keepAlive;
switch (desc->type()) {
case layers::SurfaceDescriptor::TSurfaceDescriptorD3D10:
case layers::SurfaceDescriptor::TSurfaceDescriptorMacIOSurface:
case layers::SurfaceDescriptor::TSurfaceTextureDescriptor:
case layers::SurfaceDescriptor::TSurfaceDescriptorAndroidHardwareBuffer:
case layers::SurfaceDescriptor::TEGLImageDescriptor:
case layers::SurfaceDescriptor::TSurfaceDescriptorDMABuf:
keepAlive = surf;
break;
default:
break;
}
ownerClient->PushTexture(textureId, ownerId, keepAlive, size, surfaceFormat,
*desc);
// Look for a recycled surface that matches the swap chain.
while (
auto recycledSurface = ownerClient->GetRecycledSharedSurface(
size, surfaceFormat, desc->type(), ownerId)) {
if (swapChain.StoreRecycledSurface(recycledSurface)) {
break;
}
}
return true;
}
void WebGLContext::EnsureContextLostRemoteTextureOwner(
const webgl::SwapChainOptions& options) {
if (!options.remoteTextureOwnerId.IsValid()) {
return;
}
if (!mRemoteTextureOwner) {
// Ensure we have a remote texture owner client for WebGLParent.
const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr;
if (!outOfProcess) {
return;
}
auto pid = outOfProcess->OtherPid();
mRemoteTextureOwner = MakeRefPtr<layers::RemoteTextureOwnerClient>(pid);
}
layers::RemoteTextureOwnerId ownerId = options.remoteTextureOwnerId;
if (!mRemoteTextureOwner->IsRegistered(ownerId)) {
mRemoteTextureOwner->RegisterTextureOwner(ownerId);
}
mRemoteTextureOwner->NotifyContextLost();
}
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<layers::SurfaceDescriptor> WebGLContext::GetFrontBuffer(
WebGLFramebuffer*
const xrFb,
const bool webvr) {
auto* swapChain = GetSwapChain(xrFb, webvr);
if (!swapChain)
return {};
const auto& front = swapChain->FrontBuffer();
if (!front)
return {};
return front->ToSurfaceDescriptor();
}
Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
const Maybe<Range<uint8_t>> maybeDest,
const Maybe<size_t> destStride) {
const auto& front = mSwapChain.FrontBuffer();
if (!front)
return {};
return FrontBufferSnapshotInto(front, maybeDest, destStride);
}
Maybe<uvec2> WebGLContext::FrontBufferSnapshotInto(
const std::shared_ptr<gl::SharedSurface>& front,
const Maybe<Range<uint8_t>> maybeDest,
const Maybe<size_t> destStride) {
const auto& size = front->mDesc.size;
if (!maybeDest)
return Some(*uvec2::FromSize(size));
// -
front->WaitForBufferOwnership();
front->LockProd();
front->ProducerReadAcquire();
auto reset = MakeScopeExit([&] {
front->ProducerReadRelease();
front->UnlockProd();
});
// -
return SnapshotInto(front->mFb ? front->mFb->mFB : 0, size, *maybeDest,
destStride);
}
Maybe<uvec2> WebGLContext::SnapshotInto(GLuint srcFb,
const gfx::IntSize& size,
const Range<uint8_t>& dest,
const Maybe<size_t> destStride) {
const auto minStride = CheckedInt<size_t>(size.width) * 4;
if (!minStride.isValid()) {
gfxCriticalError() <<
"SnapshotInto: invalid stride, width:" << size.width;
return {};
}
size_t stride = destStride.valueOr(minStride.value());
if (stride < minStride.value() || (stride % 4) != 0) {
gfxCriticalError() <<
"SnapshotInto: invalid stride, width:" << size.width
<<
", stride:" << stride;
return {};
}
gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
if (IsWebGL2()) {
gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH,
stride > minStride.value() ? stride / 4 : 0);
gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, 0);
gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
}
// -
const auto readFbWas = mBoundReadFramebuffer;
const auto pboWas = mBoundPixelPackBuffer;
GLenum fbTarget = LOCAL_GL_READ_FRAMEBUFFER;
if (!IsWebGL2()) {
fbTarget = LOCAL_GL_FRAMEBUFFER;
}
auto reset2 = MakeScopeExit([&] {
DoBindFB(readFbWas, fbTarget);
if (pboWas) {
BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
}
});
gl->fBindFramebuffer(fbTarget, srcFb);
if (pboWas) {
BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
}
// -
const auto srcByteCount = CheckedInt<size_t>(stride) * size.height;
if (!srcByteCount.isValid()) {
gfxCriticalError() <<
"SnapshotInto: invalid srcByteCount, width:"
<< size.width <<
", height:" << size.height;
return {};
}
const auto dstByteCount = dest.length();
if (srcByteCount.value() > dstByteCount) {
gfxCriticalError() <<
"SnapshotInto: srcByteCount:" << srcByteCount.value()
<<
" > dstByteCount:" << dstByteCount;
return {};
}
uint8_t* dstPtr = dest.begin().get();
gl->fReadPixels(0, 0, size.width, size.height, LOCAL_GL_RGBA,
LOCAL_GL_UNSIGNED_BYTE, dstPtr);
if (!IsWebGL2() && stride > minStride.value() && size.height > 1) {
// WebGL 1 doesn't support PACK_ROW_LENGTH. Instead, we read the data tight
// into the front of the buffer, and use memmove (since the source and dest
// may overlap) starting from the back to move it to the correct stride
// offsets. We don't move the first row as it is already in the right place.
uint8_t* destRow = dstPtr + stride * (size.height - 1);
const uint8_t* srcRow = dstPtr + minStride.value() * (size.height - 1);
while (destRow > dstPtr) {
memmove(destRow, srcRow, minStride.value());
destRow -= stride;
srcRow -= minStride.value();
}
}
return Some(*uvec2::FromSize(size));
}
void WebGLContext::ClearVRSwapChain() { mWebVRSwapChain.ClearPool(); }
// ------------------------
RefPtr<gfx::DataSourceSurface> GetTempSurface(
const gfx::IntSize& aSize,
gfx::SurfaceFormat& aFormat) {
uint32_t stride =
gfx::GetAlignedStride<8>(aSize.width, BytesPerPixel(aFormat));
return gfx::Factory::CreateDataSourceSurfaceWithStride(aSize, aFormat,
stride);
}
void WebGLContext::DummyReadFramebufferOperation() {
if (!mBoundReadFramebuffer)
return;
// Infallible.
const auto status = mBoundReadFramebuffer->CheckFramebufferStatus();
if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
ErrorInvalidFramebufferOperation(
"Framebuffer must be complete.");
}
}
layers::SharedSurfacesHolder* WebGLContext::GetSharedSurfacesHolder()
const {
const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr;
if (outOfProcess) {
return outOfProcess->mSharedSurfacesHolder;
}
MOZ_ASSERT_UNREACHABLE(
"Unexpected use of SharedSurfacesHolder in process!");
return nullptr;
}
dom::ContentParentId WebGLContext::GetContentId()
const {
const auto* outOfProcess = mHost ? mHost->mOwnerData.outOfProcess : nullptr;
if (outOfProcess) {
return outOfProcess->mContentId;
}
if (XRE_IsContentProcess()) {
return dom::ContentChild::GetSingleton()->GetID();
}
return dom::ContentParentId();
}
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;
if (mHost) {
mHost->OnContextLoss(mPendingContextLossReason);
}
}
void WebGLContext::LoseContextLruLocked(
const webgl::ContextLossReason reason) {
printf_stderr(
"WebGL(%p)::LoseContext(%u)\n",
this,
static_cast<uint32_t>(reason));
mLruPosition.ResetLocked();
mPendingContextLossReason = reason;
mPendingContextLoss =
true;
}
void WebGLContext::LoseContext(
const webgl::ContextLossReason reason) {
StaticMutexAutoLock lock(sLruMutex);
LoseContextLruLocked(reason);
HandlePendingContextLoss();
if (mRemoteTextureOwner) {
mRemoteTextureOwner->NotifyContextLost();
}
}
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;
}
const gl::MozFramebuffer* WebGLContext::GetDefaultFBForRead(
const GetDefaultFBForReadDesc& desc) {
if (!ValidateAndInitFB(nullptr))
return nullptr;
if (!mDefaultFB->mSamples) {
return mDefaultFB.get();
}
if (!mResolvedDefaultFB) {
mResolvedDefaultFB =
gl::MozFramebuffer::Create(gl, mDefaultFB->mSize, 0,
false);
if (!mResolvedDefaultFB) {
gfxCriticalNote << FuncName() <<
": Failed to create mResolvedDefaultFB.";
return nullptr;
}
}
gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, mResolvedDefaultFB->mFB);
BlitBackbufferToCurDriverFB();
if (desc.endOfFrame && !mOptions.preserveDrawingBuffer) {
gl->InvalidateFramebuffer(LOCAL_GL_READ_FRAMEBUFFER);
}
return mResolvedDefaultFB.get();
}
bool WebGLContext::BindDefaultFBForRead() {
const auto fb = GetDefaultFBForRead();
if (!fb)
return false;
gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fb->mFB);
return true;
}
void WebGLContext::DoColorMask(Maybe<GLuint> i,
const uint8_t bitmask)
const {
if (!IsExtensionEnabled(WebGLExtensionID::OES_draw_buffers_indexed)) {
i = Nothing();
}
const auto bs = std::bitset<4>(bitmask);
if (i) {
gl->fColorMaski(*i, bs[0], bs[1], bs[2], bs[3]);
}
else {
gl->fColorMask(bs[0], bs[1], bs[2], bs[3]);
}
}
////////////////////////////////////////////////////////////////////////////////
ScopedDrawCallWrapper::ScopedDrawCallWrapper(WebGLContext& webgl)
: mWebGL(webgl) {
uint8_t driverColorMask0 = mWebGL.mColorWriteMask0;
bool driverDepthTest = mWebGL.mDepthTestEnabled;
bool driverStencilTest = mWebGL.mStencilTestEnabled;
const auto& fb = mWebGL.mBoundDrawFramebuffer;
if (!fb) {
if (mWebGL.mDefaultFB_DrawBuffer0 == LOCAL_GL_NONE) {
driverColorMask0 = 0;
// Is this well-optimized enough for depth-first
// rendering?
}
else {
driverColorMask0 &= ~(uint8_t(mWebGL.mNeedsFakeNoAlpha) << 3);
}
driverDepthTest &= !mWebGL.mNeedsFakeNoDepth;
driverStencilTest &= !mWebGL.mNeedsFakeNoStencil;
}
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() =
default;
IndexedBufferBinding::~IndexedBufferBinding() =
default;
uint64_t IndexedBufferBinding::ByteCount()
const {
if (!mBufferBinding)
return 0;
uint64_t bufferSize = mBufferBinding->ByteLength();
if (!mRangeSize)
// BindBufferBase
return bufferSize;
if (mRangeStart >= bufferSize)
return 0;
bufferSize -= mRangeStart;
return std::min(bufferSize, mRangeSize);
}
////////////////////////////////////////
ScopedFBRebinder::~ScopedFBRebinder() {
const auto fnName = [&](WebGLFramebuffer* fb) {
return fb ? fb->mGLName : 0;
};
const auto& gl = mWebGL->gl;
if (mWebGL->IsWebGL2()) {
gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER,
fnName(mWebGL->mBoundDrawFramebuffer));
gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
fnName(mWebGL->mBoundReadFramebuffer));
}
else {
MOZ_ASSERT(mWebGL->mBoundDrawFramebuffer == mWebGL->mBoundReadFramebuffer);
gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER,
fnName(mWebGL->mBoundDrawFramebuffer));
}
}
////////////////////
void DoBindBuffer(gl::GLContext& gl,
const GLenum target,
const WebGLBuffer*
const buffer) {
gl.fBindBuffer(target, buffer ? buffer->mGLName : 0);
}
////////////////////////////////////////
bool Intersect(
const int32_t srcSize,
const int32_t read0,
const int32_t readSize, int32_t*
const out_intRead0,
int32_t*
const out_intWrite0, int32_t*
const out_intSize) {
MOZ_ASSERT(srcSize >= 0);
MOZ_ASSERT(readSize >= 0);
const auto read1 = int64_t(read0) + readSize;
int32_t intRead0 = read0;
// Clearly doesn't need validation.
int64_t intWrite0 = 0;
int64_t intSize = readSize;
if (read1 <= 0 || read0 >= srcSize) {
// Disjoint ranges.
intSize = 0;
}
else {
if (read0 < 0) {
const auto diff = int64_t(0) - read0;
MOZ_ASSERT(diff >= 0);
intRead0 = 0;
intWrite0 = diff;
intSize -= diff;
}
if (read1 > srcSize) {
const auto diff = int64_t(read1) - srcSize;
MOZ_ASSERT(diff >= 0);
intSize -= diff;
}
if (!CheckedInt<int32_t>(intWrite0).isValid() ||
!CheckedInt<int32_t>(intSize).isValid()) {
return false;
}
}
*out_intRead0 = intRead0;
*out_intWrite0 = intWrite0;
*out_intSize = intSize;
return true;
}
// --
uint64_t AvailGroups(
const uint64_t totalAvailItems,
const uint64_t firstItemOffset,
const uint32_t groupSize,
const uint32_t groupStride) {
MOZ_ASSERT(groupSize && groupStride);
MOZ_ASSERT(groupSize <= groupStride);
if (totalAvailItems <= firstItemOffset)
return 0;
const size_t availItems = totalAvailItems - firstItemOffset;
size_t availGroups = availItems / groupStride;
const size_t tailItems = availItems % groupStride;
if (tailItems >= groupSize) {
availGroups += 1;
}
return availGroups;
}
////////////////////////////////////////////////////////////////////////////////
const char* WebGLContext::FuncName()
const {
const char* ret;
if (MOZ_LIKELY(mFuncScope)) {
ret = mFuncScope->mFuncName;
}
else {
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<dom::Promise> ClientWebGLContext::MakeXRCompatible(
ErrorResult& aRv) {
const FuncScope funcScope(*
this,
"MakeXRCompatible");
nsCOMPtr<nsIGlobalObject> global = GetParentObject();
if (!global) {
aRv.ThrowInvalidAccessError(
"Using a WebGL context that is not attached to either a canvas or an "
"OffscreenCanvas");
return nullptr;
}
RefPtr<dom::Promise> promise = dom::Promise::Create(global, aRv);
NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
if (IsContextLost()) {
promise->MaybeRejectWithInvalidStateError(
"Can not make context XR compatible when context is already lost.");
return promise.forget();
}
// TODO: Bug 1580258 - WebGLContext.MakeXRCompatible needs to switch to
// the device connected to the XR hardware
// This should update `options` and lose+restore the context.
mXRCompatible =
true;
promise->MaybeResolveWithUndefined();
return promise.forget();
}
// --
webgl::AvailabilityRunnable& ClientWebGLContext::EnsureAvailabilityRunnable()
const {
if (!mAvailabilityRunnable) {
mAvailabilityRunnable =
new webgl::AvailabilityRunnable(
this);
auto forgettable = mAvailabilityRunnable;
NS_DispatchToCurrentThread(forgettable.forget());
}
return *mAvailabilityRunnable;
}
webgl::AvailabilityRunnable::AvailabilityRunnable(
const ClientWebGLContext*
const webgl)
: DiscardableRunnable(
"webgl::AvailabilityRunnable"), mWebGL(webgl) {}
webgl::AvailabilityRunnable::~AvailabilityRunnable() {
MOZ_ASSERT(mQueries.empty());
MOZ_ASSERT(mSyncs.empty());
}
nsresult webgl::AvailabilityRunnable::Run() {
for (
const auto& cur : mQueries) {
if (!cur)
continue;
cur->mCanBeAvailable =
true;
}
mQueries.clear();
for (
const auto& cur : mSyncs) {
if (!cur)
continue;
cur->mCanBeAvailable =
true;
}
mSyncs.clear();
if (mWebGL) {
mWebGL->mAvailabilityRunnable = nullptr;
}
return NS_OK;
}
// -
void WebGLContext::JsWarning(
const std::string& text)
const {
if (mHost) {
mHost->JsWarning(text);
}
#ifdef DEBUG
if (!mHost) {
NS_WARNING(text.c_str());
}
#endif
}
--> --------------------
--> maximum size reached
--> --------------------