/* -*- 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 "ClientWebGLContext.h"
#include <bitset>
#include "ClientWebGLExtensions.h"
#include "gfxCrashReporterUtils.h"
#include "HostWebGLContext.h"
#include "js/PropertyAndElement.h" // JS_DefineElement
#include "js/ScalarType.h" // js::Scalar::Type
#include "mozilla/dom/Document.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/WebGLContextEvent.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/EnumeratedRange.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/CanvasManagerChild.h"
#include "mozilla/ipc/Shmem.h"
#include "mozilla/gfx/Swizzle.h"
#include "mozilla/layers/CompositableForwarder.h"
#include "mozilla/layers/CompositorBridgeChild.h"
#include "mozilla/layers/ImageBridgeChild.h"
#include "mozilla/layers/OOPCanvasRenderer.h"
#include "mozilla/layers/TextureClientSharedSurface.h"
#include "mozilla/layers/WebRenderUserData.h"
#include "mozilla/layers/WebRenderCanvasRenderer.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_webgl.h"
#include "nsContentUtils.h"
#include "nsDisplayList.h"
#include "TexUnpackBlob.h"
#include "WebGLFormats.h"
#include "WebGLMethodDispatcher.h"
#include "WebGLChild.h"
#include "WebGLTextureUpload.h"
#include "WebGLValidateStrings.h"
namespace mozilla {
namespace webgl {
std::string SanitizeRenderer(
const std::string&);
}
// namespace webgl
// -
webgl::NotLostData::NotLostData(ClientWebGLContext& _context)
: context(_context) {}
webgl::NotLostData::~NotLostData() {
if (outOfProcess) {
outOfProcess->Destroy();
}
}
// -
bool webgl::ObjectJS::ValidateForContext(
const ClientWebGLContext& targetContext,
const char*
const argName)
const {
if (!IsForContext(targetContext)) {
targetContext.EnqueueError(
LOCAL_GL_INVALID_OPERATION,
"`%s` is from a different (or lost) WebGL context.", argName);
return false;
}
return true;
}
void webgl::ObjectJS::WarnInvalidUse(
const ClientWebGLContext& targetContext,
const char*
const argName)
const {
if (!ValidateForContext(targetContext, argName))
return;
const auto errEnum = ErrorOnDeleted();
targetContext.EnqueueError(errEnum,
"Object `%s` is already deleted.",
argName);
}
// -
WebGLBufferJS::~WebGLBufferJS() {
const auto webgl = Context();
if (webgl) {
webgl->DeleteBuffer(
this);
}
}
WebGLFramebufferJS::~WebGLFramebufferJS() {
const auto webgl = Context();
if (webgl) {
webgl->DeleteFramebuffer(
this);
}
}
WebGLQueryJS::~WebGLQueryJS() {
const auto webgl = Context();
if (webgl) {
webgl->DeleteQuery(
this);
}
}
WebGLRenderbufferJS::~WebGLRenderbufferJS() {
const auto webgl = Context();
if (webgl) {
webgl->DeleteRenderbuffer(
this);
}
}
WebGLSamplerJS::~WebGLSamplerJS() {
const auto webgl = Context();
if (webgl) {
webgl->DeleteSampler(
this);
}
}
WebGLSyncJS::~WebGLSyncJS() {
const auto webgl = Context();
if (webgl) {
webgl->DeleteSync(
this);
}
}
WebGLTextureJS::~WebGLTextureJS() {
const auto webgl = Context();
if (webgl) {
webgl->DeleteTexture(
this);
}
}
WebGLTransformFeedbackJS::~WebGLTransformFeedbackJS() {
const auto webgl = Context();
if (webgl) {
webgl->DeleteTransformFeedback(
this);
}
}
WebGLVertexArrayJS::~WebGLVertexArrayJS() {
const auto webgl = Context();
if (webgl) {
webgl->DeleteVertexArray(
this);
}
}
// -
static bool GetJSScalarFromGLType(GLenum type,
js::Scalar::Type*
const out_scalarType) {
switch (type) {
case LOCAL_GL_BYTE:
*out_scalarType = js::Scalar::Int8;
return true;
case LOCAL_GL_UNSIGNED_BYTE:
*out_scalarType = js::Scalar::Uint8;
return true;
case LOCAL_GL_SHORT:
*out_scalarType = js::Scalar::Int16;
return true;
case LOCAL_GL_HALF_FLOAT:
case LOCAL_GL_HALF_FLOAT_OES:
case LOCAL_GL_UNSIGNED_SHORT:
case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
*out_scalarType = js::Scalar::Uint16;
return true;
case LOCAL_GL_UNSIGNED_INT:
case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
case LOCAL_GL_UNSIGNED_INT_24_8:
*out_scalarType = js::Scalar::Uint32;
return true;
case LOCAL_GL_INT:
*out_scalarType = js::Scalar::Int32;
return true;
case LOCAL_GL_FLOAT:
*out_scalarType = js::Scalar::Float32;
return true;
default:
return false;
}
}
ClientWebGLContext::ClientWebGLContext(
const bool webgl2)
: mIsWebGL2(webgl2),
mExtLoseContext(
new ClientWebGLExtensionLoseContext(*
this)) {}
ClientWebGLContext::~ClientWebGLContext() { RemovePostRefreshObserver(); }
void ClientWebGLContext::JsWarning(
const std::string& utf8)
const {
nsIGlobalObject* global = nullptr;
if (mCanvasElement) {
mozilla::dom::Document* doc = mCanvasElement->OwnerDoc();
if (doc) {
global = doc->GetScopeObject();
}
}
else if (mOffscreenCanvas) {
global = mOffscreenCanvas->GetOwnerGlobal();
}
dom::AutoJSAPI api;
if (!api.Init(global)) {
return;
}
const auto& cx = api.cx();
JS::WarnUTF8(cx,
"%s", utf8.c_str());
}
void AutoJsWarning(
const std::string& utf8) {
if (NS_IsMainThread()) {
const AutoJSContext cx;
JS::WarnUTF8(cx,
"%s", utf8.c_str());
return;
}
JSContext* cx = dom::GetCurrentWorkerThreadJSContext();
if (NS_WARN_IF(!cx)) {
return;
}
JS::WarnUTF8(cx,
"%s", utf8.c_str());
}
// ---------
bool ClientWebGLContext::DispatchEvent(
const nsAString& eventName)
const {
const auto kCanBubble = CanBubble::eYes;
const auto kIsCancelable = Cancelable::eYes;
bool useDefaultHandler =
true;
if (mCanvasElement) {
nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
mCanvasElement, eventName, kCanBubble,
kIsCancelable, &useDefaultHandler);
}
else if (mOffscreenCanvas) {
// OffscreenCanvas case
RefPtr<dom::Event> event =
new dom::Event(mOffscreenCanvas, nullptr, nullptr);
event->InitEvent(eventName, kCanBubble, kIsCancelable);
event->SetTrusted(
true);
useDefaultHandler = mOffscreenCanvas->DispatchEvent(
*event, dom::CallerType::System, IgnoreErrors());
}
return useDefaultHandler;
}
// -
void ClientWebGLContext::EmulateLoseContext()
const {
const FuncScope funcScope(*
this,
"loseContext");
if (mLossStatus != webgl::LossStatus::Ready) {
JsWarning(
"loseContext: Already lost.");
if (!mNextError) {
mNextError = LOCAL_GL_INVALID_OPERATION;
}
return;
}
OnContextLoss(webgl::ContextLossReason::Manual);
}
void ClientWebGLContext::OnContextLoss(
const webgl::ContextLossReason reason)
const {
JsWarning(
"WebGL context was lost.");
if (mNotLost) {
for (
const auto& ext : mNotLost->extensions) {
if (!ext)
continue;
ext->mContext = nullptr;
// Detach.
}
mNotLost = {};
// Lost now!
mNextError = LOCAL_GL_CONTEXT_LOST_WEBGL;
}
switch (reason) {
case webgl::ContextLossReason::Guilty:
mLossStatus = webgl::LossStatus::LostForever;
break;
case webgl::ContextLossReason::None:
mLossStatus = webgl::LossStatus::Lost;
break;
case webgl::ContextLossReason::Manual:
mLossStatus = webgl::LossStatus::LostManually;
break;
}
const auto weak = WeakPtr<
const ClientWebGLContext>(
this);
const auto fnRun = [weak]() {
const auto strong = RefPtr<
const ClientWebGLContext>(weak);
if (!strong)
return;
strong->Event_webglcontextlost();
};
already_AddRefed<mozilla::CancelableRunnable> runnable =
NS_NewCancelableRunnableFunction(
"enqueue Event_webglcontextlost", fnRun);
NS_DispatchToCurrentThread(std::move(runnable));
}
void ClientWebGLContext::Event_webglcontextlost()
const {
const bool useDefaultHandler = DispatchEvent(u
"webglcontextlost"_ns);
if (useDefaultHandler) {
mLossStatus = webgl::LossStatus::LostForever;
}
if (mLossStatus == webgl::LossStatus::Lost) {
RestoreContext(webgl::LossStatus::Lost);
}
}
void ClientWebGLContext::RestoreContext(
const webgl::LossStatus requiredStatus)
const {
if (requiredStatus != mLossStatus) {
JsWarning(
"restoreContext: Only valid iff context lost with loseContext().");
if (!mNextError) {
mNextError = LOCAL_GL_INVALID_OPERATION;
}
return;
}
MOZ_RELEASE_ASSERT(mLossStatus == webgl::LossStatus::Lost ||
mLossStatus == webgl::LossStatus::LostManually);
if (mAwaitingRestore)
return;
mAwaitingRestore =
true;
const auto weak = WeakPtr<
const ClientWebGLContext>(
this);
const auto fnRun = [weak]() {
const auto strong = RefPtr<
const ClientWebGLContext>(weak);
if (!strong)
return;
strong->Event_webglcontextrestored();
};
already_AddRefed<mozilla::CancelableRunnable> runnable =
NS_NewCancelableRunnableFunction(
"enqueue Event_webglcontextrestored",
fnRun);
NS_DispatchToCurrentThread(std::move(runnable));
}
void ClientWebGLContext::Event_webglcontextrestored()
const {
mAwaitingRestore =
false;
mLossStatus = webgl::LossStatus::Ready;
mNextError = 0;
uvec2 requestSize;
if (mCanvasElement) {
requestSize = {mCanvasElement->Width(), mCanvasElement->Height()};
}
else if (mOffscreenCanvas) {
requestSize = {mOffscreenCanvas->Width(), mOffscreenCanvas->Height()};
}
else {
MOZ_ASSERT_UNREACHABLE(
"no HTMLCanvasElement or OffscreenCanvas!");
return;
}
if (!requestSize.x) {
requestSize.x = 1;
}
if (!requestSize.y) {
requestSize.y = 1;
}
const auto mutThis =
const_cast<ClientWebGLContext*>(
this);
// TODO: Make context loss non-mutable.
if (!mutThis->CreateHostContext(requestSize)) {
mLossStatus = webgl::LossStatus::LostForever;
return;
}
mResetLayer =
true;
(
void)DispatchEvent(u
"webglcontextrestored"_ns);
}
// ---------
void ClientWebGLContext::ThrowEvent_WebGLContextCreationError(
const std::string& text)
const {
nsCString msg;
msg.AppendPrintf(
"Failed to create WebGL context: %s", text.c_str());
JsWarning(msg.BeginReading());
RefPtr<dom::EventTarget> target = mCanvasElement;
if (!target && mOffscreenCanvas) {
target = mOffscreenCanvas;
}
else if (!target) {
return;
}
const auto kEventName = u
"webglcontextcreationerror"_ns;
dom::WebGLContextEventInit eventInit;
// eventInit.mCancelable = true; // The spec says this, but it's silly.
eventInit.mStatusMessage = NS_ConvertASCIItoUTF16(text.c_str());
const RefPtr<dom::WebGLContextEvent> event =
dom::WebGLContextEvent::Constructor(target, kEventName, eventInit);
event->SetTrusted(
true);
target->DispatchEvent(*event);
}
// -------------------------------------------------------------------------
// Client-side helper methods. Dispatch to a Host method.
// -------------------------------------------------------------------------
// If we are running WebGL in this process then call the HostWebGLContext
// method directly. Otherwise, dispatch over IPC.
template <
typename MethodT,
typename... Args>
void ClientWebGLContext::Run_WithDestArgTypes(
std::optional<JS::AutoCheckCannotGC>&& noGc,
const MethodT method,
const size_t id,
const Args&... args)
const {
const auto notLost =
mNotLost;
// Hold a strong-ref to prevent LoseContext=>UAF.
// `AutoCheckCannotGC` must be reset after the GC data is done being used but
// *before* the `notLost` destructor runs, since the latter can GC.
const auto cleanup = MakeScopeExit([&]() { noGc.reset(); });
if (IsContextLost()) {
return;
}
const auto& inProcess = notLost->inProcess;
if (inProcess) {
(inProcess.get()->*method)(args...);
return;
}
const auto& child = notLost->outOfProcess;
const auto info = webgl::SerializationInfo(id, args...);
const auto maybeDest = child->AllocPendingCmdBytes(info.requiredByteCount,
info.alignmentOverhead);
if (!maybeDest) {
noGc.reset();
// Reset early, as GC data will not be used, but JsWarning
// can GC.
JsWarning(
"Failed to allocate internal command buffer.");
OnContextLoss(webgl::ContextLossReason::None);
return;
}
const auto& destBytes = *maybeDest;
webgl::Serialize(destBytes, id, args...);
}
// -
#define RPROC(_METHOD) \
decltype(&HostWebGLContext::_METHOD), &HostWebGLContext::_METHOD
// ------------------------- Composition, etc -------------------------
void ClientWebGLContext::OnBeforePaintTransaction() { Present(nullptr); }
void ClientWebGLContext::EndComposition() {
// Mark ourselves as no longer invalidated.
MarkContextClean();
}
// -
layers::TextureType ClientWebGLContext::GetTexTypeForSwapChain()
const {
const RefPtr<layers::ImageBridgeChild> imageBridge =
layers::ImageBridgeChild::GetSingleton();
const bool isOutOfProcess = mNotLost && mNotLost->outOfProcess != nullptr;
return layers::TexTypeForWebgl(imageBridge, isOutOfProcess);
}
void ClientWebGLContext::Present(WebGLFramebufferJS*
const xrFb,
const bool webvr,
const webgl::SwapChainOptions& options) {
const auto texType = GetTexTypeForSwapChain();
Present(xrFb, texType, webvr, options);
}
// Fill in remote texture ids to SwapChainOptions if async present is enabled.
webgl::SwapChainOptions ClientWebGLContext::PrepareAsyncSwapChainOptions(
WebGLFramebufferJS* fb,
bool webvr,
const webgl::SwapChainOptions& options) {
// Currently remote texture ids should only be set internally.
MOZ_ASSERT(!options.remoteTextureOwnerId.IsValid() &&
!options.remoteTextureId.IsValid());
// Async present only works when out-of-process. It is not supported in WebVR.
// Allow it if it is either forced or if the pref is set.
if (fb || webvr) {
return options;
}
if (!IsContextLost() && !mNotLost->inProcess &&
(options.forceAsyncPresent ||
StaticPrefs::webgl_out_of_process_async_present())) {
if (!mRemoteTextureOwnerId) {
mRemoteTextureOwnerId = Some(layers::RemoteTextureOwnerId::GetNext());
}
mLastRemoteTextureId = Some(layers::RemoteTextureId::GetNext());
webgl::SwapChainOptions asyncOptions = options;
asyncOptions.remoteTextureOwnerId = *mRemoteTextureOwnerId;
asyncOptions.remoteTextureId = *mLastRemoteTextureId;
return asyncOptions;
}
// Clear the current remote texture id so that we disable async.
mRemoteTextureOwnerId = Nothing();
return options;
}
void ClientWebGLContext::Present(WebGLFramebufferJS*
const xrFb,
const layers::TextureType type,
const bool webvr,
const webgl::SwapChainOptions& options) {
if (!mIsCanvasDirty && !xrFb)
return;
if (!xrFb) {
mIsCanvasDirty =
false;
}
CancelAutoFlush();
webgl::SwapChainOptions asyncOptions =
PrepareAsyncSwapChainOptions(xrFb, webvr, options);
Run<RPROC(Present)>(xrFb ? xrFb->mId : 0, type, webvr, asyncOptions);
}
void ClientWebGLContext::CopyToSwapChain(
WebGLFramebufferJS*
const fb,
const webgl::SwapChainOptions& options) {
CancelAutoFlush();
const auto texType = GetTexTypeForSwapChain();
webgl::SwapChainOptions asyncOptions =
PrepareAsyncSwapChainOptions(fb,
false, options);
Run<RPROC(CopyToSwapChain)>(fb ? fb->mId : 0, texType, asyncOptions);
}
void ClientWebGLContext::EndOfFrame() {
CancelAutoFlush();
Run<RPROC(EndOfFrame)>();
}
Maybe<layers::SurfaceDescriptor> ClientWebGLContext::GetFrontBuffer(
WebGLFramebufferJS*
const fb,
bool vr) {
const FuncScope funcScope(*
this,
"");
if (IsContextLost())
return {};
const auto& inProcess = mNotLost->inProcess;
if (inProcess) {
return inProcess->GetFrontBuffer(fb ? fb->mId : 0, vr);
}
const auto& child = mNotLost->outOfProcess;
child->FlushPendingCmds();
// Always synchronously get the front buffer if not using a remote texture.
bool needsSync =
true;
Maybe<layers::SurfaceDescriptor> syncDesc;
Maybe<layers::SurfaceDescriptor> remoteDesc;
auto& info = child->GetFlushedCmdInfo();
// If valid remote texture data was set for async present, then use it.
if (!fb && !vr && mRemoteTextureOwnerId && mLastRemoteTextureId) {
const auto tooManyFlushes = 10;
// If there are many flushed cmds, force synchronous IPC to avoid too many
// pending ipc messages. Otherwise don't sync for other cases to avoid any
// performance penalty.
needsSync = XRE_IsParentProcess() ||
gfx::gfxVars::WebglOopAsyncPresentForceSync() ||
info.flushesSinceLastCongestionCheck > tooManyFlushes;
// Only send over a remote texture descriptor if the WebGLChild actor is
// alive to ensure the remote texture id is valid.
if (child->CanSend()) {
remoteDesc = Some(layers::SurfaceDescriptorRemoteTexture(
*mLastRemoteTextureId, *mRemoteTextureOwnerId));
}
}
if (needsSync &&
!child->SendGetFrontBuffer(fb ? fb->mId : 0, vr, &syncDesc)) {
return {};
}
// Reset flushesSinceLastCongestionCheck
info.flushesSinceLastCongestionCheck = 0;
info.congestionCheckGeneration++;
// If there is a remote texture descriptor, use that preferentially, as the
// sync front buffer descriptor was only created to force a sync first.
return remoteDesc ? remoteDesc : syncDesc;
}
Maybe<layers::SurfaceDescriptor> ClientWebGLContext::PresentFrontBuffer(
WebGLFramebufferJS*
const fb,
bool webvr) {
const auto texType = GetTexTypeForSwapChain();
Present(fb, texType, webvr);
return GetFrontBuffer(fb, webvr);
}
already_AddRefed<layers::FwdTransactionTracker>
ClientWebGLContext::UseCompositableForwarder(
layers::CompositableForwarder* aForwarder) {
if (mRemoteTextureOwnerId) {
return layers::FwdTransactionTracker::GetOrCreate(mFwdTransactionTracker);
}
return nullptr;
}
void ClientWebGLContext::OnDestroyChild(dom::WebGLChild* aChild) {
// Since NotLostData may be destructing at this point, the RefPtr to
// WebGLChild may be unreliable. Instead, it must be explicitly passed in.
if (mRemoteTextureOwnerId && mFwdTransactionTracker &&
mFwdTransactionTracker->IsUsed()) {
(
void)aChild->SendWaitForTxn(
*mRemoteTextureOwnerId,
layers::ToRemoteTextureTxnType(mFwdTransactionTracker),
layers::ToRemoteTextureTxnId(mFwdTransactionTracker));
}
}
void ClientWebGLContext::ClearVRSwapChain() { Run<RPROC(ClearVRSwapChain)>(); }
// -
bool ClientWebGLContext::UpdateWebRenderCanvasData(
nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
CanvasRenderer* renderer = aCanvasData->GetCanvasRenderer();
if (!IsContextLost() && !mResetLayer && renderer) {
return true;
}
const auto& size = DrawingBufferSize();
if (!IsContextLost() && !renderer && mNotLost->mCanvasRenderer &&
mNotLost->mCanvasRenderer->GetSize() == gfx::IntSize(size.x, size.y) &&
aCanvasData->SetCanvasRenderer(mNotLost->mCanvasRenderer)) {
mNotLost->mCanvasRenderer->SetDirty();
mResetLayer =
false;
return true;
}
renderer = aCanvasData->CreateCanvasRenderer();
if (!InitializeCanvasRenderer(aBuilder, renderer)) {
// Clear CanvasRenderer of WebRenderCanvasData
aCanvasData->ClearCanvasRenderer();
return false;
}
mNotLost->mCanvasRenderer = renderer;
MOZ_ASSERT(renderer);
mResetLayer =
false;
return true;
}
bool ClientWebGLContext::InitializeCanvasRenderer(
nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) {
const FuncScope funcScope(*
this,
"");
if (IsContextLost())
return false;
layers::CanvasRendererData data;
data.mContext =
this;
data.mOriginPos = gl::OriginPos::BottomLeft;
const auto& options = *mInitialOptions;
const auto& size = DrawingBufferSize();
if (IsContextLost())
return false;
data.mIsOpaque = !options.alpha;
data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
data.mSize = {size.x, size.y};
if (aBuilder->IsPaintingToWindow() && mCanvasElement) {
data.mDoPaintCallbacks =
true;
}
aRenderer->Initialize(data);
aRenderer->SetDirty();
return true;
}
void ClientWebGLContext::UpdateCanvasParameters() {
if (!mOffscreenCanvas) {
return;
}
const auto& options = *mInitialOptions;
const auto& size = DrawingBufferSize();
mozilla::dom::OffscreenCanvasDisplayData data;
data.mOriginPos = gl::OriginPos::BottomLeft;
data.mIsOpaque = !options.alpha;
data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
data.mSize = {size.x, size.y};
data.mDoPaintCallbacks =
false;
mOffscreenCanvas->UpdateDisplayData(data);
}
layers::LayersBackend ClientWebGLContext::GetCompositorBackendType()
const {
if (mCanvasElement) {
return mCanvasElement->GetCompositorBackendType();
}
else if (mOffscreenCanvas) {
return mOffscreenCanvas->GetCompositorBackendType();
}
return layers::LayersBackend::LAYERS_NONE;
}
mozilla::dom::Document* ClientWebGLContext::GetOwnerDoc()
const {
MOZ_ASSERT(mCanvasElement);
if (!mCanvasElement) {
return nullptr;
}
return mCanvasElement->OwnerDoc();
}
void ClientWebGLContext::Commit() {
if (mOffscreenCanvas) {
mOffscreenCanvas->CommitFrameToCompositor();
}
}
void ClientWebGLContext::GetCanvas(
dom::Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& retval) {
if (mCanvasElement) {
MOZ_RELEASE_ASSERT(!mOffscreenCanvas,
"GFX: Canvas is offscreen.");
if (mCanvasElement->IsInNativeAnonymousSubtree()) {
retval.SetNull();
}
else {
retval.SetValue().SetAsHTMLCanvasElement() = mCanvasElement;
}
}
else if (mOffscreenCanvas) {
retval.SetValue().SetAsOffscreenCanvas() = mOffscreenCanvas;
}
else {
retval.SetNull();
}
}
void ClientWebGLContext::SetDrawingBufferColorSpace(
const dom::PredefinedColorSpace val) {
mDrawingBufferColorSpace = val;
Run<RPROC(SetDrawingBufferColorSpace)>(*mDrawingBufferColorSpace);
}
void ClientWebGLContext::SetUnpackColorSpace(
const dom::PredefinedColorSpace val) {
mUnpackColorSpace = val;
Run<RPROC(SetUnpackColorSpace)>(*mUnpackColorSpace);
}
void ClientWebGLContext::GetContextAttributes(
dom::Nullable<dom::WebGLContextAttributes>& retval) {
retval.SetNull();
const FuncScope funcScope(*
this,
"getContextAttributes");
if (IsContextLost())
return;
dom::WebGLContextAttributes& result = retval.SetValue();
const auto& options = mNotLost->info.options;
result.mAlpha.Construct(options.alpha);
result.mDepth = options.depth;
result.mStencil = options.stencil;
result.mAntialias.Construct(options.antialias);
result.mPremultipliedAlpha = options.premultipliedAlpha;
result.mPreserveDrawingBuffer = options.preserveDrawingBuffer;
result.mFailIfMajorPerformanceCaveat = options.failIfMajorPerformanceCaveat;
result.mPowerPreference = options.powerPreference;
result.mForceSoftwareRendering = options.forceSoftwareRendering;
}
// -----------------------
NS_IMETHODIMP
ClientWebGLContext::SetDimensions(
const int32_t signedWidth,
const int32_t signedHeight) {
const FuncScope funcScope(*
this,
"");
MOZ_ASSERT(mInitialOptions);
if (mLossStatus != webgl::LossStatus::Ready) {
// Attempted resize of a lost context.
return NS_OK;
}
uvec2 size = {
static_cast<uint32_t>(signedWidth),
static_cast<uint32_t>(signedHeight)};
if (!size.x) {
size.x = 1;
}
if (!size.y) {
size.y = 1;
}
const auto prevRequestedSize = mRequestedSize;
mRequestedSize = size;
mResetLayer =
true;
// Always treat this as resize.
if (mNotLost) {
auto& state = State();
auto curSize = prevRequestedSize;
if (state.mDrawingBufferSize) {
curSize = *state.mDrawingBufferSize;
}
if (size == curSize)
return NS_OK;
// MUST skip no-op resize
state.mDrawingBufferSize = Nothing();
Run<RPROC(Resize)>(size);
UpdateCanvasParameters();
MarkCanvasDirty();
return NS_OK;
}
// -
// Context (re-)creation
if (!CreateHostContext(size)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
void ClientWebGLContext::ResetBitmap() {
const auto size = DrawingBufferSize();
Run<RPROC(Resize)>(size);
// No-change resize still clears/resets everything.
}
static bool IsWebglOutOfProcessEnabled() {
if (StaticPrefs::webgl_out_of_process_force()) {
return true;
}
if (!gfx::gfxVars::AllowWebglOop()) {
return false;
}
if (!NS_IsMainThread()) {
return StaticPrefs::webgl_out_of_process_worker();
}
return StaticPrefs::webgl_out_of_process();
}
bool ClientWebGLContext::CreateHostContext(
const uvec2& requestedSize) {
const auto pNotLost = std::make_shared<webgl::NotLostData>(*
this);
auto& notLost = *pNotLost;
auto res = [&]() -> Result<Ok, std::string> {
auto options = *mInitialOptions;
if (StaticPrefs::webgl_disable_fail_if_major_performance_caveat()) {
options.failIfMajorPerformanceCaveat =
false;
}
if (options.failIfMajorPerformanceCaveat) {
const auto backend = GetCompositorBackendType();
bool isCompositorSlow =
false;
isCompositorSlow |= (backend == layers::LayersBackend::LAYERS_WR &&
gfx::gfxVars::UseSoftwareWebRender());
if (isCompositorSlow) {
return Err(
"failIfMajorPerformanceCaveat: Compositor is not"
" hardware-accelerated.");
}
}
const bool resistFingerprinting =
ShouldResistFingerprinting(RFPTarget::WebGLRenderCapability);
const auto principalKey = GetPrincipalHashValue();
const auto initDesc = webgl::InitContextDesc{
.isWebgl2 = mIsWebGL2,
.resistFingerprinting = resistFingerprinting,
.principalKey = principalKey,
.size = requestedSize,
.options = options,
};
// -
auto useOop = IsWebglOutOfProcessEnabled();
if (XRE_IsParentProcess()) {
useOop =
false;
}
if (!useOop) {
notLost.inProcess =
HostWebGLContext::Create({
this, nullptr}, initDesc, ¬Lost.info);
return Ok();
}
// -
ScopedGfxFeatureReporter reporter(
"IpcWebGL");
auto*
const cm = gfx::CanvasManagerChild::Get();
if (NS_WARN_IF(!cm)) {
return Err(
"!CanvasManagerChild::Get()");
}
RefPtr<dom::WebGLChild> outOfProcess =
new dom::WebGLChild(*
this);
outOfProcess =
static_cast<dom::WebGLChild*>(cm->SendPWebGLConstructor(outOfProcess));
if (!outOfProcess) {
return Err(
"SendPWebGLConstructor failed");
}
// Clear RemoteTextureOwnerId. HostWebGLContext is going to be replaced in
// WebGLParent.
if (mRemoteTextureOwnerId.isSome()) {
mRemoteTextureOwnerId = Nothing();
mFwdTransactionTracker = nullptr;
}
if (!outOfProcess->SendInitialize(initDesc, ¬Lost.info)) {
return Err(
"WebGL actor Initialize failed");
}
notLost.outOfProcess = outOfProcess;
reporter.SetSuccessful();
return Ok();
}();
if (!res.isOk()) {
auto str = res.unwrapErr();
if (StartsWith(str,
"failIfMajorPerformanceCaveat")) {
str +=
" (about:config override available:"
" webgl.disable-fail-if-major-performance-caveat)";
}
notLost.info.error = str;
}
if (!notLost.info.error->empty()) {
ThrowEvent_WebGLContextCreationError(notLost.info.error);
return false;
}
mNotLost = pNotLost;
UpdateCanvasParameters();
MarkCanvasDirty();
// Init state
const auto& limits = Limits();
auto& state = State();
state.mIsEnabledMap = webgl::MakeIsEnabledMap(mIsWebGL2);
state.mDefaultTfo =
new WebGLTransformFeedbackJS(*
this);
state.mDefaultVao =
new WebGLVertexArrayJS(
this);
state.mBoundTfo = state.mDefaultTfo;
state.mBoundVao = state.mDefaultVao;
(
void)state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER];
state.mTexUnits.resize(limits.maxTexUnits);
state.mBoundUbos.resize(limits.maxUniformBufferBindings);
{
webgl::TypedQuad initVal;
const float fData[4] = {0, 0, 0, 1};
memcpy(initVal.data.data(), fData, initVal.data.size());
state.mGenericVertexAttribs.resize(limits.maxVertexAttribs, initVal);
}
const auto& size = DrawingBufferSize();
state.mViewport = {0, 0,
static_cast<int32_t>(size.x),
static_cast<int32_t>(size.y)};
state.mScissor = state.mViewport;
if (mIsWebGL2) {
// Insert keys to enable slots:
(
void)state.mBoundBufferByTarget[LOCAL_GL_COPY_READ_BUFFER];
(
void)state.mBoundBufferByTarget[LOCAL_GL_COPY_WRITE_BUFFER];
(
void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_PACK_BUFFER];
(
void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_UNPACK_BUFFER];
(
void)state.mBoundBufferByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER];
(
void)state.mBoundBufferByTarget[LOCAL_GL_UNIFORM_BUFFER];
(
void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED];
//(void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE];
//// Same slot as ANY_SAMPLES_PASSED.
(
void)state
.mCurrentQueryByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN];
}
return true;
}
std::unordered_map<GLenum,
bool> webgl::MakeIsEnabledMap(
const bool webgl2) {
auto ret = std::unordered_map<GLenum,
bool>{};
ret[LOCAL_GL_BLEND] =
false;
ret[LOCAL_GL_CULL_FACE] =
false;
ret[LOCAL_GL_DEPTH_TEST] =
false;
ret[LOCAL_GL_DITHER] =
true;
ret[LOCAL_GL_POLYGON_OFFSET_FILL] =
false;
ret[LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE] =
false;
ret[LOCAL_GL_SAMPLE_COVERAGE] =
false;
ret[LOCAL_GL_SCISSOR_TEST] =
false;
ret[LOCAL_GL_STENCIL_TEST] =
false;
if (webgl2) {
ret[LOCAL_GL_RASTERIZER_DISCARD] =
false;
}
return ret;
}
// -------
uvec2 ClientWebGLContext::DrawingBufferSize() {
if (IsContextLost())
return {};
const auto notLost =
mNotLost;
// Hold a strong-ref to prevent LoseContext=>UAF.
auto& state = State();
auto& size = state.mDrawingBufferSize;
if (!size) {
const auto& inProcess = mNotLost->inProcess;
if (inProcess) {
size = Some(inProcess->DrawingBufferSize());
}
else {
const auto& child = mNotLost->outOfProcess;
child->FlushPendingCmds();
uvec2 actual = {};
if (!child->SendDrawingBufferSize(&actual))
return {};
size = Some(actual);
}
}
return *size;
}
void ClientWebGLContext::OnMemoryPressure() {
if (IsContextLost())
return;
const auto& inProcess = mNotLost->inProcess;
if (inProcess) {
return inProcess->OnMemoryPressure();
}
const auto& child = mNotLost->outOfProcess;
(
void)child->SendOnMemoryPressure();
}
NS_IMETHODIMP
ClientWebGLContext::SetContextOptions(JSContext* cx,
JS::Handle<JS::Value> options,
ErrorResult& aRvForDictionaryInit) {
if (mInitialOptions && options.isNullOrUndefined())
return NS_OK;
dom::WebGLContextAttributes attributes;
if (!attributes.Init(cx, options)) {
aRvForDictionaryInit.
Throw(NS_ERROR_UNEXPECTED);
return NS_ERROR_UNEXPECTED;
}
WebGLContextOptions newOpts;
newOpts.stencil = attributes.mStencil;
newOpts.depth = attributes.mDepth;
newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha;
newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer;
newOpts.failIfMajorPerformanceCaveat =
attributes.mFailIfMajorPerformanceCaveat;
newOpts.xrCompatible = attributes.mXrCompatible;
newOpts.powerPreference = attributes.mPowerPreference;
newOpts.forceSoftwareRendering = attributes.mForceSoftwareRendering;
newOpts.enableDebugRendererInfo =
StaticPrefs::webgl_enable_debug_renderer_info();
MOZ_ASSERT(mCanvasElement || mOffscreenCanvas);
newOpts.shouldResistFingerprinting =
ShouldResistFingerprinting(RFPTarget::WebGLRenderCapability);
if (attributes.mAlpha.WasPassed()) {
newOpts.alpha = attributes.mAlpha.Value();
}
if (attributes.mAntialias.WasPassed()) {
newOpts.antialias = attributes.mAntialias.Value();
}
// Don't do antialiasing if we've disabled MSAA.
if (!StaticPrefs::webgl_msaa_samples()) {
newOpts.antialias =
false;
}
// -
if (mInitialOptions && *mInitialOptions != newOpts) {
// Err if the options asked for aren't the same as what they were
// originally.
return NS_ERROR_FAILURE;
}
mXRCompatible = attributes.mXrCompatible;
mInitialOptions.emplace(newOpts);
return NS_OK;
}
void ClientWebGLContext::DidRefresh() { Run<RPROC(DidRefresh)>(); }
already_AddRefed<gfx::SourceSurface> ClientWebGLContext::GetSurfaceSnapshot(
gfxAlphaType*
const out_alphaType) {
const FuncScope funcScope(*
this,
"");
if (IsContextLost())
return nullptr;
const auto notLost =
mNotLost;
// Hold a strong-ref to prevent LoseContext=>UAF.
auto ret = BackBufferSnapshot();
if (!ret)
return nullptr;
// -
const auto& options = mNotLost->info.options;
auto srcAlphaType = gfxAlphaType::Opaque;
if (options.alpha) {
if (options.premultipliedAlpha) {
srcAlphaType = gfxAlphaType::Premult;
}
else {
srcAlphaType = gfxAlphaType::NonPremult;
}
}
if (out_alphaType) {
*out_alphaType = srcAlphaType;
}
else {
// Expects Opaque or Premult
if (srcAlphaType == gfxAlphaType::NonPremult) {
const gfx::DataSourceSurface::ScopedMap map(
ret, gfx::DataSourceSurface::READ_WRITE);
MOZ_RELEASE_ASSERT(map.IsMapped(),
"Failed to map snapshot surface!");
const auto& size = ret->GetSize();
const auto format = ret->GetFormat();
bool rv =
gfx::PremultiplyData(map.GetData(), map.GetStride(), format,
map.GetData(), map.GetStride(), format, size);
MOZ_RELEASE_ASSERT(rv,
"PremultiplyData failed!");
}
}
return ret.forget();
}
RefPtr<gfx::SourceSurface> ClientWebGLContext::GetFrontBufferSnapshot(
const bool requireAlphaPremult) {
const FuncScope funcScope(*
this,
"");
if (IsContextLost())
return nullptr;
const auto notLost =
mNotLost;
// Hold a strong-ref to prevent LoseContext=>UAF.
const auto& options = mNotLost->info.options;
const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
: gfx::SurfaceFormat::B8G8R8X8;
const auto fnNewSurf = [&](
const uvec2 size) {
const auto stride = size.x * 4;
return RefPtr<gfx::DataSourceSurface>(
gfx::Factory::CreateDataSourceSurfaceWithStride({size.x, size.y},
surfFormat, stride,
/*zero=*/true));
};
const auto& inProcess = mNotLost->inProcess;
if (inProcess) {
const auto maybeSize = inProcess->FrontBufferSnapshotInto({});
if (!maybeSize)
return nullptr;
const auto& surfSize = *maybeSize;
const auto stride = surfSize.x * 4;
const auto byteSize = stride * surfSize.y;
const auto surf = fnNewSurf(surfSize);
if (!surf)
return nullptr;
{
const gfx::DataSourceSurface::ScopedMap map(
surf, gfx::DataSourceSurface::READ_WRITE);
if (!map.IsMapped()) {
MOZ_ASSERT(
false);
return nullptr;
}
MOZ_RELEASE_ASSERT(map.GetStride() ==
static_cast<int64_t>(stride));
auto range = Range<uint8_t>{map.GetData(), byteSize};
if (!inProcess->FrontBufferSnapshotInto(Some(range))) {
gfxCriticalNote <<
"ClientWebGLContext::GetFrontBufferSnapshot: "
"FrontBufferSnapshotInto(some) failed after "
"FrontBufferSnapshotInto(none)";
return nullptr;
}
if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
bool rv = gfx::PremultiplyData(
map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
surf->GetSize());
MOZ_RELEASE_ASSERT(rv,
"PremultiplyData failed!");
}
else {
bool rv = gfx::SwizzleData(
map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
surf->GetSize());
MOZ_RELEASE_ASSERT(rv,
"SwizzleData failed!");
}
}
return surf;
}
const auto& child = mNotLost->outOfProcess;
child->FlushPendingCmds();
webgl::FrontBufferSnapshotIpc res;
if (!child->SendGetFrontBufferSnapshot(&res)) {
res = {};
}
if (!res.shmem)
return nullptr;
const auto& surfSize = res.surfSize;
const webgl::RaiiShmem shmem{child, res.shmem.ref()};
if (!shmem)
return nullptr;
const auto& shmemBytes = shmem.ByteRange();
if (!surfSize.x)
return nullptr;
// Zero means failure.
const auto stride = surfSize.x * 4;
const auto byteSize = stride * surfSize.y;
const auto surf = fnNewSurf(surfSize);
if (!surf)
return nullptr;
{
const gfx::DataSourceSurface::ScopedMap map(
surf, gfx::DataSourceSurface::READ_WRITE);
if (!map.IsMapped()) {
MOZ_ASSERT(
false);
return nullptr;
}
MOZ_RELEASE_ASSERT(shmemBytes.length() == byteSize);
if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
bool rv = gfx::PremultiplyData(
shmemBytes.begin().get(), stride, gfx::SurfaceFormat::R8G8B8A8,
map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
surf->GetSize());
MOZ_RELEASE_ASSERT(rv,
"PremultiplyData failed!");
}
else {
bool rv = gfx::SwizzleData(shmemBytes.begin().get(), stride,
gfx::SurfaceFormat::R8G8B8A8, map.GetData(),
map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
surf->GetSize());
MOZ_RELEASE_ASSERT(rv,
"SwizzleData failed!");
}
}
return surf;
}
RefPtr<gfx::DataSourceSurface> ClientWebGLContext::BackBufferSnapshot() {
if (IsContextLost())
return nullptr;
const auto notLost =
mNotLost;
// Hold a strong-ref to prevent LoseContext=>UAF.
const auto& options = mNotLost->info.options;
const auto& state = State();
const auto drawFbWas = state.mBoundDrawFb;
const auto readFbWas = state.mBoundReadFb;
const auto pboWas =
Find(state.mBoundBufferByTarget, LOCAL_GL_PIXEL_PACK_BUFFER);
const auto size = DrawingBufferSize();
// -
BindFramebuffer(LOCAL_GL_FRAMEBUFFER, nullptr);
if (pboWas) {
BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
}
auto reset = MakeScopeExit([&] {
if (drawFbWas == readFbWas) {
BindFramebuffer(LOCAL_GL_FRAMEBUFFER, drawFbWas);
}
else {
BindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, drawFbWas);
BindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, readFbWas);
}
if (pboWas) {
BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
}
});
const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
: gfx::SurfaceFormat::B8G8R8X8;
const auto stride = size.x * 4;
RefPtr<gfx::DataSourceSurface> surf =
gfx::Factory::CreateDataSourceSurfaceWithStride(
{size.x, size.y}, surfFormat, stride,
/*zero=*/true);
if (NS_WARN_IF(!surf)) {
// Was this an OOM or alloc-limit? (500MB is our default resource size
// limit)
surf = gfx::Factory::CreateDataSourceSurfaceWithStride({1, 1}, surfFormat,
4,
/*zero=*/true);
if (!surf) {
// Still failed for a 1x1 size.
gfxCriticalError() <<
"CreateDataSourceSurfaceWithStride(surfFormat="
<< surfFormat <<
") failed.";
}
return nullptr;
}
{
const gfx::DataSourceSurface::ScopedMap map(
surf, gfx::DataSourceSurface::READ_WRITE);
if (!map.IsMapped()) {
MOZ_ASSERT(
false);
return nullptr;
}
MOZ_ASSERT(
static_cast<uint32_t>(map.GetStride()) == stride);
const auto desc = webgl::ReadPixelsDesc{{0, 0}, size};
const auto pixels = Span<uint8_t>(map.GetData(), stride * size.y);
if (!DoReadPixels(desc, pixels))
return nullptr;
// RGBA->BGRA and flip-y.
MOZ_RELEASE_ASSERT(gfx::SwizzleYFlipData(
pixels.data(), stride, gfx::SurfaceFormat::R8G8B8A8, pixels.data(),
stride, gfx::SurfaceFormat::B8G8R8A8, {size.x, size.y}));
}
return surf;
}
UniquePtr<uint8_t[]> ClientWebGLContext::GetImageBuffer(
int32_t* out_format, gfx::IntSize* out_imageSize) {
*out_format = 0;
*out_imageSize = {};
// Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
gfxAlphaType any;
RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
if (!snapshot)
return nullptr;
RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;
*out_imageSize = dataSurface->GetSize();
if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
return gfxUtils::GetImageBufferWithRandomNoise(
dataSurface, premultAlpha, GetCookieJarSettings(), out_format);
}
return gfxUtils::GetImageBuffer(dataSurface, premultAlpha, out_format);
}
NS_IMETHODIMP
ClientWebGLContext::GetInputStream(
const char* mimeType,
const nsAString& encoderOptions,
nsIInputStream** out_stream) {
// Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
gfxAlphaType any;
RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
if (!snapshot)
return NS_ERROR_FAILURE;
RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;
if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
return gfxUtils::GetInputStreamWithRandomNoise(
dataSurface, premultAlpha, mimeType, encoderOptions,
GetCookieJarSettings(), out_stream);
}
return gfxUtils::GetInputStream(dataSurface, premultAlpha, mimeType,
encoderOptions, out_stream);
}
// ------------------------- Client WebGL Objects -------------------------
// ------------------------- Create/Destroy/Is -------------------------
template <
typename T>
static already_AddRefed<T> AsAddRefed(T* ptr) {
RefPtr<T> rp = ptr;
return rp.forget();
}
template <
typename T>
static RefPtr<T> AsRefPtr(T* ptr) {
return {ptr};
}
already_AddRefed<WebGLBufferJS> ClientWebGLContext::CreateBuffer()
const {
const FuncScope funcScope(*
this,
"createBuffer");
auto ret = AsRefPtr(
new WebGLBufferJS(*
this));
Run<RPROC(CreateBuffer)>(ret->mId);
return ret.forget();
}
already_AddRefed<WebGLFramebufferJS> ClientWebGLContext::CreateFramebuffer()
const {
const FuncScope funcScope(*
this,
"createFramebuffer");
auto ret = AsRefPtr(
new WebGLFramebufferJS(*
this));
Run<RPROC(CreateFramebuffer)>(ret->mId);
return ret.forget();
}
already_AddRefed<WebGLFramebufferJS>
ClientWebGLContext::CreateOpaqueFramebuffer(
const webgl::OpaqueFramebufferOptions& options)
const {
const FuncScope funcScope(*
this,
"createOpaqueFramebuffer");
auto ret = AsRefPtr(
new WebGLFramebufferJS(*
this,
true));
if (mNotLost) {
const auto& inProcess = mNotLost->inProcess;
if (inProcess) {
if (!inProcess->CreateOpaqueFramebuffer(ret->mId, options)) {
ret = nullptr;
}
return ret.forget();
}
const auto& child = mNotLost->outOfProcess;
child->FlushPendingCmds();
bool ok =
false;
if (!child->SendCreateOpaqueFramebuffer(ret->mId, options, &ok))
return nullptr;
if (!ok)
return nullptr;
}
return ret.forget();
}
already_AddRefed<WebGLProgramJS> ClientWebGLContext::CreateProgram()
const {
const FuncScope funcScope(*
this,
"createProgram");
auto ret = AsRefPtr(
new WebGLProgramJS(*
this));
Run<RPROC(CreateProgram)>(ret->mId);
return ret.forget();
}
already_AddRefed<WebGLQueryJS> ClientWebGLContext::CreateQuery()
const {
const FuncScope funcScope(*
this,
"createQuery");
auto ret = AsRefPtr(
new WebGLQueryJS(
this));
Run<RPROC(CreateQuery)>(ret->mId);
return ret.forget();
}
already_AddRefed<WebGLRenderbufferJS> ClientWebGLContext::CreateRenderbuffer()
const {
const FuncScope funcScope(*
this,
"createRenderbuffer");
auto ret = AsRefPtr(
new WebGLRenderbufferJS(*
this));
Run<RPROC(CreateRenderbuffer)>(ret->mId);
return ret.forget();
}
already_AddRefed<WebGLSamplerJS> ClientWebGLContext::CreateSampler()
const {
const FuncScope funcScope(*
this,
"createSampler");
auto ret = AsRefPtr(
new WebGLSamplerJS(*
this));
Run<RPROC(CreateSampler)>(ret->mId);
return ret.forget();
}
already_AddRefed<WebGLShaderJS> ClientWebGLContext::CreateShader(
const GLenum type)
const {
const FuncScope funcScope(*
this,
"createShader");
switch (type) {
case LOCAL_GL_VERTEX_SHADER:
case LOCAL_GL_FRAGMENT_SHADER:
break;
default:
EnqueueError_ArgEnum(
"type", type);
return nullptr;
}
auto ret = AsRefPtr(
new WebGLShaderJS(*
this, type));
Run<RPROC(CreateShader)>(ret->mId, ret->mType);
return ret.forget();
}
already_AddRefed<WebGLSyncJS> ClientWebGLContext::FenceSync(
const GLenum condition,
const GLbitfield flags)
const {
const FuncScope funcScope(*
this,
"fenceSync");
if (condition != LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE) {
EnqueueError_ArgEnum(
"condition", condition);
return nullptr;
}
if (flags) {
EnqueueError(LOCAL_GL_INVALID_VALUE,
"`flags` must be 0.");
return nullptr;
}
auto ret = AsRefPtr(
new WebGLSyncJS(*
this));
Run<RPROC(CreateSync)>(ret->mId);
auto& availRunnable = EnsureAvailabilityRunnable();
availRunnable.mSyncs.push_back(ret.get());
ret->mCanBeAvailable =
false;
AutoEnqueueFlush();
return ret.forget();
}
already_AddRefed<WebGLTextureJS> ClientWebGLContext::CreateTexture()
const {
const FuncScope funcScope(*
this,
"createTexture");
auto ret = AsRefPtr(
new WebGLTextureJS(*
this));
Run<RPROC(CreateTexture)>(ret->mId);
return ret.forget();
}
already_AddRefed<WebGLTransformFeedbackJS>
ClientWebGLContext::CreateTransformFeedback()
const {
const FuncScope funcScope(*
this,
"createTransformFeedback");
auto ret = AsRefPtr(
new WebGLTransformFeedbackJS(*
this));
Run<RPROC(CreateTransformFeedback)>(ret->mId);
return ret.forget();
}
already_AddRefed<WebGLVertexArrayJS> ClientWebGLContext::CreateVertexArray()
const {
const FuncScope funcScope(*
this,
"createVertexArray");
auto ret = AsRefPtr(
new WebGLVertexArrayJS(
this));
Run<RPROC(CreateVertexArray)>(ret->mId);
return ret.forget();
}
// -
static bool ValidateOrSkipForDelete(
const ClientWebGLContext& context,
const webgl::ObjectJS*
const obj) {
if (!obj)
return false;
if (!obj->ValidateForContext(context,
"obj"))
return false;
if (obj->IsDeleted())
return false;
return true;
}
void ClientWebGLContext::DeleteBuffer(WebGLBufferJS*
const obj) {
const FuncScope funcScope(*
this,
"deleteBuffer");
if (IsContextLost())
return;
if (!ValidateOrSkipForDelete(*
this, obj))
return;
auto& state = State();
// Unbind from all bind points and bound containers
// UBOs
for (
const auto i : IntegerRange(state.mBoundUbos.size())) {
if (state.mBoundUbos[i] == obj) {
BindBufferBase(LOCAL_GL_UNIFORM_BUFFER, i, nullptr);
}
}
// TFO only if not active
if (!state.mBoundTfo->mActiveOrPaused) {
const auto& buffers = state.mBoundTfo->mAttribBuffers;
for (
const auto i : IntegerRange(buffers.size())) {
if (buffers[i] == obj) {
BindBufferBase(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, i, nullptr);
}
}
}
// Generic/global bind points
for (
const auto& pair : state.mBoundBufferByTarget) {
if (pair.second == obj) {
BindBuffer(pair.first, nullptr);
}
}
// VAO attachments
if (state.mBoundVao->mIndexBuffer == obj) {
BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, nullptr);
}
const auto& vaoBuffers = state.mBoundVao->mAttribBuffers;
Maybe<WebGLBufferJS*> toRestore;
for (
const auto i : IntegerRange(vaoBuffers.size())) {
if (vaoBuffers[i] == obj) {
if (!toRestore) {
toRestore =
Some(state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER].get());
if (*toRestore) {
BindBuffer(LOCAL_GL_ARRAY_BUFFER, nullptr);
}
}
VertexAttribPointer(i, 4, LOCAL_GL_FLOAT,
false, 0, 0);
}
}
if (toRestore && *toRestore) {
BindBuffer(LOCAL_GL_ARRAY_BUFFER, *toRestore);
}
// -
obj->mDeleteRequested =
true;
Run<RPROC(DeleteBuffer)>(obj->mId);
}
void ClientWebGLContext::DeleteFramebuffer(WebGLFramebufferJS*
const obj,
bool canDeleteOpaque) {
const FuncScope funcScope(*
this,
"deleteFramebuffer");
if (IsContextLost())
return;
if (!ValidateOrSkipForDelete(*
this, obj))
return;
if (!canDeleteOpaque && obj->mOpaque) {
EnqueueError(
LOCAL_GL_INVALID_OPERATION,
"An opaque framebuffer's attachments cannot be inspected or changed.");
return;
}
const auto& state = State();
// Unbind
const auto fnDetach = [&](
const GLenum target,
const WebGLFramebufferJS*
const fb) {
if (obj == fb) {
BindFramebuffer(target, nullptr);
}
};
if (state.mBoundDrawFb == state.mBoundReadFb) {
fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
}
else {
fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
}
obj->mDeleteRequested =
true;
Run<RPROC(DeleteFramebuffer)>(obj->mId);
}
void ClientWebGLContext::DeleteProgram(WebGLProgramJS*
const obj)
const {
const FuncScope funcScope(*
this,
"deleteProgram");
if (IsContextLost())
return;
if (!ValidateOrSkipForDelete(*
this, obj))
return;
// Don't unbind
obj->mKeepAlive = nullptr;
}
webgl::ProgramKeepAlive::~ProgramKeepAlive() {
if (!mParent)
return;
const auto& context = mParent->Context();
if (!context)
return;
context->DoDeleteProgram(*mParent);
}
void ClientWebGLContext::DoDeleteProgram(WebGLProgramJS& obj)
const {
obj.mNextLink_Shaders = {};
Run<RPROC(DeleteProgram)>(obj.mId);
}
static GLenum QuerySlotTarget(
const GLenum specificTarget);
void ClientWebGLContext::DeleteQuery(WebGLQueryJS*
const obj) {
const FuncScope funcScope(*
this,
"deleteQuery");
if (IsContextLost())
return;
if (!ValidateOrSkipForDelete(*
this, obj))
return;
const auto& state = State();
// Unbind if current
if (obj->mTarget) {
// Despite mTarget being set, we may not have called BeginQuery on this
// object. QueryCounter may also set mTarget.
const auto slotTarget = QuerySlotTarget(obj->mTarget);
const auto curForTarget =
MaybeFind(state.mCurrentQueryByTarget, slotTarget);
if (curForTarget && *curForTarget == obj) {
EndQuery(obj->mTarget);
}
}
obj->mDeleteRequested =
true;
Run<RPROC(DeleteQuery)>(obj->mId);
}
void ClientWebGLContext::DeleteRenderbuffer(WebGLRenderbufferJS*
const obj) {
const FuncScope funcScope(*
this,
"deleteRenderbuffer");
if (IsContextLost())
return;
if (!ValidateOrSkipForDelete(*
this, obj))
return;
const auto& state = State();
// Unbind
if (state.mBoundRb == obj) {
BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nullptr);
}
// Unbind from bound FBs
const auto fnDetach = [&](
const GLenum target,
const WebGLFramebufferJS*
const fb) {
if (!fb)
return;
for (
const auto& pair : fb->mAttachments) {
if (pair.second.rb == obj) {
FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
nullptr);
}
}
};
if (state.mBoundDrawFb == state.mBoundReadFb) {
fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
}
else {
fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
}
obj->mDeleteRequested =
true;
Run<RPROC(DeleteRenderbuffer)>(obj->mId);
}
void ClientWebGLContext::DeleteSampler(WebGLSamplerJS*
const obj) {
const FuncScope funcScope(*
this,
"deleteSampler");
if (IsContextLost())
return;
if (!ValidateOrSkipForDelete(*
this, obj))
return;
const auto& state = State();
// Unbind
for (
const auto i : IntegerRange(state.mTexUnits.size())) {
if (state.mTexUnits[i].sampler == obj) {
BindSampler(i, nullptr);
}
}
obj->mDeleteRequested =
true;
Run<RPROC(DeleteSampler)>(obj->mId);
}
void ClientWebGLContext::DeleteShader(WebGLShaderJS*
const obj)
const {
const FuncScope funcScope(*
this,
"deleteShader");
if (IsContextLost())
return;
if (!ValidateOrSkipForDelete(*
this, obj))
return;
// Don't unbind
obj->mKeepAlive = nullptr;
}
webgl::ShaderKeepAlive::~ShaderKeepAlive() {
if (!mParent)
return;
const auto& context = mParent->Context();
if (!context)
return;
context->DoDeleteShader(*mParent);
}
void ClientWebGLContext::DoDeleteShader(
const WebGLShaderJS& obj)
const {
Run<RPROC(DeleteShader)>(obj.mId);
}
void ClientWebGLContext::DeleteSync(WebGLSyncJS*
const obj)
const {
const FuncScope funcScope(*
this,
"deleteSync");
if (IsContextLost())
return;
if (!ValidateOrSkipForDelete(*
this, obj))
return;
// Nothing to unbind
obj->mDeleteRequested =
true;
Run<RPROC(DeleteSync)>(obj->mId);
}
void ClientWebGLContext::DeleteTexture(WebGLTextureJS*
const obj) {
const FuncScope funcScope(*
this,
"deleteTexture");
if (IsContextLost())
return;
if (!ValidateOrSkipForDelete(*
this, obj))
return;
auto& state = State();
// Unbind
const auto& target = obj->mTarget;
if (target) {
// Unbind from tex units
Maybe<uint32_t> restoreTexUnit;
for (
const auto i : IntegerRange(state.mTexUnits.size())) {
if (state.mTexUnits[i].texByTarget[target] == obj) {
if (!restoreTexUnit) {
restoreTexUnit = Some(state.mActiveTexUnit);
}
ActiveTexture(LOCAL_GL_TEXTURE0 + i);
BindTexture(target, nullptr);
}
}
if (restoreTexUnit) {
ActiveTexture(LOCAL_GL_TEXTURE0 + *restoreTexUnit);
}
// Unbind from bound FBs
const auto fnDetach = [&](
const GLenum target,
const WebGLFramebufferJS*
const fb) {
if (!fb)
return;
for (
const auto& pair : fb->mAttachments) {
if (pair.second.tex == obj) {
FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
nullptr);
}
}
};
if (state.mBoundDrawFb == state.mBoundReadFb) {
fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
}
else {
fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
}
}
obj->mDeleteRequested =
true;
Run<RPROC(DeleteTexture)>(obj->mId);
}
void ClientWebGLContext::DeleteTransformFeedback(
WebGLTransformFeedbackJS*
const obj) {
const FuncScope funcScope(*
this,
"deleteTransformFeedback");
if (IsContextLost())
return;
if (!ValidateOrSkipForDelete(*
this, obj))
return;
const auto& state = State();
if (obj->mActiveOrPaused) {
EnqueueError(LOCAL_GL_INVALID_OPERATION,
"Transform Feedback object still active or paused.");
return;
}
// Unbind
if (state.mBoundTfo == obj) {
BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
}
obj->mDeleteRequested =
true;
Run<RPROC(DeleteTransformFeedback)>(obj->mId);
}
void ClientWebGLContext::DeleteVertexArray(WebGLVertexArrayJS*
const obj) {
const FuncScope funcScope(*
this,
"deleteVertexArray");
if (IsContextLost())
return;
if (!ValidateOrSkipForDelete(*
this, obj))
return;
const auto& state = State();
// Unbind
if (state.mBoundVao == obj) {
BindVertexArray(nullptr);
}
obj->mDeleteRequested =
true;
Run<RPROC(DeleteVertexArray)>(obj->mId);
}
// -
bool ClientWebGLContext::IsBuffer(
const WebGLBufferJS*
const obj)
const {
const FuncScope funcScope(*
this,
"isBuffer");
if (IsContextLost())
return false;
return obj && obj->IsUsable(*
this) &&
obj->mKind != webgl::BufferKind::Undefined;
}
bool ClientWebGLContext::IsFramebuffer(
const WebGLFramebufferJS*
const obj)
const {
const FuncScope funcScope(*
this,
"isFramebuffer");
if (IsContextLost())
return false;
return obj && obj->IsUsable(*
this) && obj->mHasBeenBound;
}
bool ClientWebGLContext::IsProgram(
const WebGLProgramJS*
const obj)
const {
const FuncScope funcScope(*
this,
"isProgram");
if (IsContextLost())
return false;
return obj && obj->IsUsable(*
this);
}
bool ClientWebGLContext::IsQuery(
const WebGLQueryJS*
const obj)
const {
const FuncScope funcScope(*
this,
"isQuery");
if (IsContextLost())
return false;
return obj && obj->IsUsable(*
this) && obj->mTarget;
}
bool ClientWebGLContext::IsRenderbuffer(
const WebGLRenderbufferJS*
const obj)
const {
const FuncScope funcScope(*
this,
"isRenderbuffer");
if (IsContextLost())
return false;
return obj && obj->IsUsable(*
this) && obj->mHasBeenBound;
}
bool ClientWebGLContext::IsSampler(
const WebGLSamplerJS*
const obj)
const {
const FuncScope funcScope(*
this,
"isSampler");
if (IsContextLost())
return false;
return obj && obj->IsUsable(*
this);
}
bool ClientWebGLContext::IsShader(
const WebGLShaderJS*
const obj)
const {
const FuncScope funcScope(*
this,
"isShader");
if (IsContextLost())
return false;
return obj && obj->IsUsable(*
this);
}
bool ClientWebGLContext::IsSync(
const WebGLSyncJS*
const obj)
const {
const FuncScope funcScope(*
this,
"isSync");
if (IsContextLost())
return false;
return obj && obj->IsUsable(*
this);
}
bool ClientWebGLContext::IsTexture(
const WebGLTextureJS*
const obj)
const {
const FuncScope funcScope(*
this,
"isTexture");
if (IsContextLost())
return false;
return obj && obj->IsUsable(*
this) && obj->mTarget;
}
bool ClientWebGLContext::IsTransformFeedback(
const WebGLTransformFeedbackJS*
const obj)
const {
const FuncScope funcScope(*
this,
"isTransformFeedback");
if (IsContextLost())
return false;
return obj && obj->IsUsable(*
this) && obj->mHasBeenBound;
}
bool ClientWebGLContext::IsVertexArray(
const WebGLVertexArrayJS*
const obj)
const {
const FuncScope funcScope(*
this,
"isVertexArray");
if (IsContextLost())
return false;
return obj && obj->IsUsable(*
this) && obj->mHasBeenBound;
}
// ------------------------- GL State -------------------------
void ClientWebGLContext::SetEnabledI(
const GLenum cap,
const Maybe<GLuint> i,
const bool val)
const {
const FuncScope funcScope(*
this,
"enable/disable");
if (IsContextLost())
return;
auto& map = mNotLost->state.mIsEnabledMap;
auto slot = MaybeFind(map, cap);
if (i && cap != LOCAL_GL_BLEND) {
slot = nullptr;
}
if (!slot) {
EnqueueError_ArgEnum(
"cap", cap);
return;
}
Run<RPROC(SetEnabled)>(cap, i, val);
if (!i || *i == 0) {
*slot = val;
}
}
bool ClientWebGLContext::IsEnabled(
const GLenum cap)
const {
const FuncScope funcScope(*
this,
"isEnabled");
if (IsContextLost())
return false;
const auto& map = mNotLost->state.mIsEnabledMap;
const auto slot = MaybeFind(map, cap);
if (!slot) {
EnqueueError_ArgEnum(
"cap", cap);
return false;
}
return *slot;
}
template <
typename T,
typename S>
static JS::Value Create(JSContext* cx, nsWrapperCache* creator,
const S& src,
ErrorResult& rv) {
return JS::ObjectOrNullValue(T::Create(cx, creator, src, rv));
}
void ClientWebGLContext::GetInternalformatParameter(
JSContext* cx, GLenum target, GLenum internalformat, GLenum pname,
JS::MutableHandle<JS::Value> retval, ErrorResult& rv) {
const FuncScope funcScope(*
this,
"getInternalformatParameter");
retval.set(JS::NullValue());
const auto notLost =
mNotLost;
// Hold a strong-ref to prevent LoseContext=>UAF.
if (IsContextLost())
return;
const auto& inProcessContext = notLost->inProcess;
Maybe<std::vector<int32_t>> maybe;
if (inProcessContext) {
maybe = inProcessContext->GetInternalformatParameter(target, internalformat,
pname);
}
else {
const auto& child = notLost->outOfProcess;
child->FlushPendingCmds();
if (!child->SendGetInternalformatParameter(target, internalformat, pname,
&maybe)) {
return;
}
}
if (!maybe) {
return;
}
retval.set(Create<dom::Int32Array>(cx,
this, *maybe, rv));
}
static JS::Value StringValue(JSContext* cx,
const std::string& str,
ErrorResult& er) {
JSString* jsStr = JS_NewStringCopyN(cx, str.data(), str.size());
if (!jsStr) {
er.
Throw(NS_ERROR_OUT_OF_MEMORY);
return JS::NullValue();
}
return JS::StringValue(jsStr);
}
template <
typename T>
bool ToJSValueOrNull(JSContext*
const cx,
const RefPtr<T>& ptr,
JS::MutableHandle<JS::Value> retval) {
if (!ptr) {
retval.set(JS::NullValue());
return true;
}
return dom::ToJSValue(cx, ptr, retval);
}
Maybe<
double> ClientWebGLContext::GetNumber(
const GLenum pname) {
MOZ_ASSERT(!IsContextLost());
const auto& inProcess = mNotLost->inProcess;
if (inProcess) {
return inProcess->GetNumber(pname);
}
const auto& child = mNotLost->outOfProcess;
child->FlushPendingCmds();
Maybe<
double> ret;
if (!child->SendGetNumber(pname, &ret)) {
ret.reset();
}
return ret;
}
Maybe<std::string> ClientWebGLContext::GetString(
const GLenum pname) {
MOZ_ASSERT(!IsContextLost());
const auto& inProcess = mNotLost->inProcess;
if (inProcess) {
return inProcess->GetString(pname);
}
const auto& child = mNotLost->outOfProcess;
child->FlushPendingCmds();
Maybe<std::string> ret;
if (!child->SendGetString(pname, &ret)) {
ret.reset();
}
return ret;
}
void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
JS::MutableHandle<JS::Value> retval,
ErrorResult& rv,
const bool debug) {
retval.set(JS::NullValue());
const FuncScope funcScope(*
this,
"getParameter");
if (IsContextLost())
return;
const auto& limits = Limits();
const auto& state = State();
// -
const auto fnSetRetval_Buffer = [&](
const GLenum target) {
const auto buffer = *MaybeFind(state.mBoundBufferByTarget, target);
(
void)ToJSValueOrNull(cx, buffer, retval);
};
const auto fnSetRetval_Tex = [&](
const GLenum texTarget) {
const auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
const auto tex = Find(texUnit.texByTarget, texTarget, nullptr);
(
void)ToJSValueOrNull(cx, tex, retval);
};
switch (pname) {
case LOCAL_GL_ARRAY_BUFFER_BINDING:
fnSetRetval_Buffer(LOCAL_GL_ARRAY_BUFFER);
return;
case LOCAL_GL_CURRENT_PROGRAM:
(
void)ToJSValueOrNull(cx, state.mCurrentProgram, retval);
return;
case LOCAL_GL_ELEMENT_ARRAY_BUFFER_BINDING:
(
void)ToJSValueOrNull(cx, state.mBoundVao->mIndexBuffer, retval);
return;
case LOCAL_GL_FRAMEBUFFER_BINDING:
(
void)ToJSValueOrNull(cx, state.mBoundDrawFb, retval);
return;
case LOCAL_GL_RENDERBUFFER_BINDING:
(
void)ToJSValueOrNull(cx, state.mBoundRb, retval);
return;
case LOCAL_GL_TEXTURE_BINDING_2D:
fnSetRetval_Tex(LOCAL_GL_TEXTURE_2D);
return;
case LOCAL_GL_TEXTURE_BINDING_CUBE_MAP:
fnSetRetval_Tex(LOCAL_GL_TEXTURE_CUBE_MAP);
return;
case LOCAL_GL_VERTEX_ARRAY_BINDING: {
if (!mIsWebGL2 &&
!IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object))
break;
auto ret = state.mBoundVao;
if (ret == state.mDefaultVao) {
ret = nullptr;
}
(
void)ToJSValueOrNull(cx, ret, retval);
return;
}
case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
retval.set(JS::NumberValue(limits.maxTexUnits));
return;
case LOCAL_GL_MAX_TEXTURE_SIZE:
retval.set(JS::NumberValue(limits.maxTex2dSize));
return;
case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE:
retval.set(JS::NumberValue(limits.maxTexCubeSize));
return;
case LOCAL_GL_MAX_VERTEX_ATTRIBS:
retval.set(JS::NumberValue(limits.maxVertexAttribs));
return;
case LOCAL_GL_MAX_VIEWS_OVR:
if (IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) {
retval.set(JS::NumberValue(limits.maxMultiviewLayers));
return;
}
break;
case LOCAL_GL_PACK_ALIGNMENT:
retval.set(JS::NumberValue(state.mPixelPackState.alignmentInTypeElems));
--> --------------------
--> maximum size reached
--> --------------------