summaryrefslogtreecommitdiffstats
path: root/dom/canvas/ClientWebGLContext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/ClientWebGLContext.cpp')
-rw-r--r--dom/canvas/ClientWebGLContext.cpp6870
1 files changed, 6870 insertions, 0 deletions
diff --git a/dom/canvas/ClientWebGLContext.cpp b/dom/canvas/ClientWebGLContext.cpp
new file mode 100644
index 0000000000..c32c4813ca
--- /dev/null
+++ b/dom/canvas/ClientWebGLContext.cpp
@@ -0,0 +1,6870 @@
+/* -*- 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/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/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 "WebGLMethodDispatcher.h"
+#include "WebGLChild.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) {
+ Unused << dom::WebGLChild::Send__delete__(outOfProcess.get());
+ }
+}
+
+// -
+
+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(), static_cast<nsIContent*>(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);
+}
+
+// -
+
+// If we are running WebGL in this process then call the HostWebGLContext
+// method directly. Otherwise, dispatch over IPC.
+template <typename MethodType, MethodType method, typename... Args>
+void ClientWebGLContext::Run(Args&&... args) const {
+ const auto notLost =
+ mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
+ if (IsContextLost()) return;
+
+ const auto& inProcess = notLost->inProcess;
+ if (inProcess) {
+ return (inProcess.get()->*method)(std::forward<Args>(args)...);
+ }
+
+ const auto& child = notLost->outOfProcess;
+
+ const auto id = IdByMethod<MethodType, method>();
+
+ const auto info = webgl::SerializationInfo(id, args...);
+ const auto maybeDest = child->AllocPendingCmdBytes(info.requiredByteCount,
+ info.alignmentOverhead);
+ if (!maybeDest) {
+ JsWarning("Failed to allocate internal command buffer.");
+ OnContextLoss(webgl::ContextLossReason::None);
+ return;
+ }
+ const auto& destBytes = *maybeDest;
+ webgl::Serialize(destBytes, id, args...);
+}
+
+// -------------------------------------------------------------------------
+// Client-side helper methods. Dispatch to a Host method.
+// -------------------------------------------------------------------------
+
+#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();
+ return layers::TexTypeForWebgl(imageBridge);
+}
+
+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());
+ auto& ownerId = fb ? fb->mRemoteTextureOwnerId : mRemoteTextureOwnerId;
+ auto& textureId = fb ? fb->mLastRemoteTextureId : mLastRemoteTextureId;
+ // 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 (!IsContextLost() && !mNotLost->inProcess && !webvr &&
+ (options.forceAsyncPresent ||
+ StaticPrefs::webgl_out_of_process_async_present())) {
+ if (!ownerId) {
+ ownerId = Some(layers::RemoteTextureOwnerId::GetNext());
+ }
+ textureId = Some(layers::RemoteTextureId::GetNext());
+ webgl::SwapChainOptions asyncOptions = options;
+ asyncOptions.remoteTextureOwnerId = *ownerId;
+ asyncOptions.remoteTextureId = *textureId;
+ return asyncOptions;
+ }
+ // Clear the current remote texture id so that we disable async.
+ textureId = 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, "<GetFrontBuffer>");
+ 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();
+
+ Maybe<layers::SurfaceDescriptor> ret;
+
+ // If valid remote texture data was set for async present, then use it.
+ const auto& ownerId = fb ? fb->mRemoteTextureOwnerId : mRemoteTextureOwnerId;
+ const auto& textureId = fb ? fb->mLastRemoteTextureId : mLastRemoteTextureId;
+ auto& needsSync = fb ? fb->mNeedsRemoteTextureSync : mNeedsRemoteTextureSync;
+ if (ownerId && textureId) {
+ if (XRE_IsParentProcess() ||
+ gfx::gfxVars::WebglOopAsyncPresentForceSync() || needsSync) {
+ needsSync = false;
+ // Request the front buffer from IPDL to cause a sync, even though we
+ // will continue to use the remote texture descriptor after.
+ (void)child->SendGetFrontBuffer(fb ? fb->mId : 0, vr, &ret);
+ }
+ return Some(layers::SurfaceDescriptorRemoteTexture(*textureId, *ownerId));
+ }
+
+ if (!child->SendGetFrontBuffer(fb ? fb->mId : 0, vr, &ret)) return {};
+
+ return ret;
+}
+
+Maybe<layers::SurfaceDescriptor> ClientWebGLContext::PresentFrontBuffer(
+ WebGLFramebufferJS* const fb, const layers::TextureType type, bool webvr) {
+ Present(fb, type, webvr);
+ return GetFrontBuffer(fb, webvr);
+}
+
+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;
+ mNeedsRemoteTextureSync = true;
+
+ return true;
+}
+
+bool ClientWebGLContext::InitializeCanvasRenderer(
+ nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) {
+ const FuncScope funcScope(*this, "<InitializeCanvasRenderer>");
+ 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::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;
+}
+
+// -----------------------
+
+NS_IMETHODIMP
+ClientWebGLContext::SetDimensions(const int32_t signedWidth,
+ const int32_t signedHeight) {
+ const FuncScope funcScope(*this, "<SetDimensions>");
+ 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();
+ const auto principalKey = GetPrincipalHashValue();
+ const auto initDesc = webgl::InitContextDesc{
+ mIsWebGL2, resistFingerprinting, requestedSize, options, principalKey};
+
+ // -
+
+ auto useOop = IsWebglOutOfProcessEnabled();
+ if (XRE_IsParentProcess()) {
+ useOop = false;
+ }
+
+ if (!useOop) {
+ notLost.inProcess =
+ HostWebGLContext::Create({this, nullptr}, initDesc, &notLost.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");
+ }
+
+ if (!outOfProcess->SendInitialize(initDesc, &notLost.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.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;
+}
+
+// -------
+
+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.enableDebugRendererInfo =
+ StaticPrefs::webgl_enable_debug_renderer_info();
+ MOZ_ASSERT(mCanvasElement || mOffscreenCanvas);
+ newOpts.shouldResistFingerprinting = ShouldResistFingerprinting();
+
+ if (attributes.mAlpha.WasPassed()) {
+ newOpts.alpha = attributes.mAlpha.Value();
+ }
+ if (attributes.mAntialias.WasPassed()) {
+ newOpts.antialias = attributes.mAntialias.Value();
+ }
+ newOpts.ignoreColorSpace = true;
+ if (attributes.mColorSpace.WasPassed()) {
+ newOpts.ignoreColorSpace = false;
+ newOpts.colorSpace = attributes.mColorSpace.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, "<GetSurfaceSnapshot>");
+ 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, "<GetSurfaceSnapshot>");
+ 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 range = Range<uint8_t>(map.GetData(), stride * size.y);
+ if (!DoReadPixels(desc, range)) return nullptr;
+
+ const auto begin = range.begin().get();
+
+ std::vector<uint8_t> temp;
+ temp.resize(stride);
+ for (const auto i : IntegerRange(size.y / 2)) {
+ const auto top = begin + stride * i;
+ const auto bottom = begin + stride * (size.y - 1 - i);
+ memcpy(temp.data(), top, stride);
+ memcpy(top, bottom, stride);
+ gfxUtils::ConvertBGRAtoRGBA(top, stride);
+
+ memcpy(bottom, temp.data(), stride);
+ gfxUtils::ConvertBGRAtoRGBA(bottom, stride);
+ }
+
+ if (size.y % 2) {
+ const auto middle = begin + stride * (size.y / 2);
+ gfxUtils::ConvertBGRAtoRGBA(middle, stride);
+ }
+ }
+
+ 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");
+ if (IsContextLost()) return nullptr;
+
+ 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");
+ if (IsContextLost()) return nullptr;
+
+ 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");
+ if (IsContextLost()) return nullptr;
+
+ auto ret = AsRefPtr(new WebGLFramebufferJS(*this, true));
+
+ 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");
+ if (IsContextLost()) return nullptr;
+
+ 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");
+ if (IsContextLost()) return nullptr;
+
+ 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");
+ if (IsContextLost()) return nullptr;
+
+ 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");
+ if (IsContextLost()) return nullptr;
+
+ 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");
+ if (IsContextLost()) return nullptr;
+
+ 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 (IsContextLost()) return nullptr;
+
+ 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;
+
+ return ret.forget();
+}
+
+already_AddRefed<WebGLTextureJS> ClientWebGLContext::CreateTexture() const {
+ const FuncScope funcScope(*this, "createTexture");
+ if (IsContextLost()) return nullptr;
+
+ 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");
+ if (IsContextLost()) return nullptr;
+
+ 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");
+ if (IsContextLost()) return nullptr;
+
+ 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(GLenum cap, Maybe<GLuint> i,
+ bool val) const {
+ Run<RPROC(SetEnabled)>(cap, i, val);
+}
+
+bool ClientWebGLContext::IsEnabled(GLenum cap) const {
+ const FuncScope funcScope(*this, "isEnabled");
+ if (IsContextLost()) return false;
+
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->IsEnabled(cap);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ bool ret = {};
+ if (!child->SendIsEnabled(cap, &ret)) return false;
+ return ret;
+}
+
+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;
+ }
+ // zero-length array indicates out-of-memory
+ JSObject* obj =
+ dom::Int32Array::Create(cx, this, maybe->size(), maybe->data());
+ if (!obj) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ retval.setObjectOrNull(obj);
+}
+
+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);
+}
+
+template <typename T, typename U, typename S>
+static JS::Value CreateAs(JSContext* cx, nsWrapperCache* creator, const S& src,
+ ErrorResult& rv) {
+ const auto obj =
+ T::Create(cx, creator, src.size(), reinterpret_cast<U>(src.data()));
+ if (!obj) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ return JS::ObjectOrNullValue(obj);
+}
+
+template <typename T, typename S>
+static JS::Value Create(JSContext* cx, nsWrapperCache* creator, const S& src,
+ ErrorResult& rv) {
+ return CreateAs<T, decltype(&src[0]), S>(cx, creator, src, rv);
+}
+
+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));
+ return;
+ case LOCAL_GL_UNPACK_ALIGNMENT:
+ retval.set(JS::NumberValue(state.mPixelUnpackState.alignmentInTypeElems));
+ return;
+
+ case dom::WebGLRenderingContext_Binding::UNPACK_FLIP_Y_WEBGL:
+ retval.set(JS::BooleanValue(state.mPixelUnpackState.flipY));
+ return;
+ case dom::WebGLRenderingContext_Binding::UNPACK_PREMULTIPLY_ALPHA_WEBGL:
+ retval.set(JS::BooleanValue(state.mPixelUnpackState.premultiplyAlpha));
+ return;
+ case dom::WebGLRenderingContext_Binding::UNPACK_COLORSPACE_CONVERSION_WEBGL:
+ retval.set(JS::NumberValue(state.mPixelUnpackState.colorspaceConversion));
+ return;
+
+ case dom::WEBGL_provoking_vertex_Binding::PROVOKING_VERTEX_WEBGL:
+ if (!IsExtensionEnabled(WebGLExtensionID::WEBGL_provoking_vertex)) break;
+ retval.set(JS::NumberValue(UnderlyingValue(state.mProvokingVertex)));
+ return;
+
+ // -
+ // Array returns
+
+ // 2 floats
+ case LOCAL_GL_DEPTH_RANGE:
+ retval.set(Create<dom::Float32Array>(cx, this, state.mDepthRange, rv));
+ return;
+
+ case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
+ retval.set(
+ Create<dom::Float32Array>(cx, this, limits.pointSizeRange, rv));
+ return;
+
+ case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE:
+ retval.set(
+ Create<dom::Float32Array>(cx, this, limits.lineWidthRange, rv));
+ return;
+
+ // 4 floats
+ case LOCAL_GL_COLOR_CLEAR_VALUE:
+ retval.set(Create<dom::Float32Array>(cx, this, state.mClearColor, rv));
+ return;
+
+ case LOCAL_GL_BLEND_COLOR:
+ retval.set(Create<dom::Float32Array>(cx, this, state.mBlendColor, rv));
+ return;
+
+ // 2 ints
+ case LOCAL_GL_MAX_VIEWPORT_DIMS: {
+ const auto dims =
+ std::array<uint32_t, 2>{limits.maxViewportDim, limits.maxViewportDim};
+ retval.set(CreateAs<dom::Int32Array, const int32_t*>(cx, this, dims, rv));
+ return;
+ }
+
+ // 4 ints
+ case LOCAL_GL_SCISSOR_BOX:
+ retval.set(Create<dom::Int32Array>(cx, this, state.mScissor, rv));
+ return;
+
+ case LOCAL_GL_VIEWPORT:
+ retval.set(Create<dom::Int32Array>(cx, this, state.mViewport, rv));
+ return;
+
+ // any
+ case LOCAL_GL_COMPRESSED_TEXTURE_FORMATS:
+ retval.set(Create<dom::Uint32Array>(cx, this,
+ state.mCompressedTextureFormats, rv));
+ return;
+ }
+
+ if (mIsWebGL2) {
+ switch (pname) {
+ case LOCAL_GL_COPY_READ_BUFFER_BINDING:
+ fnSetRetval_Buffer(LOCAL_GL_COPY_READ_BUFFER);
+ return;
+
+ case LOCAL_GL_COPY_WRITE_BUFFER_BINDING:
+ fnSetRetval_Buffer(LOCAL_GL_COPY_WRITE_BUFFER);
+ return;
+
+ case LOCAL_GL_DRAW_FRAMEBUFFER_BINDING:
+ (void)ToJSValueOrNull(cx, state.mBoundDrawFb, retval);
+ return;
+
+ case LOCAL_GL_PIXEL_PACK_BUFFER_BINDING:
+ fnSetRetval_Buffer(LOCAL_GL_PIXEL_PACK_BUFFER);
+ return;
+
+ case LOCAL_GL_PIXEL_UNPACK_BUFFER_BINDING:
+ fnSetRetval_Buffer(LOCAL_GL_PIXEL_UNPACK_BUFFER);
+ return;
+
+ case LOCAL_GL_READ_FRAMEBUFFER_BINDING:
+ (void)ToJSValueOrNull(cx, state.mBoundReadFb, retval);
+ return;
+
+ case LOCAL_GL_SAMPLER_BINDING: {
+ const auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
+ (void)ToJSValueOrNull(cx, texUnit.sampler, retval);
+ return;
+ }
+
+ case LOCAL_GL_TEXTURE_BINDING_2D_ARRAY:
+ fnSetRetval_Tex(LOCAL_GL_TEXTURE_2D_ARRAY);
+ return;
+
+ case LOCAL_GL_TEXTURE_BINDING_3D:
+ fnSetRetval_Tex(LOCAL_GL_TEXTURE_3D);
+ return;
+
+ case LOCAL_GL_TRANSFORM_FEEDBACK_BINDING: {
+ auto ret = state.mBoundTfo;
+ if (ret == state.mDefaultTfo) {
+ ret = nullptr;
+ }
+ (void)ToJSValueOrNull(cx, ret, retval);
+ return;
+ }
+
+ case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
+ fnSetRetval_Buffer(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER);
+ return;
+
+ case LOCAL_GL_UNIFORM_BUFFER_BINDING:
+ fnSetRetval_Buffer(LOCAL_GL_UNIFORM_BUFFER);
+ return;
+
+ case LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS:
+ retval.set(
+ JS::NumberValue(webgl::kMaxTransformFeedbackSeparateAttribs));
+ return;
+ case LOCAL_GL_MAX_UNIFORM_BUFFER_BINDINGS:
+ retval.set(JS::NumberValue(limits.maxUniformBufferBindings));
+ return;
+ case LOCAL_GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT:
+ retval.set(JS::NumberValue(limits.uniformBufferOffsetAlignment));
+ return;
+ case LOCAL_GL_MAX_3D_TEXTURE_SIZE:
+ retval.set(JS::NumberValue(limits.maxTex3dSize));
+ return;
+ case LOCAL_GL_MAX_ARRAY_TEXTURE_LAYERS:
+ retval.set(JS::NumberValue(limits.maxTexArrayLayers));
+ return;
+
+ case LOCAL_GL_PACK_ROW_LENGTH:
+ retval.set(JS::NumberValue(state.mPixelPackState.rowLength));
+ return;
+ case LOCAL_GL_PACK_SKIP_PIXELS:
+ retval.set(JS::NumberValue(state.mPixelPackState.skipPixels));
+ return;
+ case LOCAL_GL_PACK_SKIP_ROWS:
+ retval.set(JS::NumberValue(state.mPixelPackState.skipRows));
+ return;
+
+ case LOCAL_GL_UNPACK_IMAGE_HEIGHT:
+ retval.set(JS::NumberValue(state.mPixelUnpackState.imageHeight));
+ return;
+ case LOCAL_GL_UNPACK_ROW_LENGTH:
+ retval.set(JS::NumberValue(state.mPixelUnpackState.rowLength));
+ return;
+ case LOCAL_GL_UNPACK_SKIP_IMAGES:
+ retval.set(JS::NumberValue(state.mPixelUnpackState.skipImages));
+ return;
+ case LOCAL_GL_UNPACK_SKIP_PIXELS:
+ retval.set(JS::NumberValue(state.mPixelUnpackState.skipPixels));
+ return;
+ case LOCAL_GL_UNPACK_SKIP_ROWS:
+ retval.set(JS::NumberValue(state.mPixelUnpackState.skipRows));
+ return;
+ } // switch pname
+ } // if webgl2
+
+ // -
+
+ if (!debug) {
+ const auto GetUnmaskedRenderer = [&]() {
+ const auto prefLock = StaticPrefs::webgl_override_unmasked_renderer();
+ if (!prefLock->IsEmpty()) {
+ return Some(ToString(*prefLock));
+ }
+ return GetString(LOCAL_GL_RENDERER);
+ };
+
+ const auto GetUnmaskedVendor = [&]() {
+ const auto prefLock = StaticPrefs::webgl_override_unmasked_vendor();
+ if (!prefLock->IsEmpty()) {
+ return Some(ToString(*prefLock));
+ }
+ return GetString(LOCAL_GL_VENDOR);
+ };
+
+ // -
+
+ Maybe<std::string> ret;
+
+ switch (pname) {
+ case LOCAL_GL_VENDOR:
+ ret = Some(std::string{"Mozilla"});
+ break;
+
+ case LOCAL_GL_RENDERER: {
+ bool allowRenderer = StaticPrefs::webgl_enable_renderer_query();
+ if (ShouldResistFingerprinting()) {
+ allowRenderer = false;
+ }
+ if (allowRenderer) {
+ ret = GetUnmaskedRenderer();
+ if (ret) {
+ ret = Some(webgl::SanitizeRenderer(*ret));
+ }
+ }
+ if (!ret) {
+ ret = Some(std::string{"Mozilla"});
+ }
+ break;
+ }
+
+ case LOCAL_GL_VERSION:
+ if (mIsWebGL2) {
+ ret = Some(std::string{"WebGL 2.0"});
+ } else {
+ ret = Some(std::string{"WebGL 1.0"});
+ }
+ break;
+
+ case LOCAL_GL_SHADING_LANGUAGE_VERSION:
+ if (mIsWebGL2) {
+ ret = Some(std::string{"WebGL GLSL ES 3.00"});
+ } else {
+ ret = Some(std::string{"WebGL GLSL ES 1.0"});
+ }
+ break;
+
+ case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
+ case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL: {
+ if (!IsExtensionEnabled(WebGLExtensionID::WEBGL_debug_renderer_info)) {
+ EnqueueError_ArgEnum("pname", pname);
+ return;
+ }
+
+ switch (pname) {
+ case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
+ ret = GetUnmaskedRenderer();
+ if (ret && StaticPrefs::webgl_sanitize_unmasked_renderer()) {
+ *ret = webgl::SanitizeRenderer(*ret);
+ }
+ break;
+
+ case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
+ ret = GetUnmaskedVendor();
+ break;
+
+ default:
+ MOZ_CRASH();
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ if (ret) {
+ retval.set(StringValue(cx, *ret, rv));
+ return;
+ }
+ } // if (!debug)
+
+ // -
+
+ bool debugOnly = false;
+ bool asString = false;
+
+ switch (pname) {
+ case LOCAL_GL_EXTENSIONS:
+ case LOCAL_GL_RENDERER:
+ case LOCAL_GL_VENDOR:
+ case LOCAL_GL_VERSION:
+ case dom::MOZ_debug_Binding::WSI_INFO:
+ debugOnly = true;
+ asString = true;
+ break;
+
+ case dom::MOZ_debug_Binding::DOES_INDEX_VALIDATION:
+ debugOnly = true;
+ break;
+
+ default:
+ break;
+ }
+
+ if (debugOnly && !debug) {
+ EnqueueError_ArgEnum("pname", pname);
+ return;
+ }
+
+ // -
+
+ if (asString) {
+ const auto maybe = GetString(pname);
+ if (maybe) {
+ auto str = *maybe;
+ if (pname == dom::MOZ_debug_Binding::WSI_INFO) {
+ nsPrintfCString more("\nIsWebglOutOfProcessEnabled: %i",
+ int(IsWebglOutOfProcessEnabled()));
+ str += more.BeginReading();
+ }
+ retval.set(StringValue(cx, str.c_str(), rv));
+ }
+ } else {
+ const auto maybe = GetNumber(pname);
+ if (maybe) {
+ switch (pname) {
+ // WebGL 1:
+ case LOCAL_GL_BLEND:
+ case LOCAL_GL_CULL_FACE:
+ case LOCAL_GL_DEPTH_TEST:
+ case LOCAL_GL_DEPTH_WRITEMASK:
+ case LOCAL_GL_DITHER:
+ case LOCAL_GL_POLYGON_OFFSET_FILL:
+ case LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE:
+ case LOCAL_GL_SAMPLE_COVERAGE:
+ case LOCAL_GL_SAMPLE_COVERAGE_INVERT:
+ case LOCAL_GL_SCISSOR_TEST:
+ case LOCAL_GL_STENCIL_TEST:
+ // WebGL 2:
+ case LOCAL_GL_RASTERIZER_DISCARD:
+ case LOCAL_GL_TRANSFORM_FEEDBACK_ACTIVE:
+ case LOCAL_GL_TRANSFORM_FEEDBACK_PAUSED:
+ retval.set(JS::BooleanValue(*maybe));
+ break;
+
+ // 4 bools
+ case LOCAL_GL_COLOR_WRITEMASK: {
+ const auto mask = uint8_t(*maybe);
+ const auto bs = std::bitset<4>(mask);
+ const auto src = std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]};
+ JS::Rooted<JS::Value> arr(cx);
+ if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ retval.set(arr);
+ return;
+ }
+
+ default:
+ retval.set(JS::NumberValue(*maybe));
+ break;
+ }
+ }
+ }
+}
+
+void ClientWebGLContext::GetBufferParameter(
+ JSContext* cx, GLenum target, GLenum pname,
+ JS::MutableHandle<JS::Value> retval) const {
+ retval.set(JS::NullValue());
+ if (IsContextLost()) return;
+
+ const auto maybe = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetBufferParameter(target, pname);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ Maybe<double> ret;
+ if (!child->SendGetBufferParameter(target, pname, &ret)) {
+ ret.reset();
+ }
+ return ret;
+ }();
+ if (maybe) {
+ retval.set(JS::NumberValue(*maybe));
+ }
+}
+
+bool IsFramebufferTarget(const bool isWebgl2, const GLenum target) {
+ switch (target) {
+ case LOCAL_GL_FRAMEBUFFER:
+ return true;
+
+ case LOCAL_GL_DRAW_FRAMEBUFFER:
+ case LOCAL_GL_READ_FRAMEBUFFER:
+ return isWebgl2;
+
+ default:
+ return false;
+ }
+}
+
+void ClientWebGLContext::GetFramebufferAttachmentParameter(
+ JSContext* const cx, const GLenum target, const GLenum attachment,
+ const GLenum pname, JS::MutableHandle<JS::Value> retval,
+ ErrorResult& rv) const {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getFramebufferAttachmentParameter");
+ if (IsContextLost()) return;
+
+ const auto& state = State();
+
+ if (!IsFramebufferTarget(mIsWebGL2, target)) {
+ EnqueueError_ArgEnum("target", target);
+ return;
+ }
+ auto fb = state.mBoundDrawFb;
+ if (target == LOCAL_GL_READ_FRAMEBUFFER) {
+ fb = state.mBoundReadFb;
+ }
+
+ const auto fnGet = [&](const GLenum pname) {
+ const auto fbId = fb ? fb->mId : 0;
+
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetFramebufferAttachmentParameter(fbId, attachment,
+ pname);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ Maybe<double> ret;
+ if (!child->SendGetFramebufferAttachmentParameter(fbId, attachment, pname,
+ &ret)) {
+ ret.reset();
+ }
+ return ret;
+ };
+
+ if (fb) {
+ if (fb->mOpaque) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "An opaque framebuffer's attachments cannot be inspected or "
+ "changed.");
+ return;
+ }
+ auto attachmentSlotEnum = attachment;
+ if (mIsWebGL2 && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+ // In webgl2, DEPTH_STENCIL is valid iff the DEPTH and STENCIL images
+ // match, so check if the server errors.
+ const auto maybe = fnGet(LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE);
+ if (!maybe) return;
+ attachmentSlotEnum = LOCAL_GL_DEPTH_ATTACHMENT;
+ }
+
+ const auto maybeSlot = fb->GetAttachment(attachmentSlotEnum);
+ if (!maybeSlot) {
+ EnqueueError_ArgEnum("attachment", attachment);
+ return;
+ }
+ const auto& attached = *maybeSlot;
+
+ // -
+
+ if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) {
+ if (attached.rb) {
+ (void)ToJSValueOrNull(cx, attached.rb, retval);
+ } else {
+ if (!mIsWebGL2 && !attached.tex) {
+ EnqueueError_ArgEnum("pname", pname);
+ return;
+ }
+ (void)ToJSValueOrNull(cx, attached.tex, retval);
+ }
+ return;
+ }
+ }
+
+ const auto maybe = fnGet(pname);
+ if (maybe) {
+ retval.set(JS::NumberValue(*maybe));
+ }
+}
+
+void ClientWebGLContext::GetRenderbufferParameter(
+ JSContext* cx, GLenum target, GLenum pname,
+ JS::MutableHandle<JS::Value> retval) const {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getRenderbufferParameter");
+ if (IsContextLost()) return;
+
+ if (target != LOCAL_GL_RENDERBUFFER) {
+ EnqueueError_ArgEnum("target", target);
+ return;
+ }
+
+ const auto& state = State();
+ const auto& rb = state.mBoundRb;
+ const auto rbId = rb ? rb->mId : 0;
+ const auto maybe = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetRenderbufferParameter(rbId, pname);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ Maybe<double> ret;
+ if (!child->SendGetRenderbufferParameter(rbId, pname, &ret)) {
+ ret.reset();
+ }
+ return ret;
+ }();
+ if (maybe) {
+ retval.set(JS::NumberValue(*maybe));
+ }
+}
+
+void ClientWebGLContext::GetIndexedParameter(
+ JSContext* cx, GLenum target, GLuint index,
+ JS::MutableHandle<JS::Value> retval, ErrorResult& rv) const {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getIndexedParameter");
+ if (IsContextLost()) return;
+
+ const auto& state = State();
+
+ switch (target) {
+ case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: {
+ const auto& list = state.mBoundTfo->mAttribBuffers;
+ if (index >= list.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "`index` (%u) >= MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS",
+ index);
+ return;
+ }
+ (void)ToJSValueOrNull(cx, list[index], retval);
+ return;
+ }
+
+ case LOCAL_GL_UNIFORM_BUFFER_BINDING: {
+ const auto& list = state.mBoundUbos;
+ if (index >= list.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "`index` (%u) >= MAX_UNIFORM_BUFFER_BINDINGS", index);
+ return;
+ }
+ (void)ToJSValueOrNull(cx, list[index], retval);
+ return;
+ }
+ }
+
+ const auto maybe = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetIndexedParameter(target, index);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ Maybe<double> ret;
+ if (!child->SendGetIndexedParameter(target, index, &ret)) {
+ ret.reset();
+ }
+ return ret;
+ }();
+ if (maybe) {
+ switch (target) {
+ case LOCAL_GL_COLOR_WRITEMASK: {
+ const auto bs = std::bitset<4>(*maybe);
+ const auto src = std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]};
+ JS::Rooted<JS::Value> arr(cx);
+ if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ retval.set(arr);
+ return;
+ }
+
+ default:
+ retval.set(JS::NumberValue(*maybe));
+ return;
+ }
+ }
+}
+
+void ClientWebGLContext::GetUniform(JSContext* const cx,
+ const WebGLProgramJS& prog,
+ const WebGLUniformLocationJS& loc,
+ JS::MutableHandle<JS::Value> retval) {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getUniform");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "prog")) return;
+ if (!loc.ValidateUsable(*this, "loc")) return;
+
+ const auto& progLinkResult = GetLinkResult(prog);
+ if (!progLinkResult.success) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION, "Program is not linked.");
+ return;
+ }
+ const auto& uniformLinkResult = loc.mParent.lock();
+ if (uniformLinkResult.get() != &progLinkResult) {
+ EnqueueError(
+ LOCAL_GL_INVALID_OPERATION,
+ "UniformLocation is not from the most recent linking of Program.");
+ return;
+ }
+
+ const auto res = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetUniform(prog.mId, loc.mLocation);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ webgl::GetUniformData ret;
+ if (!child->SendGetUniform(prog.mId, loc.mLocation, &ret)) {
+ ret = {};
+ }
+ return ret;
+ }();
+ if (!res.type) return;
+
+ const auto elemCount = ElemTypeComponents(res.type);
+ MOZ_ASSERT(elemCount);
+
+ switch (res.type) {
+ case LOCAL_GL_BOOL:
+ retval.set(JS::BooleanValue(res.data[0]));
+ return;
+
+ case LOCAL_GL_FLOAT: {
+ const auto ptr = reinterpret_cast<const float*>(res.data);
+ MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
+ return;
+ }
+ case LOCAL_GL_INT: {
+ const auto ptr = reinterpret_cast<const int32_t*>(res.data);
+ MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
+ return;
+ }
+ case LOCAL_GL_UNSIGNED_INT:
+ case LOCAL_GL_SAMPLER_2D:
+ case LOCAL_GL_SAMPLER_3D:
+ case LOCAL_GL_SAMPLER_CUBE:
+ case LOCAL_GL_SAMPLER_2D_SHADOW:
+ case LOCAL_GL_SAMPLER_2D_ARRAY:
+ case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+ case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+ case LOCAL_GL_INT_SAMPLER_2D:
+ case LOCAL_GL_INT_SAMPLER_3D:
+ case LOCAL_GL_INT_SAMPLER_CUBE:
+ case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: {
+ const auto ptr = reinterpret_cast<const uint32_t*>(res.data);
+ MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
+ return;
+ }
+
+ // -
+
+ case LOCAL_GL_BOOL_VEC2:
+ case LOCAL_GL_BOOL_VEC3:
+ case LOCAL_GL_BOOL_VEC4: {
+ const auto intArr = reinterpret_cast<const int32_t*>(res.data);
+ bool boolArr[4] = {};
+ for (const auto i : IntegerRange(elemCount)) {
+ boolArr[i] = bool(intArr[i]);
+ }
+ MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, boolArr, elemCount, retval));
+ return;
+ }
+
+ case LOCAL_GL_FLOAT_VEC2:
+ case LOCAL_GL_FLOAT_VEC3:
+ case LOCAL_GL_FLOAT_VEC4:
+ case LOCAL_GL_FLOAT_MAT2:
+ case LOCAL_GL_FLOAT_MAT3:
+ case LOCAL_GL_FLOAT_MAT4:
+ case LOCAL_GL_FLOAT_MAT2x3:
+ case LOCAL_GL_FLOAT_MAT2x4:
+ case LOCAL_GL_FLOAT_MAT3x2:
+ case LOCAL_GL_FLOAT_MAT3x4:
+ case LOCAL_GL_FLOAT_MAT4x2:
+ case LOCAL_GL_FLOAT_MAT4x3: {
+ const auto ptr = reinterpret_cast<const float*>(res.data);
+ JSObject* obj = dom::Float32Array::Create(cx, this, elemCount, ptr);
+ MOZ_ASSERT(obj);
+ retval.set(JS::ObjectOrNullValue(obj));
+ return;
+ }
+
+ case LOCAL_GL_INT_VEC2:
+ case LOCAL_GL_INT_VEC3:
+ case LOCAL_GL_INT_VEC4: {
+ const auto ptr = reinterpret_cast<const int32_t*>(res.data);
+ JSObject* obj = dom::Int32Array::Create(cx, this, elemCount, ptr);
+ MOZ_ASSERT(obj);
+ retval.set(JS::ObjectOrNullValue(obj));
+ return;
+ }
+
+ case LOCAL_GL_UNSIGNED_INT_VEC2:
+ case LOCAL_GL_UNSIGNED_INT_VEC3:
+ case LOCAL_GL_UNSIGNED_INT_VEC4: {
+ const auto ptr = reinterpret_cast<const uint32_t*>(res.data);
+ JSObject* obj = dom::Uint32Array::Create(cx, this, elemCount, ptr);
+ MOZ_ASSERT(obj);
+ retval.set(JS::ObjectOrNullValue(obj));
+ return;
+ }
+
+ default:
+ MOZ_CRASH("GFX: Invalid elemType.");
+ }
+}
+
+already_AddRefed<WebGLShaderPrecisionFormatJS>
+ClientWebGLContext::GetShaderPrecisionFormat(const GLenum shadertype,
+ const GLenum precisiontype) {
+ if (IsContextLost()) return nullptr;
+ const auto info = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetShaderPrecisionFormat(shadertype, precisiontype);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ Maybe<webgl::ShaderPrecisionFormat> ret;
+ if (!child->SendGetShaderPrecisionFormat(shadertype, precisiontype, &ret)) {
+ ret.reset();
+ }
+ return ret;
+ }();
+
+ if (!info) return nullptr;
+ return AsAddRefed(new WebGLShaderPrecisionFormatJS(*info));
+}
+
+void ClientWebGLContext::BlendColor(GLclampf r, GLclampf g, GLclampf b,
+ GLclampf a) {
+ const FuncScope funcScope(*this, "blendColor");
+ if (IsContextLost()) return;
+ auto& state = State();
+
+ auto& cache = state.mBlendColor;
+ cache[0] = r;
+ cache[1] = g;
+ cache[2] = b;
+ cache[3] = a;
+
+ Run<RPROC(BlendColor)>(r, g, b, a);
+}
+
+void ClientWebGLContext::BlendEquationSeparateI(Maybe<GLuint> i, GLenum modeRGB,
+ GLenum modeAlpha) {
+ Run<RPROC(BlendEquationSeparate)>(i, modeRGB, modeAlpha);
+}
+
+void ClientWebGLContext::BlendFuncSeparateI(Maybe<GLuint> i, GLenum srcRGB,
+ GLenum dstRGB, GLenum srcAlpha,
+ GLenum dstAlpha) {
+ Run<RPROC(BlendFuncSeparate)>(i, srcRGB, dstRGB, srcAlpha, dstAlpha);
+}
+
+GLenum ClientWebGLContext::CheckFramebufferStatus(GLenum target) {
+ if (IsContextLost()) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
+
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->CheckFramebufferStatus(target);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ GLenum ret = 0;
+ if (!child->SendCheckFramebufferStatus(target, &ret)) {
+ ret = 0;
+ }
+ return ret;
+}
+
+void ClientWebGLContext::Clear(GLbitfield mask) {
+ Run<RPROC(Clear)>(mask);
+
+ AfterDrawCall();
+}
+
+// -
+
+void ClientWebGLContext::ClearBufferTv(const GLenum buffer,
+ const GLint drawBuffer,
+ const webgl::AttribBaseType type,
+ const Range<const uint8_t>& view,
+ const GLuint srcElemOffset) {
+ const FuncScope funcScope(*this, "clearBufferu?[fi]v");
+ if (IsContextLost()) return;
+
+ const auto byteOffset = CheckedInt<size_t>(srcElemOffset) * sizeof(float);
+ if (!byteOffset.isValid() || byteOffset.value() > view.length()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`srcOffset` too large for `values`.");
+ return;
+ }
+ webgl::TypedQuad data;
+ data.type = type;
+
+ auto dataSize = data.data.size();
+ switch (buffer) {
+ case LOCAL_GL_COLOR:
+ break;
+
+ case LOCAL_GL_DEPTH:
+ dataSize = sizeof(float);
+ break;
+
+ case LOCAL_GL_STENCIL:
+ dataSize = sizeof(int32_t);
+ break;
+
+ default:
+ EnqueueError_ArgEnum("buffer", buffer);
+ return;
+ }
+
+ const auto requiredBytes = byteOffset + dataSize;
+ if (!requiredBytes.isValid() || requiredBytes.value() > view.length()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`values` too small.");
+ return;
+ }
+
+ memcpy(data.data.data(), view.begin().get() + byteOffset.value(), dataSize);
+ Run<RPROC(ClearBufferTv)>(buffer, drawBuffer, data);
+
+ AfterDrawCall();
+}
+
+void ClientWebGLContext::ClearBufferfi(GLenum buffer, GLint drawBuffer,
+ GLfloat depth, GLint stencil) {
+ Run<RPROC(ClearBufferfi)>(buffer, drawBuffer, depth, stencil);
+
+ AfterDrawCall();
+}
+
+// -
+
+void ClientWebGLContext::ClearColor(GLclampf r, GLclampf g, GLclampf b,
+ GLclampf a) {
+ const FuncScope funcScope(*this, "clearColor");
+ if (IsContextLost()) return;
+ auto& state = State();
+
+ auto& cache = state.mClearColor;
+ cache[0] = r;
+ cache[1] = g;
+ cache[2] = b;
+ cache[3] = a;
+
+ Run<RPROC(ClearColor)>(r, g, b, a);
+}
+
+void ClientWebGLContext::ClearDepth(GLclampf v) { Run<RPROC(ClearDepth)>(v); }
+
+void ClientWebGLContext::ClearStencil(GLint v) { Run<RPROC(ClearStencil)>(v); }
+
+void ClientWebGLContext::ColorMaskI(Maybe<GLuint> i, bool r, bool g, bool b,
+ bool a) const {
+ const FuncScope funcScope(*this, "colorMask");
+ if (IsContextLost()) return;
+
+ const uint8_t mask =
+ uint8_t(r << 0) | uint8_t(g << 1) | uint8_t(b << 2) | uint8_t(a << 3);
+ Run<RPROC(ColorMask)>(i, mask);
+}
+
+void ClientWebGLContext::CullFace(GLenum face) { Run<RPROC(CullFace)>(face); }
+
+void ClientWebGLContext::DepthFunc(GLenum func) { Run<RPROC(DepthFunc)>(func); }
+
+void ClientWebGLContext::DepthMask(WebGLboolean b) { Run<RPROC(DepthMask)>(b); }
+
+void ClientWebGLContext::DepthRange(GLclampf zNear, GLclampf zFar) {
+ const FuncScope funcScope(*this, "depthRange");
+ if (IsContextLost()) return;
+ auto& state = State();
+
+ state.mDepthRange = {zNear, zFar};
+
+ Run<RPROC(DepthRange)>(zNear, zFar);
+}
+
+void ClientWebGLContext::Flush(const bool flushGl) {
+ const FuncScope funcScope(*this, "flush");
+ if (IsContextLost()) return;
+
+ if (flushGl) {
+ Run<RPROC(Flush)>();
+ }
+
+ if (mNotLost->inProcess) return;
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+}
+
+void ClientWebGLContext::Finish() {
+ if (IsContextLost()) return;
+
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ inProcess->Finish();
+ return;
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ (void)child->SendFinish();
+}
+
+void ClientWebGLContext::FrontFace(GLenum mode) { Run<RPROC(FrontFace)>(mode); }
+
+GLenum ClientWebGLContext::GetError() {
+ const FuncScope funcScope(*this, "getError");
+ if (mNextError) {
+ const auto ret = mNextError;
+ mNextError = 0;
+ return ret;
+ }
+ if (IsContextLost()) return 0;
+
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetError();
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ GLenum ret = 0;
+ if (!child->SendGetError(&ret)) {
+ ret = 0;
+ }
+ return ret;
+}
+
+void ClientWebGLContext::Hint(GLenum target, GLenum mode) {
+ Run<RPROC(Hint)>(target, mode);
+}
+
+void ClientWebGLContext::LineWidth(GLfloat width) {
+ Run<RPROC(LineWidth)>(width);
+}
+
+Maybe<webgl::ErrorInfo> SetPixelUnpack(
+ const bool isWebgl2, webgl::PixelUnpackStateWebgl* const unpacking,
+ const GLenum pname, const GLint param);
+
+void ClientWebGLContext::PixelStorei(const GLenum pname, const GLint iparam) {
+ const FuncScope funcScope(*this, "pixelStorei");
+ if (IsContextLost()) return;
+ if (!ValidateNonNegative("param", iparam)) return;
+ const auto param = static_cast<uint32_t>(iparam);
+
+ auto& state = State();
+ auto& packState = state.mPixelPackState;
+ switch (pname) {
+ case LOCAL_GL_PACK_ALIGNMENT:
+ switch (param) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ break;
+ default:
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "PACK_ALIGNMENT must be one of [1,2,4,8], was %i.",
+ iparam);
+ return;
+ }
+ packState.alignmentInTypeElems = param;
+ return;
+
+ case LOCAL_GL_PACK_ROW_LENGTH:
+ if (!mIsWebGL2) break;
+ packState.rowLength = param;
+ return;
+
+ case LOCAL_GL_PACK_SKIP_PIXELS:
+ if (!mIsWebGL2) break;
+ packState.skipPixels = param;
+ return;
+
+ case LOCAL_GL_PACK_SKIP_ROWS:
+ if (!mIsWebGL2) break;
+ packState.skipRows = param;
+ return;
+
+ case dom::MOZ_debug_Binding::UNPACK_REQUIRE_FASTPATH:
+ if (!IsSupported(WebGLExtensionID::MOZ_debug)) {
+ EnqueueError_ArgEnum("pname", pname);
+ return;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ const auto err =
+ SetPixelUnpack(mIsWebGL2, &state.mPixelUnpackState, pname, iparam);
+ if (err) {
+ EnqueueError(*err);
+ return;
+ }
+}
+
+void ClientWebGLContext::PolygonOffset(GLfloat factor, GLfloat units) {
+ Run<RPROC(PolygonOffset)>(factor, units);
+}
+
+void ClientWebGLContext::SampleCoverage(GLclampf value, WebGLboolean invert) {
+ Run<RPROC(SampleCoverage)>(value, invert);
+}
+
+void ClientWebGLContext::Scissor(GLint x, GLint y, GLsizei width,
+ GLsizei height) {
+ const FuncScope funcScope(*this, "scissor");
+ if (IsContextLost()) return;
+ auto& state = State();
+
+ if (!ValidateNonNegative("width", width) ||
+ !ValidateNonNegative("height", height)) {
+ return;
+ }
+
+ state.mScissor = {x, y, width, height};
+
+ Run<RPROC(Scissor)>(x, y, width, height);
+}
+
+void ClientWebGLContext::StencilFuncSeparate(GLenum face, GLenum func,
+ GLint ref, GLuint mask) {
+ Run<RPROC(StencilFuncSeparate)>(face, func, ref, mask);
+}
+
+void ClientWebGLContext::StencilMaskSeparate(GLenum face, GLuint mask) {
+ Run<RPROC(StencilMaskSeparate)>(face, mask);
+}
+
+void ClientWebGLContext::StencilOpSeparate(GLenum face, GLenum sfail,
+ GLenum dpfail, GLenum dppass) {
+ Run<RPROC(StencilOpSeparate)>(face, sfail, dpfail, dppass);
+}
+
+void ClientWebGLContext::Viewport(GLint x, GLint y, GLsizei width,
+ GLsizei height) {
+ const FuncScope funcScope(*this, "viewport");
+ if (IsContextLost()) return;
+ auto& state = State();
+
+ if (!ValidateNonNegative("width", width) ||
+ !ValidateNonNegative("height", height)) {
+ return;
+ }
+
+ state.mViewport = {x, y, width, height};
+
+ Run<RPROC(Viewport)>(x, y, width, height);
+}
+
+// ------------------------- Buffer Objects -------------------------
+
+Maybe<const webgl::ErrorInfo> ValidateBindBuffer(
+ const GLenum target, const webgl::BufferKind curKind) {
+ if (curKind == webgl::BufferKind::Undefined) return {};
+
+ auto requiredKind = webgl::BufferKind::NonIndex;
+ switch (target) {
+ case LOCAL_GL_COPY_READ_BUFFER:
+ case LOCAL_GL_COPY_WRITE_BUFFER:
+ return {}; // Always ok
+
+ case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
+ requiredKind = webgl::BufferKind::Index;
+ break;
+
+ default:
+ break;
+ }
+
+ if (curKind != requiredKind) {
+ const auto fnKindStr = [&](const webgl::BufferKind kind) {
+ if (kind == webgl::BufferKind::Index) return "ELEMENT_ARRAY_BUFFER";
+ return "non-ELEMENT_ARRAY_BUFFER";
+ };
+ const auto info = nsPrintfCString(
+ "Buffer previously bound to %s cannot be now bound to %s.",
+ fnKindStr(curKind), fnKindStr(requiredKind));
+ return Some(
+ webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION, info.BeginReading()});
+ }
+
+ return {};
+}
+
+Maybe<webgl::ErrorInfo> CheckBindBufferRange(
+ const GLenum target, const GLuint index, const bool isBuffer,
+ const uint64_t offset, const uint64_t size, const webgl::Limits& limits) {
+ const auto fnSome = [&](const GLenum type, const nsACString& info) {
+ return Some(webgl::ErrorInfo{type, info.BeginReading()});
+ };
+
+ switch (target) {
+ case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
+ if (index >= webgl::kMaxTransformFeedbackSeparateAttribs) {
+ const auto info = nsPrintfCString(
+ "`index` (%u) must be less than "
+ "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS (%u).",
+ index, webgl::kMaxTransformFeedbackSeparateAttribs);
+ return fnSome(LOCAL_GL_INVALID_VALUE, info);
+ }
+
+ if (offset % 4 != 0 || size % 4 != 0) {
+ const auto info =
+ nsPrintfCString("`offset` (%" PRIu64 ") and `size` (%" PRIu64
+ ") must both be aligned to 4 for"
+ " TRANSFORM_FEEDBACK_BUFFER.",
+ offset, size);
+ return fnSome(LOCAL_GL_INVALID_VALUE, info);
+ }
+ break;
+
+ case LOCAL_GL_UNIFORM_BUFFER:
+ if (index >= limits.maxUniformBufferBindings) {
+ const auto info = nsPrintfCString(
+ "`index` (%u) must be less than MAX_UNIFORM_BUFFER_BINDINGS (%u).",
+ index, limits.maxUniformBufferBindings);
+ return fnSome(LOCAL_GL_INVALID_VALUE, info);
+ }
+
+ if (offset % limits.uniformBufferOffsetAlignment != 0) {
+ const auto info =
+ nsPrintfCString("`offset` (%" PRIu64
+ ") must be aligned to "
+ "UNIFORM_BUFFER_OFFSET_ALIGNMENT (%u).",
+ offset, limits.uniformBufferOffsetAlignment);
+ return fnSome(LOCAL_GL_INVALID_VALUE, info);
+ }
+ break;
+
+ default: {
+ const auto info =
+ nsPrintfCString("Unrecognized `target`: 0x%04x", target);
+ return fnSome(LOCAL_GL_INVALID_ENUM, info);
+ }
+ }
+
+ return {};
+}
+
+// -
+
+void ClientWebGLContext::BindBuffer(const GLenum target,
+ WebGLBufferJS* const buffer) {
+ const FuncScope funcScope(*this, "bindBuffer");
+ if (IsContextLost()) return;
+ if (buffer && !buffer->ValidateUsable(*this, "buffer")) return;
+
+ // -
+ // Check for INVALID_ENUM
+
+ auto& state = State();
+ auto* slot = &(state.mBoundVao->mIndexBuffer);
+ if (target != LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
+ const auto itr = state.mBoundBufferByTarget.find(target);
+ if (itr == state.mBoundBufferByTarget.end()) {
+ EnqueueError_ArgEnum("target", target);
+ return;
+ }
+ slot = &(itr->second);
+ }
+
+ // -
+
+ auto kind = webgl::BufferKind::Undefined;
+ if (buffer) {
+ kind = buffer->mKind;
+ }
+ const auto err = ValidateBindBuffer(target, kind);
+ if (err) {
+ EnqueueError(err->type, "%s", err->info.c_str());
+ return;
+ }
+
+ // -
+ // Validation complete
+
+ if (buffer && buffer->mKind == webgl::BufferKind::Undefined) {
+ if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
+ buffer->mKind = webgl::BufferKind::Index;
+ } else {
+ buffer->mKind = webgl::BufferKind::NonIndex;
+ }
+ }
+ *slot = buffer;
+
+ // -
+
+ Run<RPROC(BindBuffer)>(target, buffer ? buffer->mId : 0);
+}
+
+// -
+
+void ClientWebGLContext::BindBufferRangeImpl(const GLenum target,
+ const GLuint index,
+ WebGLBufferJS* const buffer,
+ const uint64_t offset,
+ const uint64_t size) {
+ if (buffer && !buffer->ValidateUsable(*this, "buffer")) return;
+ auto& state = State();
+
+ // -
+
+ const auto& limits = Limits();
+ auto err =
+ CheckBindBufferRange(target, index, bool(buffer), offset, size, limits);
+ if (err) {
+ EnqueueError(err->type, "%s", err->info.c_str());
+ return;
+ }
+
+ // -
+
+ auto kind = webgl::BufferKind::Undefined;
+ if (buffer) {
+ kind = buffer->mKind;
+ }
+ err = ValidateBindBuffer(target, kind);
+ if (err) {
+ EnqueueError(err->type, "%s", err->info.c_str());
+ return;
+ }
+
+ if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) {
+ if (state.mTfActiveAndNotPaused) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Cannot change TRANSFORM_FEEDBACK_BUFFER while "
+ "TransformFeedback is active and not paused.");
+ return;
+ }
+ }
+
+ // -
+ // Validation complete
+
+ if (buffer && buffer->mKind == webgl::BufferKind::Undefined) {
+ buffer->mKind = webgl::BufferKind::NonIndex;
+ }
+
+ // -
+
+ switch (target) {
+ case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
+ state.mBoundTfo->mAttribBuffers[index] = buffer;
+ break;
+
+ case LOCAL_GL_UNIFORM_BUFFER:
+ state.mBoundUbos[index] = buffer;
+ break;
+
+ default:
+ MOZ_CRASH("Bad `target`");
+ }
+ state.mBoundBufferByTarget[target] = buffer;
+
+ // -
+
+ Run<RPROC(BindBufferRange)>(target, index, buffer ? buffer->mId : 0, offset,
+ size);
+}
+
+void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset,
+ const dom::ArrayBufferView& dstData,
+ GLuint dstElemOffset,
+ GLuint dstElemCountOverride) {
+ const FuncScope funcScope(*this, "getBufferSubData");
+ if (IsContextLost()) return;
+ const auto notLost =
+ mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
+ if (!ValidateNonNegative("srcByteOffset", srcByteOffset)) return;
+
+ uint8_t* bytes;
+ size_t byteLen;
+ if (!ValidateArrayBufferView(dstData, dstElemOffset, dstElemCountOverride,
+ LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
+ return;
+ }
+ const auto destView = Range<uint8_t>{bytes, byteLen};
+
+ const auto& inProcessContext = notLost->inProcess;
+ if (inProcessContext) {
+ inProcessContext->GetBufferSubData(target, srcByteOffset, destView);
+ return;
+ }
+
+ const auto& child = notLost->outOfProcess;
+ child->FlushPendingCmds();
+ mozilla::ipc::Shmem rawShmem;
+ if (!child->SendGetBufferSubData(target, srcByteOffset, destView.length(),
+ &rawShmem)) {
+ return;
+ }
+ const webgl::RaiiShmem shmem{child, rawShmem};
+ if (!shmem) {
+ EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "Failed to map in sub data buffer.");
+ return;
+ }
+
+ const auto shmemView = shmem.ByteRange();
+ MOZ_RELEASE_ASSERT(shmemView.length() == 1 + destView.length());
+
+ const auto ok = bool(*(shmemView.begin().get()));
+ const auto srcView =
+ Range<const uint8_t>{shmemView.begin() + 1, shmemView.end()};
+ if (ok) {
+ Memcpy(destView.begin(), srcView.begin(), srcView.length());
+ }
+}
+
+////
+
+void ClientWebGLContext::BufferData(GLenum target, WebGLsizeiptr rawSize,
+ GLenum usage) {
+ const FuncScope funcScope(*this, "bufferData");
+ if (!ValidateNonNegative("size", rawSize)) return;
+
+ const auto size = MaybeAs<size_t>(rawSize);
+ if (!size) {
+ EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "`size` too large for platform.");
+ return;
+ }
+
+ const auto data = RawBuffer<>{*size};
+ Run<RPROC(BufferData)>(target, data, usage);
+}
+
+void ClientWebGLContext::BufferData(
+ GLenum target, const dom::Nullable<dom::ArrayBuffer>& maybeSrc,
+ GLenum usage) {
+ const FuncScope funcScope(*this, "bufferData");
+ if (!ValidateNonNull("src", maybeSrc)) return;
+ const auto& src = maybeSrc.Value();
+
+ src.ComputeState();
+ const auto range = Range<const uint8_t>{src.Data(), src.Length()};
+ Run<RPROC(BufferData)>(target, RawBuffer<>(range), usage);
+}
+
+void ClientWebGLContext::BufferData(GLenum target,
+ const dom::ArrayBufferView& src,
+ GLenum usage, GLuint srcElemOffset,
+ GLuint srcElemCountOverride) {
+ const FuncScope funcScope(*this, "bufferData");
+ uint8_t* bytes;
+ size_t byteLen;
+ if (!ValidateArrayBufferView(src, srcElemOffset, srcElemCountOverride,
+ LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
+ return;
+ }
+ const auto range = Range<const uint8_t>{bytes, byteLen};
+ Run<RPROC(BufferData)>(target, RawBuffer<>(range), usage);
+}
+
+void ClientWebGLContext::RawBufferData(GLenum target, const uint8_t* srcBytes,
+ size_t srcLen, GLenum usage) {
+ const FuncScope funcScope(*this, "bufferData");
+
+ const auto srcBuffer =
+ srcBytes ? RawBuffer<>({srcBytes, srcLen}) : RawBuffer<>(srcLen);
+ Run<RPROC(BufferData)>(target, srcBuffer, usage);
+}
+
+////
+
+void ClientWebGLContext::RawBufferSubData(GLenum target,
+ WebGLsizeiptr dstByteOffset,
+ const uint8_t* srcBytes,
+ size_t srcLen, bool unsynchronized) {
+ const FuncScope funcScope(*this, "bufferSubData");
+
+ Run<RPROC(BufferSubData)>(target, dstByteOffset,
+ RawBuffer<>({srcBytes, srcLen}), unsynchronized);
+}
+
+void ClientWebGLContext::BufferSubData(GLenum target,
+ WebGLsizeiptr dstByteOffset,
+ const dom::ArrayBuffer& src) {
+ const FuncScope funcScope(*this, "bufferSubData");
+ src.ComputeState();
+ const auto range = Range<const uint8_t>{src.Data(), src.Length()};
+ Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(range),
+ /* unsynchronized */ false);
+}
+
+void ClientWebGLContext::BufferSubData(GLenum target,
+ WebGLsizeiptr dstByteOffset,
+ const dom::ArrayBufferView& src,
+ GLuint srcElemOffset,
+ GLuint srcElemCountOverride) {
+ const FuncScope funcScope(*this, "bufferSubData");
+ uint8_t* bytes;
+ size_t byteLen;
+ if (!ValidateArrayBufferView(src, srcElemOffset, srcElemCountOverride,
+ LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
+ return;
+ }
+ const auto range = Range<const uint8_t>{bytes, byteLen};
+ Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(range),
+ /* unsynchronized */ false);
+}
+
+void ClientWebGLContext::CopyBufferSubData(GLenum readTarget,
+ GLenum writeTarget,
+ GLintptr readOffset,
+ GLintptr writeOffset,
+ GLsizeiptr size) {
+ const FuncScope funcScope(*this, "copyBufferSubData");
+ if (!ValidateNonNegative("readOffset", readOffset) ||
+ !ValidateNonNegative("writeOffset", writeOffset) ||
+ !ValidateNonNegative("size", size)) {
+ return;
+ }
+ Run<RPROC(CopyBufferSubData)>(
+ readTarget, writeTarget, static_cast<uint64_t>(readOffset),
+ static_cast<uint64_t>(writeOffset), static_cast<uint64_t>(size));
+}
+
+// -------------------------- Framebuffer Objects --------------------------
+
+void ClientWebGLContext::BindFramebuffer(const GLenum target,
+ WebGLFramebufferJS* const fb) {
+ const FuncScope funcScope(*this, "bindFramebuffer");
+ if (IsContextLost()) return;
+ if (fb && !fb->ValidateUsable(*this, "fb")) return;
+
+ if (!IsFramebufferTarget(mIsWebGL2, target)) {
+ EnqueueError_ArgEnum("target", target);
+ return;
+ }
+
+ // -
+
+ auto& state = State();
+
+ switch (target) {
+ case LOCAL_GL_FRAMEBUFFER:
+ state.mBoundDrawFb = fb;
+ state.mBoundReadFb = fb;
+ break;
+
+ case LOCAL_GL_DRAW_FRAMEBUFFER:
+ state.mBoundDrawFb = fb;
+ break;
+ case LOCAL_GL_READ_FRAMEBUFFER:
+ state.mBoundReadFb = fb;
+ break;
+
+ default:
+ MOZ_CRASH();
+ }
+
+ // -
+
+ if (fb) {
+ fb->mHasBeenBound = true;
+ }
+
+ Run<RPROC(BindFramebuffer)>(target, fb ? fb->mId : 0);
+}
+
+// -
+
+void ClientWebGLContext::FramebufferTexture2D(GLenum target, GLenum attachSlot,
+ GLenum bindImageTarget,
+ WebGLTextureJS* const tex,
+ GLint mipLevel) const {
+ const FuncScope funcScope(*this, "framebufferTexture2D");
+ if (IsContextLost()) return;
+
+ const auto bindTexTarget = ImageToTexTarget(bindImageTarget);
+ uint32_t zLayer = 0;
+ switch (bindTexTarget) {
+ case LOCAL_GL_TEXTURE_2D:
+ break;
+ case LOCAL_GL_TEXTURE_CUBE_MAP:
+ zLayer = bindImageTarget - LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X;
+ break;
+ default:
+ EnqueueError_ArgEnum("imageTarget", bindImageTarget);
+ return;
+ }
+
+ if (!mIsWebGL2 &&
+ !IsExtensionEnabled(WebGLExtensionID::OES_fbo_render_mipmap)) {
+ if (mipLevel != 0) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "mipLevel != 0 requires OES_fbo_render_mipmap.");
+ return;
+ }
+ }
+
+ FramebufferAttach(target, attachSlot, bindImageTarget, nullptr, tex,
+ static_cast<uint32_t>(mipLevel), zLayer, 0);
+}
+
+Maybe<webgl::ErrorInfo> CheckFramebufferAttach(const GLenum bindImageTarget,
+ const GLenum curTexTarget,
+ const uint32_t mipLevel,
+ const uint32_t zLayerBase,
+ const uint32_t zLayerCount,
+ const webgl::Limits& limits) {
+ if (!curTexTarget) {
+ return Some(
+ webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
+ "`tex` not yet bound. Call bindTexture first."});
+ }
+
+ auto texTarget = curTexTarget;
+ if (bindImageTarget) {
+ // FramebufferTexture2D
+ const auto bindTexTarget = ImageToTexTarget(bindImageTarget);
+ if (curTexTarget != bindTexTarget) {
+ return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
+ "`tex` cannot be rebound to a new target."});
+ }
+
+ switch (bindTexTarget) {
+ case LOCAL_GL_TEXTURE_2D:
+ case LOCAL_GL_TEXTURE_CUBE_MAP:
+ break;
+ default:
+ return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_ENUM,
+ "`tex` must have been bound to target "
+ "TEXTURE_2D or TEXTURE_CUBE_MAP."});
+ }
+ texTarget = bindTexTarget;
+ } else {
+ // FramebufferTextureLayer/Multiview
+ switch (curTexTarget) {
+ case LOCAL_GL_TEXTURE_2D_ARRAY:
+ case LOCAL_GL_TEXTURE_3D:
+ break;
+ default:
+ return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
+ "`tex` must have been bound to target "
+ "TEXTURE_2D_ARRAY or TEXTURE_3D."});
+ }
+ }
+ MOZ_ASSERT(texTarget);
+ uint32_t maxSize;
+ uint32_t maxZ;
+ switch (texTarget) {
+ case LOCAL_GL_TEXTURE_2D:
+ maxSize = limits.maxTex2dSize;
+ maxZ = 1;
+ break;
+ case LOCAL_GL_TEXTURE_CUBE_MAP:
+ maxSize = limits.maxTexCubeSize;
+ maxZ = 6;
+ break;
+ case LOCAL_GL_TEXTURE_2D_ARRAY:
+ maxSize = limits.maxTex2dSize;
+ maxZ = limits.maxTexArrayLayers;
+ break;
+ case LOCAL_GL_TEXTURE_3D:
+ maxSize = limits.maxTex3dSize;
+ maxZ = limits.maxTex3dSize;
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ const auto maxMipLevel = FloorLog2(maxSize);
+ if (mipLevel > maxMipLevel) {
+ return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE,
+ "`mipLevel` too large for texture target."});
+ }
+ const auto requiredZLayers = CheckedInt<uint32_t>(zLayerBase) + zLayerCount;
+ if (!requiredZLayers.isValid() || requiredZLayers.value() > maxZ) {
+ return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE,
+ "`zLayer` too large for texture target."});
+ }
+
+ return {};
+}
+
+void ClientWebGLContext::FramebufferAttach(
+ const GLenum target, const GLenum attachSlot, const GLenum bindImageTarget,
+ WebGLRenderbufferJS* const rb, WebGLTextureJS* const tex,
+ const uint32_t mipLevel, const uint32_t zLayerBase,
+ const uint32_t numViewLayers) const {
+ if (rb && !rb->ValidateUsable(*this, "rb")) return;
+ if (tex && !tex->ValidateUsable(*this, "tex")) return;
+ const auto& state = State();
+ const auto& limits = Limits();
+
+ if (!IsFramebufferTarget(mIsWebGL2, target)) {
+ EnqueueError_ArgEnum("target", target);
+ return;
+ }
+ auto fb = state.mBoundDrawFb;
+ if (target == LOCAL_GL_READ_FRAMEBUFFER) {
+ fb = state.mBoundReadFb;
+ }
+ if (!fb) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION, "No framebuffer bound.");
+ return;
+ }
+
+ if (fb->mOpaque) {
+ EnqueueError(
+ LOCAL_GL_INVALID_OPERATION,
+ "An opaque framebuffer's attachments cannot be inspected or changed.");
+ return;
+ }
+
+ // -
+ // Multiview-specific validation skipped by Host.
+
+ if (tex && numViewLayers) {
+ if (tex->mTarget != LOCAL_GL_TEXTURE_2D_ARRAY) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "`tex` must have been bound to target TEXTURE_2D_ARRAY.");
+ return;
+ }
+ if (numViewLayers > limits.maxMultiviewLayers) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "`numViews` (%u) must be <= MAX_VIEWS (%u).", numViewLayers,
+ limits.maxMultiviewLayers);
+ return;
+ }
+ }
+
+ // -
+
+ webgl::ObjectId id = 0;
+ if (tex) {
+ auto zLayerCount = numViewLayers;
+ if (!zLayerCount) {
+ zLayerCount = 1;
+ }
+ const auto err =
+ CheckFramebufferAttach(bindImageTarget, tex->mTarget, mipLevel,
+ zLayerBase, zLayerCount, limits);
+ if (err) {
+ EnqueueError(err->type, "%s", err->info.c_str());
+ return;
+ }
+ id = tex->mId;
+ } else if (rb) {
+ if (!rb->mHasBeenBound) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "`rb` has not yet been bound with BindRenderbuffer.");
+ return;
+ }
+ id = rb->mId;
+ }
+
+ // Ready!
+ // But DEPTH_STENCIL in webgl2 is actually two slots!
+
+ const auto fnAttachTo = [&](const GLenum actualAttachSlot) {
+ const auto slot = fb->GetAttachment(actualAttachSlot);
+ if (!slot) {
+ EnqueueError_ArgEnum("attachment", actualAttachSlot);
+ return;
+ }
+
+ slot->rb = rb;
+ slot->tex = tex;
+
+ Run<RPROC(FramebufferAttach)>(target, actualAttachSlot, bindImageTarget, id,
+ mipLevel, zLayerBase, numViewLayers);
+ };
+
+ if (mIsWebGL2 && attachSlot == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+ fnAttachTo(LOCAL_GL_DEPTH_ATTACHMENT);
+ fnAttachTo(LOCAL_GL_STENCIL_ATTACHMENT);
+ } else {
+ fnAttachTo(attachSlot);
+ }
+
+ if (bindImageTarget) {
+ if (rb) {
+ rb->mHasBeenBound = true;
+ }
+ if (tex) {
+ tex->mTarget = ImageToTexTarget(bindImageTarget);
+ }
+ }
+}
+
+// -
+
+void ClientWebGLContext::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1,
+ GLint srcY1, GLint dstX0, GLint dstY0,
+ GLint dstX1, GLint dstY1,
+ GLbitfield mask, GLenum filter) {
+ Run<RPROC(BlitFramebuffer)>(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1,
+ dstY1, mask, filter);
+
+ AfterDrawCall();
+}
+
+void ClientWebGLContext::InvalidateFramebuffer(
+ GLenum target, const dom::Sequence<GLenum>& attachments,
+ ErrorResult& unused) {
+ const auto range = MakeRange(attachments);
+ const auto& buffer = RawBufferView(range);
+ Run<RPROC(InvalidateFramebuffer)>(target, buffer);
+
+ // Never invalidate the backbuffer, so never needs AfterDrawCall.
+}
+
+void ClientWebGLContext::InvalidateSubFramebuffer(
+ GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y,
+ GLsizei width, GLsizei height, ErrorResult& unused) {
+ const auto range = MakeRange(attachments);
+ const auto& buffer = RawBufferView(range);
+ Run<RPROC(InvalidateSubFramebuffer)>(target, buffer, x, y, width, height);
+
+ // Never invalidate the backbuffer, so never needs AfterDrawCall.
+}
+
+void ClientWebGLContext::ReadBuffer(GLenum mode) {
+ Run<RPROC(ReadBuffer)>(mode);
+}
+
+// ----------------------- Renderbuffer objects -----------------------
+
+void ClientWebGLContext::BindRenderbuffer(const GLenum target,
+ WebGLRenderbufferJS* const rb) {
+ const FuncScope funcScope(*this, "bindRenderbuffer");
+ if (IsContextLost()) return;
+ if (rb && !rb->ValidateUsable(*this, "rb")) return;
+ auto& state = State();
+
+ if (target != LOCAL_GL_RENDERBUFFER) {
+ EnqueueError_ArgEnum("target", target);
+ return;
+ }
+
+ state.mBoundRb = rb;
+ if (rb) {
+ rb->mHasBeenBound = true;
+ }
+}
+
+void ClientWebGLContext::RenderbufferStorageMultisample(GLenum target,
+ GLsizei samples,
+ GLenum internalFormat,
+ GLsizei width,
+ GLsizei height) const {
+ const FuncScope funcScope(*this, "renderbufferStorageMultisample");
+ if (IsContextLost()) return;
+
+ if (target != LOCAL_GL_RENDERBUFFER) {
+ EnqueueError_ArgEnum("target", target);
+ return;
+ }
+
+ const auto& state = State();
+
+ const auto& rb = state.mBoundRb;
+ if (!rb) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION, "No renderbuffer bound");
+ return;
+ }
+
+ if (!ValidateNonNegative("width", width) ||
+ !ValidateNonNegative("height", height) ||
+ !ValidateNonNegative("samples", samples)) {
+ return;
+ }
+
+ if (internalFormat == LOCAL_GL_DEPTH_STENCIL && samples > 0) {
+ // While our backend supports it trivially, the spec forbids it.
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "WebGL 1's DEPTH_STENCIL format may not be multisampled. Use "
+ "DEPTH24_STENCIL8 when `samples > 0`.");
+ return;
+ }
+
+ Run<RPROC(RenderbufferStorageMultisample)>(
+ rb->mId, static_cast<uint32_t>(samples), internalFormat,
+ static_cast<uint32_t>(width), static_cast<uint32_t>(height));
+}
+
+// --------------------------- Texture objects ---------------------------
+
+void ClientWebGLContext::ActiveTexture(const GLenum texUnitEnum) {
+ const FuncScope funcScope(*this, "activeTexture");
+ if (IsContextLost()) return;
+
+ if (texUnitEnum < LOCAL_GL_TEXTURE0) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "`texture` (0x%04x) must be >= TEXTURE0 (0x%04x).",
+ texUnitEnum, LOCAL_GL_TEXTURE0);
+ return;
+ }
+
+ const auto texUnit = texUnitEnum - LOCAL_GL_TEXTURE0;
+
+ auto& state = State();
+ if (texUnit >= state.mTexUnits.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "TEXTURE%u must be < MAX_COMBINED_TEXTURE_IMAGE_UNITS (%zu).",
+ texUnit, state.mTexUnits.size());
+ return;
+ }
+
+ //-
+
+ state.mActiveTexUnit = texUnit;
+ Run<RPROC(ActiveTexture)>(texUnit);
+}
+
+static bool IsTexTarget(const GLenum texTarget, const bool webgl2) {
+ switch (texTarget) {
+ case LOCAL_GL_TEXTURE_2D:
+ case LOCAL_GL_TEXTURE_CUBE_MAP:
+ return true;
+
+ case LOCAL_GL_TEXTURE_2D_ARRAY:
+ case LOCAL_GL_TEXTURE_3D:
+ return webgl2;
+
+ default:
+ return false;
+ }
+}
+
+void ClientWebGLContext::BindTexture(const GLenum texTarget,
+ WebGLTextureJS* const tex) {
+ const FuncScope funcScope(*this, "bindTexture");
+ if (IsContextLost()) return;
+ if (tex && !tex->ValidateUsable(*this, "tex")) return;
+
+ if (!IsTexTarget(texTarget, mIsWebGL2)) {
+ EnqueueError_ArgEnum("texTarget", texTarget);
+ return;
+ }
+
+ if (tex && tex->mTarget) {
+ if (texTarget != tex->mTarget) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Texture previously bound to %s cannot be bound now to %s.",
+ EnumString(tex->mTarget).c_str(),
+ EnumString(texTarget).c_str());
+ return;
+ }
+ }
+
+ auto& state = State();
+ auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
+ texUnit.texByTarget[texTarget] = tex;
+ if (tex) {
+ tex->mTarget = texTarget;
+ }
+
+ Run<RPROC(BindTexture)>(texTarget, tex ? tex->mId : 0);
+}
+
+void ClientWebGLContext::GenerateMipmap(GLenum texTarget) const {
+ Run<RPROC(GenerateMipmap)>(texTarget);
+}
+
+void ClientWebGLContext::GetTexParameter(
+ JSContext* cx, GLenum texTarget, GLenum pname,
+ JS::MutableHandle<JS::Value> retval) const {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getTexParameter");
+ if (IsContextLost()) return;
+ auto& state = State();
+
+ auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
+
+ const auto& tex = Find(texUnit.texByTarget, texTarget, nullptr);
+ if (!tex) {
+ if (!IsTexTarget(texTarget, mIsWebGL2)) {
+ EnqueueError_ArgEnum("texTarget", texTarget);
+ } else {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION, "No texture bound to %s[%u].",
+ EnumString(texTarget).c_str(), state.mActiveTexUnit);
+ }
+ return;
+ }
+
+ const auto maybe = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetTexParameter(tex->mId, pname);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ Maybe<double> ret;
+ if (!child->SendGetTexParameter(tex->mId, pname, &ret)) {
+ ret.reset();
+ }
+ return ret;
+ }();
+
+ if (maybe) {
+ switch (pname) {
+ case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
+ retval.set(JS::BooleanValue(*maybe));
+ break;
+
+ default:
+ retval.set(JS::NumberValue(*maybe));
+ break;
+ }
+ }
+}
+
+void ClientWebGLContext::TexParameterf(GLenum texTarget, GLenum pname,
+ GLfloat param) {
+ Run<RPROC(TexParameter_base)>(texTarget, pname, FloatOrInt(param));
+}
+
+void ClientWebGLContext::TexParameteri(GLenum texTarget, GLenum pname,
+ GLint param) {
+ Run<RPROC(TexParameter_base)>(texTarget, pname, FloatOrInt(param));
+}
+
+////////////////////////////////////
+
+static GLenum JSTypeMatchUnpackTypeError(GLenum unpackType,
+ js::Scalar::Type jsType) {
+ bool matches = false;
+ switch (unpackType) {
+ case LOCAL_GL_BYTE:
+ matches = (jsType == js::Scalar::Type::Int8);
+ break;
+
+ case LOCAL_GL_UNSIGNED_BYTE:
+ matches = (jsType == js::Scalar::Type::Uint8 ||
+ jsType == js::Scalar::Type::Uint8Clamped);
+ break;
+
+ case LOCAL_GL_SHORT:
+ matches = (jsType == js::Scalar::Type::Int16);
+ break;
+
+ 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:
+ case LOCAL_GL_HALF_FLOAT:
+ case LOCAL_GL_HALF_FLOAT_OES:
+ matches = (jsType == js::Scalar::Type::Uint16);
+ break;
+
+ case LOCAL_GL_INT:
+ matches = (jsType == js::Scalar::Type::Int32);
+ break;
+
+ case LOCAL_GL_UNSIGNED_INT:
+ case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
+ case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
+ case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
+ case LOCAL_GL_UNSIGNED_INT_24_8:
+ matches = (jsType == js::Scalar::Type::Uint32);
+ break;
+
+ case LOCAL_GL_FLOAT:
+ matches = (jsType == js::Scalar::Type::Float32);
+ break;
+
+ case LOCAL_GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
+ matches = false; // No valid jsType, but we allow uploads with null.
+ break;
+
+ default:
+ return LOCAL_GL_INVALID_ENUM;
+ }
+ if (!matches) return LOCAL_GL_INVALID_OPERATION;
+ return 0;
+}
+
+/////////////////////////////////////////////////
+
+static inline uvec2 CastUvec2(const ivec2& val) {
+ return {static_cast<uint32_t>(val.x), static_cast<uint32_t>(val.y)};
+}
+
+static inline uvec3 CastUvec3(const ivec3& val) {
+ return {static_cast<uint32_t>(val.x), static_cast<uint32_t>(val.y),
+ static_cast<uint32_t>(val.z)};
+}
+
+template <typename T>
+Range<T> SubRange(const Range<T>& full, const size_t offset,
+ const size_t length) {
+ const auto newBegin = full.begin() + offset;
+ return Range<T>{newBegin, newBegin + length};
+}
+
+static inline size_t SizeOfViewElem(const dom::ArrayBufferView& view) {
+ const auto& elemType = view.Type();
+ if (elemType == js::Scalar::MaxTypedArrayViewType) // DataViews.
+ return 1;
+
+ return js::Scalar::byteSize(elemType);
+}
+
+Maybe<Range<const uint8_t>> GetRangeFromView(const dom::ArrayBufferView& view,
+ GLuint elemOffset,
+ GLuint elemCountOverride) {
+ const auto byteRange = MakeRangeAbv(view); // In bytes.
+ const auto bytesPerElem = SizeOfViewElem(view);
+
+ auto elemCount = byteRange.length() / bytesPerElem;
+ if (elemOffset > elemCount) return {};
+ elemCount -= elemOffset;
+
+ if (elemCountOverride) {
+ if (elemCountOverride > elemCount) return {};
+ elemCount = elemCountOverride;
+ }
+ const auto subrange =
+ SubRange(byteRange, elemOffset * bytesPerElem, elemCount * bytesPerElem);
+ return Some(subrange);
+}
+
+// -
+
+static bool IsTexTargetForDims(const GLenum texTarget, const bool webgl2,
+ const uint8_t funcDims) {
+ if (!IsTexTarget(texTarget, webgl2)) return false;
+ switch (texTarget) {
+ case LOCAL_GL_TEXTURE_2D:
+ case LOCAL_GL_TEXTURE_CUBE_MAP:
+ return funcDims == 2;
+
+ default:
+ return funcDims == 3;
+ }
+}
+
+void ClientWebGLContext::TexStorage(uint8_t funcDims, GLenum texTarget,
+ GLsizei levels, GLenum internalFormat,
+ const ivec3& size) const {
+ const FuncScope funcScope(*this, "texStorage[23]D");
+ if (IsContextLost()) return;
+ if (!IsTexTargetForDims(texTarget, mIsWebGL2, funcDims)) {
+ EnqueueError_ArgEnum("texTarget", texTarget);
+ return;
+ }
+ Run<RPROC(TexStorage)>(texTarget, static_cast<uint32_t>(levels),
+ internalFormat, CastUvec3(size));
+}
+
+namespace webgl {
+// TODO: Move these definitions into statics here.
+Maybe<webgl::TexUnpackBlobDesc> FromImageBitmap(
+ GLenum target, Maybe<uvec3> size, const dom::ImageBitmap& imageBitmap,
+ ErrorResult* const out_rv);
+
+Maybe<webgl::TexUnpackBlobDesc> FromOffscreenCanvas(
+ const ClientWebGLContext&, GLenum target, Maybe<uvec3> size,
+ const dom::OffscreenCanvas& src, ErrorResult* const out_error);
+
+Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext&,
+ GLenum target, Maybe<uvec3> size,
+ const dom::Element& src,
+ ErrorResult* const out_error);
+} // namespace webgl
+
+// -
+
+void webgl::TexUnpackBlobDesc::Shrink(const webgl::PackingInfo& pi) {
+ if (cpuData) {
+ if (!size.x || !size.y || !size.z) return;
+
+ const auto unpackRes = ExplicitUnpacking(pi, {});
+ if (!unpackRes.isOk()) {
+ return;
+ }
+ const auto& unpack = unpackRes.inspect();
+
+ const auto bytesUpperBound =
+ CheckedInt<size_t>(unpack.metrics.bytesPerRowStride) *
+ unpack.metrics.totalRows;
+ if (bytesUpperBound.isValid()) {
+ cpuData->Shrink(bytesUpperBound.value());
+ }
+ }
+}
+
+// -
+
+void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget,
+ GLint level, GLenum respecFormat,
+ const ivec3& offset,
+ const Maybe<ivec3>& isize, GLint border,
+ const webgl::PackingInfo& pi,
+ const TexImageSource& src) const {
+ const FuncScope funcScope(*this, "tex(Sub)Image[23]D");
+ if (IsContextLost()) return;
+ if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
+ EnqueueError_ArgEnum("imageTarget", imageTarget);
+ return;
+ }
+ if (border != 0) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
+ return;
+ }
+
+ Maybe<uvec3> size;
+ if (isize) {
+ size = Some(CastUvec3(isize.value()));
+ }
+
+ // -
+
+ // Demarcate the region within which GC is disallowed. Typed arrays can move
+ // their data during a GC, so this will allow the rooting hazard analysis to
+ // report if a GC is possible while any data pointers extracted from the
+ // typed array are still live.
+ dom::Uint8ClampedArray scopedArr;
+ const auto reset = MakeScopeExit([&] {
+ scopedArr.Reset(); // (For the hazard analysis) Done with the data.
+ });
+
+ // -
+ bool isDataUpload = false;
+ auto desc = [&]() -> Maybe<webgl::TexUnpackBlobDesc> {
+ if (src.mPboOffset) {
+ isDataUpload = true;
+ const auto offset = static_cast<uint64_t>(*src.mPboOffset);
+ return Some(webgl::TexUnpackBlobDesc{imageTarget,
+ size.value(),
+ gfxAlphaType::NonPremult,
+ {},
+ Some(offset)});
+ }
+
+ if (src.mView) {
+ isDataUpload = true;
+ const auto& view = *src.mView;
+ const auto& jsType = view.Type();
+ const auto err = JSTypeMatchUnpackTypeError(pi.type, jsType);
+ switch (err) {
+ case LOCAL_GL_INVALID_ENUM:
+ EnqueueError_ArgEnum("unpackType", pi.type);
+ return {};
+ case LOCAL_GL_INVALID_OPERATION:
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "ArrayBufferView type %s not compatible with `type` %s.",
+ name(jsType), EnumString(pi.type).c_str());
+ return {};
+ default:
+ break;
+ }
+
+ const auto range = GetRangeFromView(view, src.mViewElemOffset,
+ src.mViewElemLengthOverride);
+ if (!range) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION, "`source` too small.");
+ return {};
+ }
+ return Some(webgl::TexUnpackBlobDesc{imageTarget,
+ size.value(),
+ gfxAlphaType::NonPremult,
+ Some(RawBuffer<>{*range}),
+ {}});
+ }
+
+ if (src.mImageBitmap) {
+ return webgl::FromImageBitmap(imageTarget, size, *(src.mImageBitmap),
+ src.mOut_error);
+ }
+
+ if (src.mImageData) {
+ const auto& imageData = *src.mImageData;
+ MOZ_RELEASE_ASSERT(scopedArr.Init(imageData.GetDataObject()));
+ scopedArr.ComputeState();
+ const auto dataSize = scopedArr.Length();
+ const auto data = reinterpret_cast<uint8_t*>(scopedArr.Data());
+ if (!data) {
+ // Neutered, e.g. via Transfer
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "ImageData.data.buffer is Detached. (Maybe you Transfered "
+ "it to a Worker?");
+ return {};
+ }
+
+ // -
+
+ const gfx::IntSize imageSize(imageData.Width(), imageData.Height());
+ const auto sizeFromDims =
+ CheckedInt<size_t>(imageSize.width) * imageSize.height * 4;
+ MOZ_RELEASE_ASSERT(sizeFromDims.isValid() &&
+ sizeFromDims.value() == dataSize);
+
+ const RefPtr<gfx::DataSourceSurface> surf =
+ gfx::Factory::CreateWrappingDataSourceSurface(
+ data, imageSize.width * 4, imageSize,
+ gfx::SurfaceFormat::R8G8B8A8);
+ MOZ_ASSERT(surf);
+
+ // -
+
+ const auto imageUSize = *uvec2::FromSize(imageSize);
+ const auto concreteSize =
+ size.valueOr(uvec3{imageUSize.x, imageUSize.y, 1});
+
+ // WhatWG "HTML Living Standard" (30 October 2015):
+ // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned
+ // as non-premultiplied alpha values."
+ return Some(webgl::TexUnpackBlobDesc{imageTarget,
+ concreteSize,
+ gfxAlphaType::NonPremult,
+ {},
+ {},
+ Some(imageUSize),
+ nullptr,
+ {},
+ surf});
+ }
+
+ if (src.mOffscreenCanvas) {
+ return webgl::FromOffscreenCanvas(
+ *this, imageTarget, size, *(src.mOffscreenCanvas), src.mOut_error);
+ }
+
+ if (src.mDomElem) {
+ return webgl::FromDomElem(*this, imageTarget, size, *(src.mDomElem),
+ src.mOut_error);
+ }
+
+ return Some(webgl::TexUnpackBlobDesc{
+ imageTarget, size.value(), gfxAlphaType::NonPremult, {}, {}});
+ }();
+ if (!desc) {
+ return;
+ }
+
+ // -
+
+ const auto& rawUnpacking = State().mPixelUnpackState;
+ {
+ auto defaultSubrectState = webgl::PixelPackingState{};
+ defaultSubrectState.alignmentInTypeElems =
+ rawUnpacking.alignmentInTypeElems;
+ const bool isSubrect = (rawUnpacking != defaultSubrectState);
+ if (isDataUpload && isSubrect) {
+ if (rawUnpacking.flipY || rawUnpacking.premultiplyAlpha) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Non-DOM-Element uploads with alpha-premult"
+ " or y-flip do not support subrect selection.");
+ return;
+ }
+ }
+ }
+ desc->unpacking = rawUnpacking;
+
+ if (desc->structuredSrcSize) {
+ // WebGL 2 spec:
+ // ### 5.35 Pixel store parameters for uploads from TexImageSource
+ // UNPACK_ALIGNMENT and UNPACK_ROW_LENGTH are ignored.
+ const auto& elemSize = *desc->structuredSrcSize;
+ desc->unpacking.alignmentInTypeElems = 1;
+ desc->unpacking.rowLength = elemSize.x;
+ }
+ if (!desc->unpacking.rowLength) {
+ desc->unpacking.rowLength = desc->size.x;
+ }
+ if (!desc->unpacking.imageHeight) {
+ desc->unpacking.imageHeight = desc->size.y;
+ }
+
+ // -
+
+ mozilla::ipc::Shmem* pShmem = nullptr;
+ // Image to release after WebGLContext::TexImage().
+ RefPtr<layers::Image> keepAliveImage;
+
+ if (desc->sd) {
+ const auto& sd = *(desc->sd);
+ const auto sdType = sd.type();
+ const auto& contextInfo = mNotLost->info;
+
+ const auto fallbackReason = [&]() -> Maybe<std::string> {
+ auto fallbackReason = BlitPreventReason(level, offset, pi, *desc);
+ if (fallbackReason) return fallbackReason;
+
+ const bool canUploadViaSd = contextInfo.uploadableSdTypes[sdType];
+ if (!canUploadViaSd) {
+ const nsPrintfCString msg(
+ "Fast uploads for resource type %i not implemented.", int(sdType));
+ return Some(ToString(msg));
+ }
+
+ if (sdType == layers::SurfaceDescriptor::TSurfaceDescriptorBuffer) {
+ const auto& sdb = sd.get_SurfaceDescriptorBuffer();
+ const auto& data = sdb.data();
+ if (data.type() == layers::MemoryOrShmem::TShmem) {
+ pShmem = &data.get_Shmem();
+ } else {
+ return Some(
+ std::string{"SurfaceDescriptorBuffer data is not Shmem."});
+ }
+ }
+
+ if (sdType == layers::SurfaceDescriptor::TSurfaceDescriptorD3D10) {
+ const auto& sdD3D = sd.get_SurfaceDescriptorD3D10();
+ const auto& inProcess = mNotLost->inProcess;
+ MOZ_ASSERT(desc->image);
+ keepAliveImage = desc->image;
+
+ if (sdD3D.gpuProcessTextureId().isSome() && inProcess) {
+ return Some(
+ std::string{"gpuProcessTextureId works only in GPU process."});
+ }
+ }
+
+ switch (respecFormat) {
+ case LOCAL_GL_SRGB:
+ case LOCAL_GL_SRGB8:
+ case LOCAL_GL_SRGB_ALPHA:
+ case LOCAL_GL_SRGB8_ALPHA8: {
+ const nsPrintfCString msg(
+ "srgb-encoded formats (like %s) are not supported.",
+ EnumString(respecFormat).c_str());
+ return Some(ToString(msg));
+ }
+ }
+
+ if (StaticPrefs::webgl_disable_DOM_blit_uploads()) {
+ return Some(std::string{"DOM blit uploads are disabled."});
+ }
+ return {};
+ }();
+
+ if (fallbackReason) {
+ EnqueuePerfWarning("Missed GPU-copy fast-path: %s",
+ fallbackReason->c_str());
+
+ const auto& image = desc->image;
+ const RefPtr<gfx::SourceSurface> surf = image->GetAsSourceSurface();
+ if (surf) {
+ // WARNING: OSX can lose our MakeCurrent here.
+ desc->dataSurf = surf->GetDataSurface();
+ }
+ if (!desc->dataSurf) {
+ EnqueueError(LOCAL_GL_OUT_OF_MEMORY,
+ "Failed to retrieve source bytes for CPU upload.");
+ return;
+ }
+ desc->sd = Nothing();
+ }
+ }
+ desc->image = nullptr;
+
+ desc->Shrink(pi);
+
+ // -
+
+ std::shared_ptr<webgl::RaiiShmem> tempShmem;
+
+ const bool doInlineUpload = !desc->sd;
+ // Why always de-inline SDs here?
+ // 1. This way we always send SDs down the same handling path, which
+ // should keep things from breaking if things flip between paths because of
+ // what we get handed by SurfaceFromElement etc.
+ // 2. We don't actually always grab strong-refs to the resources in the SDs,
+ // so we should try to use them sooner rather than later. Yes we should fix
+ // this, but for now let's give the SDs the best chance of lucking out, eh?
+ // :)
+ // 3. It means we don't need to write QueueParamTraits<SurfaceDescriptor>.
+ if (doInlineUpload) {
+ // We definitely want e.g. TexImage(PBO) here.
+ Run<RPROC(TexImage)>(static_cast<uint32_t>(level), respecFormat,
+ CastUvec3(offset), pi, std::move(*desc));
+ } else {
+ // We can't handle shmems like SurfaceDescriptorBuffer inline, so use ipdl.
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->TexImage(static_cast<uint32_t>(level), respecFormat,
+ CastUvec3(offset), pi, *desc);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+
+ // The shmem we're handling was only shared from RDD to Content, and
+ // immediately on Content receiving it, it was closed! RIP
+ // Eventually we'll be able to make shmems that can traverse multiple
+ // endpoints, but for now we need to make a new Content->WebGLParent shmem
+ // and memcpy into it. We don't use `desc` elsewhere, so just replace the
+ // Shmem buried within it with one that's valid for WebGLChild->Parent
+ // transport.
+ if (pShmem) {
+ MOZ_ASSERT(desc->sd);
+ const auto srcBytes = ShmemRange<uint8_t>(*pShmem);
+ tempShmem = std::make_shared<webgl::RaiiShmem>();
+
+ // We need Unsafe because we want to dictate when to destroy it from the
+ // client side.
+ *tempShmem = webgl::RaiiShmem::AllocUnsafe(child, srcBytes.length());
+ if (!*tempShmem) {
+ NS_WARNING("AllocShmem failed in TexImage");
+ return;
+ }
+ const auto dstBytes = ShmemRange<uint8_t>(tempShmem->Shmem());
+ Memcpy(&dstBytes, srcBytes.begin());
+
+ *pShmem = tempShmem->Shmem();
+ // Not Extract, because we free tempShmem manually below, after the remote
+ // side has finished executing SendTexImage.
+ }
+
+ (void)child->SendTexImage(static_cast<uint32_t>(level), respecFormat,
+ CastUvec3(offset), pi, std::move(*desc));
+
+ if (tempShmem || keepAliveImage) {
+ const auto eventTarget = GetCurrentSerialEventTarget();
+ MOZ_ASSERT(eventTarget);
+ child->SendPing()->Then(eventTarget, __func__,
+ [tempShmem, keepAliveImage]() {
+ // Cleans up when (our copy of)
+ // sendableShmem/image goes out of scope.
+ });
+ }
+ }
+}
+
+void ClientWebGLContext::RawTexImage(uint32_t level, GLenum respecFormat,
+ uvec3 offset, const webgl::PackingInfo& pi,
+ webgl::TexUnpackBlobDesc&& desc) const {
+ const FuncScope funcScope(*this, "tex(Sub)Image[23]D");
+ if (IsContextLost()) return;
+ if (desc.sd) {
+ // Shmems are stored in Buffer surface descriptors. We need to ensure first
+ // that all queued commands are flushed and then send the Shmem over IPDL.
+ const auto& sd = *(desc.sd);
+ if (sd.type() == layers::SurfaceDescriptor::TSurfaceDescriptorBuffer &&
+ sd.get_SurfaceDescriptorBuffer().data().type() ==
+ layers::MemoryOrShmem::TShmem) {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ inProcess->TexImage(level, respecFormat, offset, pi, desc);
+ } else {
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ (void)child->SendTexImage(level, respecFormat, offset, pi,
+ std::move(desc));
+ }
+ } else {
+ NS_WARNING(
+ "RawTexImage with SurfaceDescriptor only supports "
+ "SurfaceDescriptorBuffer with Shmem");
+ }
+ return;
+ }
+
+ Run<RPROC(TexImage)>(level, respecFormat, offset, pi, desc);
+}
+
+// -
+
+void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims,
+ GLenum imageTarget, GLint level,
+ GLenum format, const ivec3& offset,
+ const ivec3& isize, GLint border,
+ const TexImageSource& src,
+ GLsizei pboImageSize) const {
+ const FuncScope funcScope(*this, "compressedTex(Sub)Image[23]D");
+ if (IsContextLost()) return;
+ if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
+ EnqueueError_ArgEnum("imageTarget", imageTarget);
+ return;
+ }
+ if (border != 0) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
+ return;
+ }
+
+ RawBuffer<> range;
+ Maybe<uint64_t> pboOffset;
+ if (src.mView) {
+ const auto maybe = GetRangeFromView(*src.mView, src.mViewElemOffset,
+ src.mViewElemLengthOverride);
+ if (!maybe) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`source` too small.");
+ return;
+ }
+ range = RawBuffer<>{*maybe};
+ } else if (src.mPboOffset) {
+ if (!ValidateNonNegative("offset", *src.mPboOffset)) return;
+ pboOffset = Some(*src.mPboOffset);
+ } else {
+ MOZ_CRASH("impossible");
+ }
+
+ // We don't need to shrink `range` because valid calls require `range` to
+ // match requirements exactly.
+
+ Run<RPROC(CompressedTexImage)>(
+ sub, imageTarget, static_cast<uint32_t>(level), format, CastUvec3(offset),
+ CastUvec3(isize), range, static_cast<uint32_t>(pboImageSize), pboOffset);
+}
+
+void ClientWebGLContext::CopyTexImage(uint8_t funcDims, GLenum imageTarget,
+ GLint level, GLenum respecFormat,
+ const ivec3& dstOffset,
+ const ivec2& srcOffset, const ivec2& size,
+ GLint border) const {
+ const FuncScope funcScope(*this, "copy(Sub)Image[23]D");
+ if (IsContextLost()) return;
+ if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
+ EnqueueError_ArgEnum("imageTarget", imageTarget);
+ return;
+ }
+ if (border != 0) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
+ return;
+ }
+ Run<RPROC(CopyTexImage)>(imageTarget, static_cast<uint32_t>(level),
+ respecFormat, CastUvec3(dstOffset), srcOffset,
+ CastUvec2(size));
+}
+
+// ------------------- Programs and shaders --------------------------------
+
+void ClientWebGLContext::UseProgram(WebGLProgramJS* const prog) {
+ const FuncScope funcScope(*this, "useProgram");
+ if (IsContextLost()) return;
+ if (prog && !prog->ValidateUsable(*this, "prog")) return;
+
+ auto& state = State();
+
+ if (state.mTfActiveAndNotPaused) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Transform feedback is active and not paused.");
+ return;
+ }
+
+ if (prog) {
+ const auto& res = GetLinkResult(*prog);
+ if (!res.success) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Program must be linked successfully.");
+ return;
+ }
+ }
+
+ // -
+
+ state.mCurrentProgram = prog;
+ state.mProgramKeepAlive = prog ? prog->mKeepAliveWeak.lock() : nullptr;
+ state.mActiveLinkResult = prog ? prog->mResult : nullptr;
+
+ Run<RPROC(UseProgram)>(prog ? prog->mId : 0);
+}
+
+void ClientWebGLContext::ValidateProgram(WebGLProgramJS& prog) const {
+ const FuncScope funcScope(*this, "validateProgram");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "prog")) return;
+
+ prog.mLastValidate = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->ValidateProgram(prog.mId);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ bool ret = {};
+ if (!child->SendValidateProgram(prog.mId, &ret)) {
+ ret = {};
+ }
+ return ret;
+ }();
+}
+
+// ------------------------ Uniforms and attributes ------------------------
+
+Maybe<double> ClientWebGLContext::GetVertexAttribPriv(const GLuint index,
+ const GLenum pname) {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetVertexAttrib(index, pname);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ Maybe<double> ret;
+ if (!child->SendGetVertexAttrib(index, pname, &ret)) {
+ ret.reset();
+ }
+ return ret;
+}
+
+void ClientWebGLContext::GetVertexAttrib(JSContext* cx, GLuint index,
+ GLenum pname,
+ JS::MutableHandle<JS::Value> retval,
+ ErrorResult& rv) {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getVertexAttrib");
+ if (IsContextLost()) return;
+ const auto& state = State();
+
+ const auto& genericAttribs = state.mGenericVertexAttribs;
+ if (index >= genericAttribs.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` (%u) >= MAX_VERTEX_ATTRIBS",
+ index);
+ return;
+ }
+
+ switch (pname) {
+ case LOCAL_GL_CURRENT_VERTEX_ATTRIB: {
+ JS::Rooted<JSObject*> obj(cx);
+
+ const auto& attrib = genericAttribs[index];
+ switch (attrib.type) {
+ case webgl::AttribBaseType::Float:
+ obj = dom::Float32Array::Create(
+ cx, this, 4, reinterpret_cast<const float*>(attrib.data.data()));
+ break;
+ case webgl::AttribBaseType::Int:
+ obj = dom::Int32Array::Create(
+ cx, this, 4,
+ reinterpret_cast<const int32_t*>(attrib.data.data()));
+ break;
+ case webgl::AttribBaseType::Uint:
+ obj = dom::Uint32Array::Create(
+ cx, this, 4,
+ reinterpret_cast<const uint32_t*>(attrib.data.data()));
+ break;
+ case webgl::AttribBaseType::Boolean:
+ MOZ_CRASH("impossible");
+ }
+
+ if (!obj) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ retval.set(JS::ObjectValue(*obj));
+ return;
+ }
+
+ case LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: {
+ const auto& buffers = state.mBoundVao->mAttribBuffers;
+ const auto& buffer = buffers[index];
+ (void)ToJSValueOrNull(cx, buffer, retval);
+ return;
+ }
+
+ case LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER:
+ // Disallowed from JS, but allowed in Host.
+ EnqueueError_ArgEnum("pname", pname);
+ return;
+
+ default:
+ break;
+ }
+
+ const auto maybe = GetVertexAttribPriv(index, pname);
+ if (maybe) {
+ switch (pname) {
+ case LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED:
+ case LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
+ case LOCAL_GL_VERTEX_ATTRIB_ARRAY_INTEGER:
+ retval.set(JS::BooleanValue(*maybe));
+ break;
+
+ default:
+ retval.set(JS::NumberValue(*maybe));
+ break;
+ }
+ }
+}
+
+void ClientWebGLContext::UniformData(const GLenum funcElemType,
+ const WebGLUniformLocationJS* const loc,
+ bool transpose,
+ const Range<const uint8_t>& bytes,
+ GLuint elemOffset,
+ GLuint elemCountOverride) const {
+ const FuncScope funcScope(*this, "uniform setter");
+ if (IsContextLost()) return;
+
+ const auto& activeLinkResult = GetActiveLinkResult();
+ if (!activeLinkResult) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION, "No active linked Program.");
+ return;
+ }
+
+ // -
+
+ auto availCount = bytes.length() / sizeof(float);
+ if (elemOffset > availCount) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`elemOffset` too large for `data`.");
+ return;
+ }
+ availCount -= elemOffset;
+ if (elemCountOverride) {
+ if (elemCountOverride > availCount) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "`elemCountOverride` too large for `data`.");
+ return;
+ }
+ availCount = elemCountOverride;
+ }
+
+ // -
+
+ const auto channels = ElemTypeComponents(funcElemType);
+ if (!availCount || availCount % channels != 0) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "`values` length (%u) must be a positive "
+ "integer multiple of size of %s.",
+ availCount, EnumString(funcElemType).c_str());
+ return;
+ }
+
+ // -
+
+ uint32_t locId = -1;
+ if (MOZ_LIKELY(loc)) {
+ locId = loc->mLocation;
+ if (!loc->ValidateUsable(*this, "location")) return;
+
+ // -
+
+ const auto& reqLinkInfo = loc->mParent.lock();
+ if (reqLinkInfo.get() != activeLinkResult) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "UniformLocation is not from the current active Program.");
+ return;
+ }
+
+ // -
+
+ bool funcMatchesLocation = false;
+ for (const auto allowed : loc->mValidUploadElemTypes) {
+ funcMatchesLocation |= (funcElemType == allowed);
+ }
+ if (MOZ_UNLIKELY(!funcMatchesLocation)) {
+ std::string validSetters;
+ for (const auto allowed : loc->mValidUploadElemTypes) {
+ validSetters += EnumString(allowed);
+ validSetters += '/';
+ }
+ validSetters.pop_back(); // Cheekily discard the extra trailing '/'.
+
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Uniform's `type` requires uniform setter of type %s.",
+ validSetters.c_str());
+ return;
+ }
+ }
+
+ // -
+
+ const auto begin =
+ reinterpret_cast<const webgl::UniformDataVal*>(bytes.begin().get()) +
+ elemOffset;
+ const auto range = Range{begin, availCount};
+ Run<RPROC(UniformData)>(locId, transpose, RawBuffer{range});
+}
+
+// -
+
+void ClientWebGLContext::BindVertexArray(WebGLVertexArrayJS* const vao) {
+ const FuncScope funcScope(*this, "bindVertexArray");
+ if (IsContextLost()) return;
+ if (vao && !vao->ValidateUsable(*this, "vao")) return;
+ auto& state = State();
+
+ if (vao) {
+ vao->mHasBeenBound = true;
+ state.mBoundVao = vao;
+ } else {
+ state.mBoundVao = state.mDefaultVao;
+ }
+
+ Run<RPROC(BindVertexArray)>(vao ? vao->mId : 0);
+}
+
+void ClientWebGLContext::EnableVertexAttribArray(GLuint index) {
+ Run<RPROC(EnableVertexAttribArray)>(index);
+}
+
+void ClientWebGLContext::DisableVertexAttribArray(GLuint index) {
+ Run<RPROC(DisableVertexAttribArray)>(index);
+}
+
+WebGLsizeiptr ClientWebGLContext::GetVertexAttribOffset(GLuint index,
+ GLenum pname) {
+ const FuncScope funcScope(*this, "getVertexAttribOffset");
+ if (IsContextLost()) return 0;
+
+ if (pname != LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER) {
+ EnqueueError_ArgEnum("pname", pname);
+ return 0;
+ }
+
+ const auto maybe = GetVertexAttribPriv(index, pname);
+ if (!maybe) return 0;
+ return *maybe;
+}
+
+void ClientWebGLContext::VertexAttrib4Tv(GLuint index, webgl::AttribBaseType t,
+ const Range<const uint8_t>& src) {
+ const FuncScope funcScope(*this, "vertexAttrib[1234]u?[fi]{v}");
+ if (IsContextLost()) return;
+ auto& state = State();
+
+ if (src.length() / sizeof(float) < 4) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "Array must have >=4 elements.");
+ return;
+ }
+
+ auto& list = state.mGenericVertexAttribs;
+ if (index >= list.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "`index` must be < MAX_VERTEX_ATTRIBS.");
+ return;
+ }
+
+ auto& attrib = list[index];
+ attrib.type = t;
+ memcpy(attrib.data.data(), src.begin().get(), attrib.data.size());
+
+ Run<RPROC(VertexAttrib4T)>(index, attrib);
+}
+
+// -
+
+void ClientWebGLContext::VertexAttribDivisor(GLuint index, GLuint divisor) {
+ Run<RPROC(VertexAttribDivisor)>(index, divisor);
+}
+
+// -
+
+void ClientWebGLContext::VertexAttribPointerImpl(bool isFuncInt, GLuint index,
+ GLint rawChannels, GLenum type,
+ bool normalized,
+ GLsizei rawByteStrideOrZero,
+ WebGLintptr rawByteOffset) {
+ const FuncScope funcScope(*this, "vertexAttribI?Pointer");
+ if (IsContextLost()) return;
+ auto& state = State();
+
+ const auto channels = MaybeAs<uint8_t>(rawChannels);
+ if (!channels) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "Channel count `size` must be within [1,4].");
+ return;
+ }
+
+ const auto byteStrideOrZero = MaybeAs<uint8_t>(rawByteStrideOrZero);
+ if (!byteStrideOrZero) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`stride` must be within [0,255].");
+ return;
+ }
+
+ if (!ValidateNonNegative("byteOffset", rawByteOffset)) return;
+ const auto byteOffset = static_cast<uint64_t>(rawByteOffset);
+
+ // -
+
+ const webgl::VertAttribPointerDesc desc{
+ isFuncInt, *channels, normalized, *byteStrideOrZero, type, byteOffset};
+
+ const auto res = CheckVertexAttribPointer(mIsWebGL2, desc);
+ if (res.isErr()) {
+ const auto& err = res.inspectErr();
+ EnqueueError(err.type, "%s", err.info.c_str());
+ return;
+ }
+
+ auto& list = state.mBoundVao->mAttribBuffers;
+ if (index >= list.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "`index` (%u) must be < MAX_VERTEX_ATTRIBS.", index);
+ return;
+ }
+
+ const auto buffer = state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER];
+ if (!buffer && byteOffset) {
+ return EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "If ARRAY_BUFFER is null, byteOffset must be zero.");
+ }
+
+ Run<RPROC(VertexAttribPointer)>(index, desc);
+
+ list[index] = buffer;
+}
+
+// -------------------------------- Drawing -------------------------------
+
+void ClientWebGLContext::DrawArraysInstanced(GLenum mode, GLint first,
+ GLsizei count, GLsizei primcount) {
+ Run<RPROC(DrawArraysInstanced)>(mode, first, count, primcount);
+ AfterDrawCall();
+}
+
+void ClientWebGLContext::DrawElementsInstanced(GLenum mode, GLsizei count,
+ GLenum type, WebGLintptr offset,
+ GLsizei primcount) {
+ Run<RPROC(DrawElementsInstanced)>(mode, count, type, offset, primcount);
+ AfterDrawCall();
+}
+
+// ------------------------------ Readback -------------------------------
+void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
+ GLsizei height, GLenum format, GLenum type,
+ WebGLsizeiptr offset,
+ dom::CallerType aCallerType,
+ ErrorResult& out_error) const {
+ const FuncScope funcScope(*this, "readPixels");
+ if (!ReadPixels_SharedPrecheck(aCallerType, out_error)) return;
+ const auto& state = State();
+ if (!ValidateNonNegative("width", width)) return;
+ if (!ValidateNonNegative("height", height)) return;
+ if (!ValidateNonNegative("offset", offset)) return;
+
+ const auto desc = webgl::ReadPixelsDesc{{x, y},
+ *uvec2::From(width, height),
+ {format, type},
+ state.mPixelPackState};
+ Run<RPROC(ReadPixelsPbo)>(desc, static_cast<uint64_t>(offset));
+}
+
+void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
+ GLsizei height, GLenum format, GLenum type,
+ const dom::ArrayBufferView& dstData,
+ GLuint dstElemOffset,
+ dom::CallerType aCallerType,
+ ErrorResult& out_error) const {
+ const FuncScope funcScope(*this, "readPixels");
+ if (!ReadPixels_SharedPrecheck(aCallerType, out_error)) return;
+ const auto& state = State();
+ if (!ValidateNonNegative("width", width)) return;
+ if (!ValidateNonNegative("height", height)) return;
+
+ ////
+
+ js::Scalar::Type reqScalarType;
+ if (!GetJSScalarFromGLType(type, &reqScalarType)) {
+ nsCString name;
+ WebGLContext::EnumName(type, &name);
+ EnqueueError(LOCAL_GL_INVALID_ENUM, "type: invalid enum value %s",
+ name.BeginReading());
+ return;
+ }
+
+ auto viewElemType = dstData.Type();
+ if (viewElemType == js::Scalar::Uint8Clamped) {
+ viewElemType = js::Scalar::Uint8;
+ }
+ if (viewElemType != reqScalarType) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "`pixels` type does not match `type`.");
+ return;
+ }
+
+ uint8_t* bytes;
+ size_t byteLen;
+ if (!ValidateArrayBufferView(dstData, dstElemOffset, 0,
+ LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
+ return;
+ }
+
+ const auto desc = webgl::ReadPixelsDesc{{x, y},
+ *uvec2::From(width, height),
+ {format, type},
+ state.mPixelPackState};
+ const auto range = Range<uint8_t>(bytes, byteLen);
+ if (!DoReadPixels(desc, range)) {
+ return;
+ }
+}
+
+bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
+ const Range<uint8_t> dest) const {
+ const auto notLost =
+ mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
+ if (!notLost) return false;
+ const auto& inProcess = notLost->inProcess;
+ if (inProcess) {
+ inProcess->ReadPixelsInto(desc, dest);
+ return true;
+ }
+ const auto& child = notLost->outOfProcess;
+ child->FlushPendingCmds();
+ webgl::ReadPixelsResultIpc res = {};
+ if (!child->SendReadPixels(desc, dest.length(), &res)) {
+ res = {};
+ }
+ if (!res.byteStride || !res.shmem) return false;
+ const auto& byteStride = res.byteStride;
+ const auto& subrect = res.subrect;
+ const webgl::RaiiShmem shmem{child, res.shmem.ref()};
+ if (!shmem) {
+ EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "Failed to map in back buffer.");
+ return false;
+ }
+
+ const auto& shmemBytes = shmem.ByteRange();
+
+ const auto pii = webgl::PackingInfoInfo::For(desc.pi);
+ if (!pii) {
+ gfxCriticalError() << "ReadPixels: Bad " << desc.pi;
+ return false;
+ }
+ const auto bpp = pii->BytesPerPixel();
+
+ const auto& packing = desc.packState;
+ auto packRect = *uvec2::From(subrect.x, subrect.y);
+ packRect.x += packing.skipPixels;
+ packRect.y += packing.skipRows;
+
+ const auto xByteSize = bpp * static_cast<uint32_t>(subrect.width);
+ const ptrdiff_t byteOffset = packRect.y * byteStride + packRect.x * bpp;
+
+ auto srcItr = shmemBytes.begin() + byteOffset;
+ auto destItr = dest.begin() + byteOffset;
+
+ for (const auto i : IntegerRange(subrect.height)) {
+ if (i) {
+ // Don't trigger an assert on the last loop by pushing a RangedPtr past
+ // its bounds.
+ srcItr += byteStride;
+ destItr += byteStride;
+ MOZ_RELEASE_ASSERT(srcItr + xByteSize <= shmemBytes.end());
+ MOZ_RELEASE_ASSERT(destItr + xByteSize <= dest.end());
+ }
+ Memcpy(destItr, srcItr, xByteSize);
+ }
+
+ return true;
+}
+
+bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
+ const mozilla::ipc::Shmem& shmem) const {
+ const auto notLost =
+ mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
+ if (!notLost) return false;
+ const auto& inProcess = notLost->inProcess;
+ if (inProcess) {
+ const auto& shmemBytes = shmem.Range<uint8_t>();
+ inProcess->ReadPixelsInto(desc, shmemBytes);
+ return true;
+ }
+ const auto& child = notLost->outOfProcess;
+ child->FlushPendingCmds();
+ webgl::ReadPixelsResultIpc res = {};
+ // We assume the input is an unsafe shmem which won't be consumed by this
+ // request. Since SendReadPixels expects a Shmem rvalue, we must create a copy
+ // to provide it that can be consumed instead of the original descriptor.
+ mozilla::ipc::Shmem dest = shmem;
+ if (!child->SendReadPixels(desc, dest, &res)) {
+ res = {};
+ }
+ return res.byteStride > 0;
+}
+
+bool ClientWebGLContext::ReadPixels_SharedPrecheck(
+ dom::CallerType aCallerType, ErrorResult& out_error) const {
+ if (IsContextLost()) return false;
+
+ if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
+ aCallerType != dom::CallerType::System) {
+ JsWarning("readPixels: Not allowed");
+ out_error.Throw(NS_ERROR_DOM_SECURITY_ERR);
+ return false;
+ }
+
+ return true;
+}
+
+// --------------------------------- GL Query ---------------------------------
+
+static inline GLenum QuerySlotTarget(const GLenum specificTarget) {
+ if (specificTarget == LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
+ return LOCAL_GL_ANY_SAMPLES_PASSED;
+ }
+ return specificTarget;
+}
+
+void ClientWebGLContext::GetQuery(JSContext* cx, GLenum specificTarget,
+ GLenum pname,
+ JS::MutableHandle<JS::Value> retval) const {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getQuery");
+ if (IsContextLost()) return;
+ const auto& limits = Limits();
+ auto& state = State();
+
+ if (IsExtensionEnabled(WebGLExtensionID::EXT_disjoint_timer_query)) {
+ if (pname == LOCAL_GL_QUERY_COUNTER_BITS) {
+ switch (specificTarget) {
+ case LOCAL_GL_TIME_ELAPSED_EXT:
+ retval.set(JS::NumberValue(limits.queryCounterBitsTimeElapsed));
+ return;
+
+ case LOCAL_GL_TIMESTAMP_EXT:
+ retval.set(JS::NumberValue(limits.queryCounterBitsTimestamp));
+ return;
+
+ default:
+ EnqueueError_ArgEnum("target", specificTarget);
+ return;
+ }
+ }
+ }
+
+ if (pname != LOCAL_GL_CURRENT_QUERY) {
+ EnqueueError_ArgEnum("pname", pname);
+ return;
+ }
+
+ const auto slotTarget = QuerySlotTarget(specificTarget);
+ const auto& slot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
+ if (!slot) {
+ EnqueueError_ArgEnum("target", specificTarget);
+ return;
+ }
+
+ auto query = *slot;
+ if (query && query->mTarget != specificTarget) {
+ query = nullptr;
+ }
+
+ (void)ToJSValueOrNull(cx, query, retval);
+}
+
+void ClientWebGLContext::GetQueryParameter(
+ JSContext*, WebGLQueryJS& query, const GLenum pname,
+ JS::MutableHandle<JS::Value> retval) const {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getQueryParameter");
+ if (IsContextLost()) return;
+ if (!query.ValidateUsable(*this, "query")) return;
+
+ auto maybe = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetQueryParameter(query.mId, pname);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ Maybe<double> ret;
+ if (!child->SendGetQueryParameter(query.mId, pname, &ret)) {
+ ret.reset();
+ }
+ return ret;
+ }();
+ if (!maybe) return;
+
+ // We must usually wait for an event loop before the query can be available.
+ const bool canBeAvailable =
+ (query.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
+ if (!canBeAvailable) {
+ if (pname != LOCAL_GL_QUERY_RESULT_AVAILABLE) {
+ return;
+ }
+ maybe = Some(0.0);
+ }
+
+ switch (pname) {
+ case LOCAL_GL_QUERY_RESULT_AVAILABLE:
+ retval.set(JS::BooleanValue(*maybe));
+ break;
+
+ default:
+ retval.set(JS::NumberValue(*maybe));
+ break;
+ }
+}
+
+void ClientWebGLContext::BeginQuery(const GLenum specificTarget,
+ WebGLQueryJS& query) {
+ const FuncScope funcScope(*this, "beginQuery");
+ if (IsContextLost()) return;
+ if (!query.ValidateUsable(*this, "query")) return;
+ auto& state = State();
+
+ const auto slotTarget = QuerySlotTarget(specificTarget);
+ const auto& slot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
+ if (!slot) {
+ EnqueueError_ArgEnum("target", specificTarget);
+ return;
+ }
+
+ if (*slot) {
+ auto enumStr = EnumString(slotTarget);
+ if (slotTarget == LOCAL_GL_ANY_SAMPLES_PASSED) {
+ enumStr += "/ANY_SAMPLES_PASSED_CONSERVATIVE";
+ }
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "A Query is already active for %s.", enumStr.c_str());
+ return;
+ }
+
+ if (query.mTarget && query.mTarget != specificTarget) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "`query` cannot be changed to a different target.");
+ return;
+ }
+
+ *slot = &query;
+ query.mTarget = specificTarget;
+
+ Run<RPROC(BeginQuery)>(specificTarget, query.mId);
+}
+
+void ClientWebGLContext::EndQuery(const GLenum specificTarget) {
+ const FuncScope funcScope(*this, "endQuery");
+ if (IsContextLost()) return;
+ auto& state = State();
+
+ const auto slotTarget = QuerySlotTarget(specificTarget);
+ const auto& maybeSlot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
+ if (!maybeSlot) {
+ EnqueueError_ArgEnum("target", specificTarget);
+ return;
+ }
+ auto& slot = *maybeSlot;
+ if (!slot || slot->mTarget != specificTarget) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION, "No Query is active for %s.",
+ EnumString(specificTarget).c_str());
+ return;
+ }
+ const auto query = slot;
+ slot = nullptr;
+
+ Run<RPROC(EndQuery)>(specificTarget);
+
+ auto& availRunnable = EnsureAvailabilityRunnable();
+ availRunnable.mQueries.push_back(query.get());
+ query->mCanBeAvailable = false;
+}
+
+void ClientWebGLContext::QueryCounter(WebGLQueryJS& query,
+ const GLenum target) const {
+ const FuncScope funcScope(*this, "queryCounter");
+ if (IsContextLost()) return;
+ if (!query.ValidateUsable(*this, "query")) return;
+
+ if (target != LOCAL_GL_TIMESTAMP) {
+ EnqueueError(LOCAL_GL_INVALID_ENUM, "`target` must be TIMESTAMP.");
+ return;
+ }
+
+ if (query.mTarget && query.mTarget != target) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "`query` cannot be changed to a different target.");
+ return;
+ }
+ query.mTarget = target;
+
+ Run<RPROC(QueryCounter)>(query.mId);
+
+ auto& availRunnable = EnsureAvailabilityRunnable();
+ availRunnable.mQueries.push_back(&query);
+ query.mCanBeAvailable = false;
+}
+
+// -------------------------------- Sampler -------------------------------
+void ClientWebGLContext::GetSamplerParameter(
+ JSContext* cx, const WebGLSamplerJS& sampler, const GLenum pname,
+ JS::MutableHandle<JS::Value> retval) const {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getSamplerParameter");
+ if (IsContextLost()) return;
+ if (!sampler.ValidateUsable(*this, "sampler")) return;
+
+ const auto maybe = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetSamplerParameter(sampler.mId, pname);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ Maybe<double> ret;
+ if (!child->SendGetSamplerParameter(sampler.mId, pname, &ret)) {
+ ret.reset();
+ }
+ return ret;
+ }();
+ if (maybe) {
+ retval.set(JS::NumberValue(*maybe));
+ }
+}
+
+void ClientWebGLContext::BindSampler(const GLuint unit,
+ WebGLSamplerJS* const sampler) {
+ const FuncScope funcScope(*this, "bindSampler");
+ if (IsContextLost()) return;
+ if (sampler && !sampler->ValidateUsable(*this, "sampler")) return;
+ auto& state = State();
+
+ auto& texUnits = state.mTexUnits;
+ if (unit >= texUnits.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`unit` (%u) larger than %zu.", unit,
+ texUnits.size());
+ return;
+ }
+
+ // -
+
+ texUnits[unit].sampler = sampler;
+
+ Run<RPROC(BindSampler)>(unit, sampler ? sampler->mId : 0);
+}
+
+void ClientWebGLContext::SamplerParameteri(WebGLSamplerJS& sampler,
+ const GLenum pname,
+ const GLint param) const {
+ const FuncScope funcScope(*this, "samplerParameteri");
+ if (IsContextLost()) return;
+ if (!sampler.ValidateUsable(*this, "sampler")) return;
+
+ Run<RPROC(SamplerParameteri)>(sampler.mId, pname, param);
+}
+
+void ClientWebGLContext::SamplerParameterf(WebGLSamplerJS& sampler,
+ const GLenum pname,
+ const GLfloat param) const {
+ const FuncScope funcScope(*this, "samplerParameterf");
+ if (IsContextLost()) return;
+ if (!sampler.ValidateUsable(*this, "sampler")) return;
+
+ Run<RPROC(SamplerParameterf)>(sampler.mId, pname, param);
+}
+
+// ------------------------------- GL Sync ---------------------------------
+
+void ClientWebGLContext::GetSyncParameter(
+ JSContext* const cx, WebGLSyncJS& sync, const GLenum pname,
+ JS::MutableHandle<JS::Value> retval) const {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getSyncParameter");
+ if (IsContextLost()) return;
+ if (!sync.ValidateUsable(*this, "sync")) return;
+
+ retval.set([&]() -> JS::Value {
+ switch (pname) {
+ case LOCAL_GL_OBJECT_TYPE:
+ return JS::NumberValue(LOCAL_GL_SYNC_FENCE);
+ case LOCAL_GL_SYNC_CONDITION:
+ return JS::NumberValue(LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE);
+ case LOCAL_GL_SYNC_FLAGS:
+ return JS::NumberValue(0);
+ case LOCAL_GL_SYNC_STATUS: {
+ if (!sync.mSignaled) {
+ const auto res = ClientWaitSync(sync, 0, 0);
+ sync.mSignaled = (res == LOCAL_GL_ALREADY_SIGNALED ||
+ res == LOCAL_GL_CONDITION_SATISFIED);
+ }
+ return JS::NumberValue(sync.mSignaled ? LOCAL_GL_SIGNALED
+ : LOCAL_GL_UNSIGNALED);
+ }
+ default:
+ EnqueueError_ArgEnum("pname", pname);
+ return JS::NullValue();
+ }
+ }());
+}
+
+GLenum ClientWebGLContext::ClientWaitSync(WebGLSyncJS& sync,
+ const GLbitfield flags,
+ const GLuint64 timeout) const {
+ const FuncScope funcScope(*this, "clientWaitSync");
+ if (IsContextLost()) return LOCAL_GL_WAIT_FAILED;
+ if (!sync.ValidateUsable(*this, "sync")) return LOCAL_GL_WAIT_FAILED;
+
+ if (flags != 0 && flags != LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "`flags` must be SYNC_FLUSH_COMMANDS_BIT or 0.");
+ return LOCAL_GL_WAIT_FAILED;
+ }
+
+ const auto ret = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->ClientWaitSync(sync.mId, flags, timeout);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ GLenum ret = {};
+ if (!child->SendClientWaitSync(sync.mId, flags, timeout, &ret)) {
+ ret = {};
+ }
+ return ret;
+ }();
+
+ switch (ret) {
+ case LOCAL_GL_CONDITION_SATISFIED:
+ case LOCAL_GL_ALREADY_SIGNALED:
+ sync.mSignaled = true;
+ break;
+ }
+
+ // -
+
+ const bool canBeAvailable =
+ (sync.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
+ if (!canBeAvailable) {
+ constexpr uint8_t WARN_AT = 100;
+ if (sync.mNumQueriesBeforeFirstFrameBoundary <= WARN_AT) {
+ sync.mNumQueriesBeforeFirstFrameBoundary += 1;
+ if (sync.mNumQueriesBeforeFirstFrameBoundary == WARN_AT) {
+ EnqueueWarning(
+ "ClientWaitSync must return TIMEOUT_EXPIRED until control has"
+ " returned to the user agent's main loop, but was polled %hhu "
+ "times. Are you spin-locking? (only warns once)",
+ sync.mNumQueriesBeforeFirstFrameBoundary);
+ }
+ }
+ return LOCAL_GL_TIMEOUT_EXPIRED;
+ }
+
+ return ret;
+}
+
+void ClientWebGLContext::WaitSync(const WebGLSyncJS& sync,
+ const GLbitfield flags,
+ const GLint64 timeout) const {
+ const FuncScope funcScope(*this, "waitSync");
+ if (IsContextLost()) return;
+ if (!sync.ValidateUsable(*this, "sync")) return;
+
+ if (flags != 0) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`flags` must be 0.");
+ return;
+ }
+ if (timeout != -1) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`timeout` must be TIMEOUT_IGNORED.");
+ return;
+ }
+
+ JsWarning("waitSync is a no-op.");
+}
+
+// -------------------------- Transform Feedback ---------------------------
+
+void ClientWebGLContext::BindTransformFeedback(
+ const GLenum target, WebGLTransformFeedbackJS* const tf) {
+ const FuncScope funcScope(*this, "bindTransformFeedback");
+ if (IsContextLost()) return;
+ if (tf && !tf->ValidateUsable(*this, "tf")) return;
+ auto& state = State();
+
+ if (target != LOCAL_GL_TRANSFORM_FEEDBACK) {
+ EnqueueError(LOCAL_GL_INVALID_ENUM, "`target` must be TRANSFORM_FEEDBACK.");
+ return;
+ }
+ if (state.mTfActiveAndNotPaused) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Current Transform Feedback object is active and not paused.");
+ return;
+ }
+
+ if (tf) {
+ tf->mHasBeenBound = true;
+ state.mBoundTfo = tf;
+ } else {
+ state.mBoundTfo = state.mDefaultTfo;
+ }
+
+ Run<RPROC(BindTransformFeedback)>(tf ? tf->mId : 0);
+}
+
+void ClientWebGLContext::BeginTransformFeedback(const GLenum primMode) {
+ const FuncScope funcScope(*this, "beginTransformFeedback");
+ if (IsContextLost()) return;
+ auto& state = State();
+ auto& tfo = *(state.mBoundTfo);
+
+ if (tfo.mActiveOrPaused) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Transform Feedback is already active or paused.");
+ return;
+ }
+ MOZ_ASSERT(!state.mTfActiveAndNotPaused);
+
+ auto& prog = state.mCurrentProgram;
+ if (!prog) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION, "No program in use.");
+ return;
+ }
+ const auto& linkResult = GetLinkResult(*prog);
+ if (!linkResult.success) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Program is not successfully linked.");
+ return;
+ }
+
+ auto tfBufferCount = linkResult.active.activeTfVaryings.size();
+ if (tfBufferCount &&
+ linkResult.tfBufferMode == LOCAL_GL_INTERLEAVED_ATTRIBS) {
+ tfBufferCount = 1;
+ }
+ if (!tfBufferCount) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Program does not use Transform Feedback.");
+ return;
+ }
+
+ const auto& buffers = tfo.mAttribBuffers;
+ for (const auto i : IntegerRange(tfBufferCount)) {
+ if (!buffers[i]) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Transform Feedback buffer %u is null.", i);
+ return;
+ }
+ }
+
+ switch (primMode) {
+ case LOCAL_GL_POINTS:
+ case LOCAL_GL_LINES:
+ case LOCAL_GL_TRIANGLES:
+ break;
+ default:
+ EnqueueError(LOCAL_GL_INVALID_ENUM,
+ "`primitiveMode` must be POINTS, LINES< or TRIANGLES.");
+ return;
+ }
+
+ // -
+
+ tfo.mActiveOrPaused = true;
+ tfo.mActiveProgram = prog;
+ tfo.mActiveProgramKeepAlive = prog->mKeepAliveWeak.lock();
+ prog->mActiveTfos.insert(&tfo);
+ state.mTfActiveAndNotPaused = true;
+
+ Run<RPROC(BeginTransformFeedback)>(primMode);
+}
+
+void ClientWebGLContext::EndTransformFeedback() {
+ const FuncScope funcScope(*this, "endTransformFeedback");
+ if (IsContextLost()) return;
+ auto& state = State();
+ auto& tfo = *(state.mBoundTfo);
+
+ if (!tfo.mActiveOrPaused) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Transform Feedback is not active or paused.");
+ return;
+ }
+
+ tfo.mActiveOrPaused = false;
+ tfo.mActiveProgram->mActiveTfos.erase(&tfo);
+ tfo.mActiveProgram = nullptr;
+ tfo.mActiveProgramKeepAlive = nullptr;
+ state.mTfActiveAndNotPaused = false;
+ Run<RPROC(EndTransformFeedback)>();
+}
+
+void ClientWebGLContext::PauseTransformFeedback() {
+ const FuncScope funcScope(*this, "pauseTransformFeedback");
+ if (IsContextLost()) return;
+ auto& state = State();
+ auto& tfo = *(state.mBoundTfo);
+
+ if (!tfo.mActiveOrPaused) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Transform Feedback is not active.");
+ return;
+ }
+ if (!state.mTfActiveAndNotPaused) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Transform Feedback is already paused.");
+ return;
+ }
+
+ state.mTfActiveAndNotPaused = false;
+ Run<RPROC(PauseTransformFeedback)>();
+}
+
+void ClientWebGLContext::ResumeTransformFeedback() {
+ const FuncScope funcScope(*this, "resumeTransformFeedback");
+ if (IsContextLost()) return;
+ auto& state = State();
+ auto& tfo = *(state.mBoundTfo);
+
+ if (!tfo.mActiveOrPaused) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Transform Feedback is not active and paused.");
+ return;
+ }
+ if (state.mTfActiveAndNotPaused) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Transform Feedback is not paused.");
+ return;
+ }
+ if (state.mCurrentProgram != tfo.mActiveProgram) {
+ EnqueueError(
+ LOCAL_GL_INVALID_OPERATION,
+ "Cannot Resume Transform Feedback with a program link result different"
+ " from when Begin was called.");
+ return;
+ }
+
+ state.mTfActiveAndNotPaused = true;
+ Run<RPROC(ResumeTransformFeedback)>();
+}
+
+void ClientWebGLContext::SetFramebufferIsInOpaqueRAF(WebGLFramebufferJS* fb,
+ bool value) {
+ fb->mInOpaqueRAF = value;
+ Run<RPROC(SetFramebufferIsInOpaqueRAF)>(fb->mId, value);
+}
+
+// ---------------------------- Misc Extensions ----------------------------
+void ClientWebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers) {
+ const auto range = MakeRange(buffers);
+ const auto vec = std::vector<GLenum>(range.begin().get(), range.end().get());
+ Run<RPROC(DrawBuffers)>(vec);
+}
+
+void ClientWebGLContext::EnqueueErrorImpl(const GLenum error,
+ const nsACString& text) const {
+ if (!mNotLost) return; // Ignored if context is lost.
+ Run<RPROC(GenerateError)>(error, ToString(text));
+}
+
+void ClientWebGLContext::RequestExtension(const WebGLExtensionID ext) const {
+ Run<RPROC(RequestExtension)>(ext);
+}
+
+// -
+
+bool ClientWebGLContext::IsExtensionForbiddenForCaller(
+ const WebGLExtensionID ext, const dom::CallerType callerType) const {
+ if (callerType == dom::CallerType::System) {
+ return false;
+ }
+
+ if (StaticPrefs::webgl_enable_privileged_extensions()) {
+ return false;
+ }
+
+ switch (ext) {
+ case WebGLExtensionID::MOZ_debug:
+ return true;
+
+ case WebGLExtensionID::WEBGL_debug_renderer_info:
+ return ShouldResistFingerprinting() ||
+ !StaticPrefs::webgl_enable_debug_renderer_info();
+
+ case WebGLExtensionID::WEBGL_debug_shaders:
+ return ShouldResistFingerprinting();
+
+ default:
+ return false;
+ }
+}
+
+bool ClientWebGLContext::IsSupported(const WebGLExtensionID ext,
+ const dom::CallerType callerType) const {
+ if (IsExtensionForbiddenForCaller(ext, callerType)) {
+ return false;
+ }
+
+ const auto& limits = Limits();
+ return limits.supportedExtensions[ext];
+}
+
+void ClientWebGLContext::GetSupportedExtensions(
+ dom::Nullable<nsTArray<nsString>>& retval,
+ const dom::CallerType callerType) const {
+ retval.SetNull();
+ if (!mNotLost) return;
+
+ auto& retarr = retval.SetValue();
+ for (const auto i : MakeEnumeratedRange(WebGLExtensionID::Max)) {
+ if (!IsSupported(i, callerType)) continue;
+
+ const auto& extStr = GetExtensionName(i);
+ retarr.AppendElement(NS_ConvertUTF8toUTF16(extStr));
+ }
+}
+
+// -
+
+void ClientWebGLContext::GetSupportedProfilesASTC(
+ dom::Nullable<nsTArray<nsString>>& retval) const {
+ retval.SetNull();
+ if (!mNotLost) return;
+ const auto& limits = Limits();
+
+ auto& retarr = retval.SetValue();
+ retarr.AppendElement(u"ldr"_ns);
+ if (limits.astcHdr) {
+ retarr.AppendElement(u"hdr"_ns);
+ }
+}
+
+void ClientWebGLContext::ProvokingVertex(const GLenum rawMode) const {
+ const FuncScope funcScope(*this, "provokingVertex");
+ if (IsContextLost()) return;
+
+ const auto mode = webgl::AsEnumCase<webgl::ProvokingVertex>(rawMode);
+ if (!mode) return;
+
+ Run<RPROC(ProvokingVertex)>(*mode);
+
+ funcScope.mKeepNotLostOrNull->state.mProvokingVertex = *mode;
+}
+
+// -
+
+uint32_t ClientWebGLContext::GetPrincipalHashValue() const {
+ if (mCanvasElement) {
+ return mCanvasElement->NodePrincipal()->GetHashValue();
+ }
+ if (mOffscreenCanvas) {
+ nsIGlobalObject* global = mOffscreenCanvas->GetOwnerGlobal();
+ if (global) {
+ nsIPrincipal* principal = global->PrincipalOrNull();
+ if (principal) {
+ return principal->GetHashValue();
+ }
+ }
+ }
+ return 0;
+}
+
+// ---------------------------
+
+void ClientWebGLContext::EnqueueError_ArgEnum(const char* const argName,
+ const GLenum val) const {
+ EnqueueError(LOCAL_GL_INVALID_ENUM, "Bad `%s`: 0x%04x", argName, val);
+}
+
+// -
+// WebGLProgramJS
+
+void ClientWebGLContext::AttachShader(WebGLProgramJS& prog,
+ WebGLShaderJS& shader) const {
+ const FuncScope funcScope(*this, "attachShader");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+ if (!shader.ValidateUsable(*this, "shader")) return;
+
+ auto& slot = *MaybeFind(prog.mNextLink_Shaders, shader.mType);
+ if (slot.shader) {
+ if (&shader == slot.shader) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION, "`shader` is already attached.");
+ } else {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Only one of each type of"
+ " shader may be attached to a program.");
+ }
+ return;
+ }
+ slot = {&shader, shader.mKeepAliveWeak.lock()};
+
+ Run<RPROC(AttachShader)>(prog.mId, shader.mId);
+}
+
+void ClientWebGLContext::BindAttribLocation(WebGLProgramJS& prog,
+ const GLuint location,
+ const nsAString& name) const {
+ const FuncScope funcScope(*this, "detachShader");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+
+ const auto& nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
+ Run<RPROC(BindAttribLocation)>(prog.mId, location, nameU8);
+}
+
+void ClientWebGLContext::DetachShader(WebGLProgramJS& prog,
+ const WebGLShaderJS& shader) const {
+ const FuncScope funcScope(*this, "detachShader");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+ if (!shader.ValidateUsable(*this, "shader")) return;
+
+ auto& slot = *MaybeFind(prog.mNextLink_Shaders, shader.mType);
+
+ if (slot.shader != &shader) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION, "`shader` is not attached.");
+ return;
+ }
+ slot = {};
+
+ Run<RPROC(DetachShader)>(prog.mId, shader.mId);
+}
+
+void ClientWebGLContext::GetAttachedShaders(
+ const WebGLProgramJS& prog,
+ dom::Nullable<nsTArray<RefPtr<WebGLShaderJS>>>& retval) const {
+ const FuncScope funcScope(*this, "getAttachedShaders");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+
+ auto& arr = retval.SetValue();
+ for (const auto& pair : prog.mNextLink_Shaders) {
+ const auto& attachment = pair.second;
+ if (!attachment.shader) continue;
+ arr.AppendElement(attachment.shader);
+ }
+}
+
+void ClientWebGLContext::LinkProgram(WebGLProgramJS& prog) const {
+ const FuncScope funcScope(*this, "linkProgram");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+
+ if (!prog.mActiveTfos.empty()) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION,
+ "Program still in use by active or paused"
+ " Transform Feedback objects.");
+ return;
+ }
+
+ prog.mResult = std::make_shared<webgl::LinkResult>();
+ prog.mUniformLocByName = Nothing();
+ prog.mUniformBlockBindings = {};
+ Run<RPROC(LinkProgram)>(prog.mId);
+}
+
+void ClientWebGLContext::TransformFeedbackVaryings(
+ WebGLProgramJS& prog, const dom::Sequence<nsString>& varyings,
+ const GLenum bufferMode) const {
+ const FuncScope funcScope(*this, "transformFeedbackVaryings");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+
+ std::vector<std::string> varyingsU8;
+ varyingsU8.reserve(varyings.Length());
+ for (const auto& cur : varyings) {
+ const auto curU8 = ToString(NS_ConvertUTF16toUTF8(cur));
+ varyingsU8.push_back(curU8);
+ }
+
+ Run<RPROC(TransformFeedbackVaryings)>(prog.mId, varyingsU8, bufferMode);
+}
+
+void ClientWebGLContext::UniformBlockBinding(WebGLProgramJS& prog,
+ const GLuint blockIndex,
+ const GLuint blockBinding) const {
+ const FuncScope funcScope(*this, "uniformBlockBinding");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+ const auto& state = State();
+
+ (void)GetLinkResult(prog);
+ auto& list = prog.mUniformBlockBindings;
+ if (blockIndex >= list.size()) {
+ EnqueueError(
+ LOCAL_GL_INVALID_VALUE,
+ "`blockIndex` (%u) must be less than ACTIVE_UNIFORM_BLOCKS (%zu).",
+ blockIndex, list.size());
+ return;
+ }
+ if (blockBinding >= state.mBoundUbos.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "`blockBinding` (%u) must be less than "
+ "MAX_UNIFORM_BUFFER_BINDINGS (%zu).",
+ blockBinding, state.mBoundUbos.size());
+ return;
+ }
+
+ list[blockIndex] = blockBinding;
+ Run<RPROC(UniformBlockBinding)>(prog.mId, blockIndex, blockBinding);
+}
+
+// WebGLProgramJS link result reflection
+
+already_AddRefed<WebGLActiveInfoJS> ClientWebGLContext::GetActiveAttrib(
+ const WebGLProgramJS& prog, const GLuint index) {
+ const FuncScope funcScope(*this, "getActiveAttrib");
+ if (IsContextLost()) return nullptr;
+ if (!prog.ValidateUsable(*this, "program")) return nullptr;
+
+ const auto& res = GetLinkResult(prog);
+ const auto& list = res.active.activeAttribs;
+ if (index >= list.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
+ return nullptr;
+ }
+
+ const auto& info = list[index];
+ return AsAddRefed(new WebGLActiveInfoJS(info));
+}
+
+already_AddRefed<WebGLActiveInfoJS> ClientWebGLContext::GetActiveUniform(
+ const WebGLProgramJS& prog, const GLuint index) {
+ const FuncScope funcScope(*this, "getActiveUniform");
+ if (IsContextLost()) return nullptr;
+ if (!prog.ValidateUsable(*this, "program")) return nullptr;
+
+ const auto& res = GetLinkResult(prog);
+ const auto& list = res.active.activeUniforms;
+ if (index >= list.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
+ return nullptr;
+ }
+
+ const auto& info = list[index];
+ return AsAddRefed(new WebGLActiveInfoJS(info));
+}
+
+void ClientWebGLContext::GetActiveUniformBlockName(const WebGLProgramJS& prog,
+ const GLuint index,
+ nsAString& retval) const {
+ retval.SetIsVoid(true);
+ const FuncScope funcScope(*this, "getActiveUniformBlockName");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+
+ const auto& res = GetLinkResult(prog);
+ if (!res.success) {
+ EnqueueError(LOCAL_GL_INVALID_OPERATION, "Program has not been linked.");
+ return;
+ }
+
+ const auto& list = res.active.activeUniformBlocks;
+ if (index >= list.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
+ return;
+ }
+
+ const auto& block = list[index];
+ CopyUTF8toUTF16(block.name, retval);
+}
+
+void ClientWebGLContext::GetActiveUniformBlockParameter(
+ JSContext* const cx, const WebGLProgramJS& prog, const GLuint index,
+ const GLenum pname, JS::MutableHandle<JS::Value> retval, ErrorResult& rv) {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getActiveUniformBlockParameter");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+
+ const auto& res = GetLinkResult(prog);
+ const auto& list = res.active.activeUniformBlocks;
+ if (index >= list.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
+ return;
+ }
+ const auto& block = list[index];
+
+ retval.set([&]() -> JS::Value {
+ switch (pname) {
+ case LOCAL_GL_UNIFORM_BLOCK_BINDING:
+ return JS::NumberValue(prog.mUniformBlockBindings[index]);
+
+ case LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE:
+ return JS::NumberValue(block.dataSize);
+
+ case LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS:
+ return JS::NumberValue(block.activeUniformIndices.size());
+
+ case LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: {
+ const auto& indices = block.activeUniformIndices;
+ JS::Rooted<JSObject*> obj(
+ cx,
+ dom::Uint32Array::Create(cx, this, indices.size(), indices.data()));
+ if (!obj) {
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ }
+ return JS::ObjectOrNullValue(obj);
+ }
+
+ case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER:
+ return JS::BooleanValue(block.referencedByVertexShader);
+
+ case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER:
+ return JS::BooleanValue(block.referencedByFragmentShader);
+
+ default:
+ EnqueueError_ArgEnum("pname", pname);
+ return JS::NullValue();
+ }
+ }());
+}
+
+void ClientWebGLContext::GetActiveUniforms(
+ JSContext* const cx, const WebGLProgramJS& prog,
+ const dom::Sequence<GLuint>& uniformIndices, const GLenum pname,
+ JS::MutableHandle<JS::Value> retval) const {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getActiveUniforms");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+
+ const auto& res = GetLinkResult(prog);
+ const auto& list = res.active.activeUniforms;
+
+ const auto count = uniformIndices.Length();
+ JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, count));
+ if (!array) return; // Just bail.
+
+ for (const auto i : IntegerRange(count)) {
+ const auto index = uniformIndices[i];
+ if (index >= list.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE,
+ "`uniformIndices[%u]`: `%u` too large.", i, index);
+ return;
+ }
+ const auto& uniform = list[index];
+
+ JS::Rooted<JS::Value> value(cx);
+ switch (pname) {
+ case LOCAL_GL_UNIFORM_TYPE:
+ value = JS::NumberValue(uniform.elemType);
+ break;
+
+ case LOCAL_GL_UNIFORM_SIZE:
+ value = JS::NumberValue(uniform.elemCount);
+ break;
+
+ case LOCAL_GL_UNIFORM_BLOCK_INDEX:
+ value = JS::NumberValue(uniform.block_index);
+ break;
+
+ case LOCAL_GL_UNIFORM_OFFSET:
+ value = JS::NumberValue(uniform.block_offset);
+ break;
+
+ case LOCAL_GL_UNIFORM_ARRAY_STRIDE:
+ value = JS::NumberValue(uniform.block_arrayStride);
+ break;
+
+ case LOCAL_GL_UNIFORM_MATRIX_STRIDE:
+ value = JS::NumberValue(uniform.block_matrixStride);
+ break;
+
+ case LOCAL_GL_UNIFORM_IS_ROW_MAJOR:
+ value = JS::BooleanValue(uniform.block_isRowMajor);
+ break;
+
+ default:
+ EnqueueError_ArgEnum("pname", pname);
+ return;
+ }
+ if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE)) return;
+ }
+
+ retval.setObject(*array);
+}
+
+already_AddRefed<WebGLActiveInfoJS>
+ClientWebGLContext::GetTransformFeedbackVarying(const WebGLProgramJS& prog,
+ const GLuint index) {
+ const FuncScope funcScope(*this, "getTransformFeedbackVarying");
+ if (IsContextLost()) return nullptr;
+ if (!prog.ValidateUsable(*this, "program")) return nullptr;
+
+ const auto& res = GetLinkResult(prog);
+ const auto& list = res.active.activeTfVaryings;
+ if (index >= list.size()) {
+ EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
+ return nullptr;
+ }
+
+ const auto& info = list[index];
+ return AsAddRefed(new WebGLActiveInfoJS(info));
+}
+
+GLint ClientWebGLContext::GetAttribLocation(const WebGLProgramJS& prog,
+ const nsAString& name) const {
+ const FuncScope funcScope(*this, "getAttribLocation");
+ if (IsContextLost()) return -1;
+ if (!prog.ValidateUsable(*this, "program")) return -1;
+
+ const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
+ const auto& res = GetLinkResult(prog);
+ for (const auto& cur : res.active.activeAttribs) {
+ if (cur.name == nameU8) return cur.location;
+ }
+
+ const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
+ if (err) {
+ EnqueueError(err->type, "%s", err->info.c_str());
+ }
+ return -1;
+}
+
+GLint ClientWebGLContext::GetFragDataLocation(const WebGLProgramJS& prog,
+ const nsAString& name) const {
+ const FuncScope funcScope(*this, "getFragDataLocation");
+ if (IsContextLost()) return -1;
+ if (!prog.ValidateUsable(*this, "program")) return -1;
+
+ const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
+
+ const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
+ if (err) {
+ EnqueueError(*err);
+ return -1;
+ }
+
+ return [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetFragDataLocation(prog.mId, nameU8);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ GLint ret = {};
+ if (!child->SendGetFragDataLocation(prog.mId, nameU8, &ret)) {
+ ret = {};
+ }
+ return ret;
+ }();
+}
+
+GLuint ClientWebGLContext::GetUniformBlockIndex(
+ const WebGLProgramJS& prog, const nsAString& blockName) const {
+ const FuncScope funcScope(*this, "getUniformBlockIndex");
+ if (IsContextLost()) return LOCAL_GL_INVALID_INDEX;
+ if (!prog.ValidateUsable(*this, "program")) return LOCAL_GL_INVALID_INDEX;
+
+ const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(blockName));
+
+ const auto& res = GetLinkResult(prog);
+ const auto& list = res.active.activeUniformBlocks;
+ for (const auto i : IntegerRange(list.size())) {
+ const auto& cur = list[i];
+ if (cur.name == nameU8) {
+ return i;
+ }
+ }
+ return LOCAL_GL_INVALID_INDEX;
+}
+
+void ClientWebGLContext::GetUniformIndices(
+ const WebGLProgramJS& prog, const dom::Sequence<nsString>& uniformNames,
+ dom::Nullable<nsTArray<GLuint>>& retval) const {
+ const FuncScope funcScope(*this, "getUniformIndices");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+
+ const auto& res = GetLinkResult(prog);
+ auto ret = nsTArray<GLuint>(uniformNames.Length());
+
+ for (const auto& queriedNameU16 : uniformNames) {
+ const auto queriedName = ToString(NS_ConvertUTF16toUTF8(queriedNameU16));
+ const auto impliedProperArrayQueriedName = queriedName + "[0]";
+
+ GLuint activeId = LOCAL_GL_INVALID_INDEX;
+ for (const auto i : IntegerRange(res.active.activeUniforms.size())) {
+ // O(N^2) ok for small N.
+ const auto& activeInfoForI = res.active.activeUniforms[i];
+ if (queriedName == activeInfoForI.name ||
+ impliedProperArrayQueriedName == activeInfoForI.name) {
+ activeId = i;
+ break;
+ }
+ }
+ ret.AppendElement(activeId);
+ }
+
+ retval.SetValue(std::move(ret));
+}
+
+already_AddRefed<WebGLUniformLocationJS> ClientWebGLContext::GetUniformLocation(
+ const WebGLProgramJS& prog, const nsAString& name) const {
+ const FuncScope funcScope(*this, "getUniformLocation");
+ if (IsContextLost()) return nullptr;
+ if (!prog.ValidateUsable(*this, "program")) return nullptr;
+
+ const auto& res = GetLinkResult(prog);
+
+ if (!prog.mUniformLocByName) {
+ // Cache a map from name->location.
+ // Since the only way to set uniforms requires calling GetUniformLocation,
+ // we expect apps to query most active uniforms once for each scalar or
+ // array. NB: Uniform array setters do have overflow semantics, even though
+ // uniform locations aren't guaranteed contiguous, but GetUniformLocation
+ // must still be called once per array.
+ prog.mUniformLocByName.emplace();
+
+ for (const auto& activeUniform : res.active.activeUniforms) {
+ if (activeUniform.block_index != -1) continue;
+
+ auto locName = activeUniform.name;
+ const auto indexed = webgl::ParseIndexed(locName);
+ if (indexed) {
+ locName = indexed->name;
+ }
+
+ const auto err = CheckGLSLVariableName(mIsWebGL2, locName);
+ if (err) continue;
+
+ const auto baseLength = locName.size();
+ for (const auto& pair : activeUniform.locByIndex) {
+ if (indexed) {
+ locName.erase(baseLength); // Erase previous "[N]".
+ locName += '[';
+ locName += std::to_string(pair.first);
+ locName += ']';
+ }
+ const auto locInfo =
+ WebGLProgramJS::UniformLocInfo{pair.second, activeUniform.elemType};
+ prog.mUniformLocByName->insert({locName, locInfo});
+ }
+ }
+ }
+ const auto& locByName = *(prog.mUniformLocByName);
+
+ const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
+ auto loc = MaybeFind(locByName, nameU8);
+ if (!loc) {
+ loc = MaybeFind(locByName, nameU8 + "[0]");
+ }
+ if (!loc) {
+ const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
+ if (err) {
+ EnqueueError(err->type, "%s", err->info.c_str());
+ }
+ return nullptr;
+ }
+
+ return AsAddRefed(new WebGLUniformLocationJS(*this, prog.mResult,
+ loc->location, loc->elemType));
+}
+
+std::array<uint16_t, 3> ValidUploadElemTypes(const GLenum elemType) {
+ std::vector<GLenum> ret;
+ switch (elemType) {
+ case LOCAL_GL_BOOL:
+ ret = {LOCAL_GL_FLOAT, LOCAL_GL_INT, LOCAL_GL_UNSIGNED_INT};
+ break;
+ case LOCAL_GL_BOOL_VEC2:
+ ret = {LOCAL_GL_FLOAT_VEC2, LOCAL_GL_INT_VEC2,
+ LOCAL_GL_UNSIGNED_INT_VEC2};
+ break;
+ case LOCAL_GL_BOOL_VEC3:
+ ret = {LOCAL_GL_FLOAT_VEC3, LOCAL_GL_INT_VEC3,
+ LOCAL_GL_UNSIGNED_INT_VEC3};
+ break;
+ case LOCAL_GL_BOOL_VEC4:
+ ret = {LOCAL_GL_FLOAT_VEC4, LOCAL_GL_INT_VEC4,
+ LOCAL_GL_UNSIGNED_INT_VEC4};
+ break;
+
+ case LOCAL_GL_SAMPLER_2D:
+ case LOCAL_GL_SAMPLER_3D:
+ case LOCAL_GL_SAMPLER_CUBE:
+ case LOCAL_GL_SAMPLER_2D_SHADOW:
+ case LOCAL_GL_SAMPLER_2D_ARRAY:
+ case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+ case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+ case LOCAL_GL_INT_SAMPLER_2D:
+ case LOCAL_GL_INT_SAMPLER_3D:
+ case LOCAL_GL_INT_SAMPLER_CUBE:
+ case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+ ret = {LOCAL_GL_INT};
+ break;
+
+ default:
+ ret = {elemType};
+ break;
+ }
+
+ std::array<uint16_t, 3> arr = {};
+ MOZ_ASSERT(arr[2] == 0);
+ for (const auto i : IntegerRange(ret.size())) {
+ arr[i] = AssertedCast<uint16_t>(ret[i]);
+ }
+ return arr;
+}
+
+void ClientWebGLContext::GetProgramInfoLog(const WebGLProgramJS& prog,
+ nsAString& retval) const {
+ retval.SetIsVoid(true);
+ const FuncScope funcScope(*this, "getProgramInfoLog");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+
+ const auto& res = GetLinkResult(prog);
+ CopyUTF8toUTF16(res.log, retval);
+}
+
+void ClientWebGLContext::GetProgramParameter(
+ JSContext* const js, const WebGLProgramJS& prog, const GLenum pname,
+ JS::MutableHandle<JS::Value> retval) const {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getProgramParameter");
+ if (IsContextLost()) return;
+ if (!prog.ValidateUsable(*this, "program")) return;
+
+ retval.set([&]() -> JS::Value {
+ switch (pname) {
+ case LOCAL_GL_DELETE_STATUS:
+ // "Is flagged for deletion?"
+ return JS::BooleanValue(!prog.mKeepAlive);
+ case LOCAL_GL_VALIDATE_STATUS:
+ return JS::BooleanValue(prog.mLastValidate);
+ case LOCAL_GL_ATTACHED_SHADERS: {
+ size_t shaders = 0;
+ for (const auto& pair : prog.mNextLink_Shaders) {
+ const auto& slot = pair.second;
+ if (slot.shader) {
+ shaders += 1;
+ }
+ }
+ return JS::NumberValue(shaders);
+ }
+ default:
+ break;
+ }
+
+ const auto& res = GetLinkResult(prog);
+
+ switch (pname) {
+ case LOCAL_GL_LINK_STATUS:
+ return JS::BooleanValue(res.success);
+
+ case LOCAL_GL_ACTIVE_ATTRIBUTES:
+ return JS::NumberValue(res.active.activeAttribs.size());
+
+ case LOCAL_GL_ACTIVE_UNIFORMS:
+ return JS::NumberValue(res.active.activeUniforms.size());
+
+ case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
+ if (!mIsWebGL2) break;
+ return JS::NumberValue(res.tfBufferMode);
+
+ case LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS:
+ if (!mIsWebGL2) break;
+ return JS::NumberValue(res.active.activeTfVaryings.size());
+
+ case LOCAL_GL_ACTIVE_UNIFORM_BLOCKS:
+ if (!mIsWebGL2) break;
+ return JS::NumberValue(res.active.activeUniformBlocks.size());
+
+ default:
+ break;
+ }
+ EnqueueError_ArgEnum("pname", pname);
+ return JS::NullValue();
+ }());
+}
+
+// -
+// WebGLShaderJS
+
+void ClientWebGLContext::CompileShader(WebGLShaderJS& shader) const {
+ const FuncScope funcScope(*this, "compileShader");
+ if (IsContextLost()) return;
+ if (!shader.ValidateUsable(*this, "shader")) return;
+
+ shader.mResult = {};
+ Run<RPROC(CompileShader)>(shader.mId);
+}
+
+void ClientWebGLContext::GetShaderInfoLog(const WebGLShaderJS& shader,
+ nsAString& retval) const {
+ retval.SetIsVoid(true);
+ const FuncScope funcScope(*this, "getShaderInfoLog");
+ if (IsContextLost()) return;
+ if (!shader.ValidateUsable(*this, "shader")) return;
+
+ const auto& result = GetCompileResult(shader);
+ CopyUTF8toUTF16(result.log, retval);
+}
+
+void ClientWebGLContext::GetShaderParameter(
+ JSContext* const cx, const WebGLShaderJS& shader, const GLenum pname,
+ JS::MutableHandle<JS::Value> retval) const {
+ retval.set(JS::NullValue());
+ const FuncScope funcScope(*this, "getShaderParameter");
+ if (IsContextLost()) return;
+ if (!shader.ValidateUsable(*this, "shader")) return;
+
+ retval.set([&]() -> JS::Value {
+ switch (pname) {
+ case LOCAL_GL_SHADER_TYPE:
+ return JS::NumberValue(shader.mType);
+
+ case LOCAL_GL_DELETE_STATUS: // "Is flagged for deletion?"
+ return JS::BooleanValue(!shader.mKeepAlive);
+
+ case LOCAL_GL_COMPILE_STATUS: {
+ const auto& result = GetCompileResult(shader);
+ return JS::BooleanValue(result.success);
+ }
+
+ default:
+ EnqueueError_ArgEnum("pname", pname);
+ return JS::NullValue();
+ }
+ }());
+}
+
+void ClientWebGLContext::GetShaderSource(const WebGLShaderJS& shader,
+ nsAString& retval) const {
+ retval.SetIsVoid(true);
+ const FuncScope funcScope(*this, "getShaderSource");
+ if (IsContextLost()) return;
+ if (!shader.ValidateUsable(*this, "shader")) return;
+
+ CopyUTF8toUTF16(shader.mSource, retval);
+}
+
+void ClientWebGLContext::GetTranslatedShaderSource(const WebGLShaderJS& shader,
+ nsAString& retval) const {
+ retval.SetIsVoid(true);
+ const FuncScope funcScope(*this, "getTranslatedShaderSource");
+ if (IsContextLost()) return;
+ if (!shader.ValidateUsable(*this, "shader")) return;
+
+ const auto& result = GetCompileResult(shader);
+ CopyUTF8toUTF16(result.translatedSource, retval);
+}
+
+void ClientWebGLContext::ShaderSource(WebGLShaderJS& shader,
+ const nsAString& sourceU16) const {
+ const FuncScope funcScope(*this, "shaderSource");
+ if (IsContextLost()) return;
+ if (!shader.ValidateUsable(*this, "shader")) return;
+
+ shader.mSource = ToString(NS_ConvertUTF16toUTF8(sourceU16));
+ Run<RPROC(ShaderSource)>(shader.mId, shader.mSource);
+}
+
+// -
+
+const webgl::CompileResult& ClientWebGLContext::GetCompileResult(
+ const WebGLShaderJS& shader) const {
+ if (shader.mResult.pending) {
+ shader.mResult = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetCompileResult(shader.mId);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ webgl::CompileResult ret = {};
+ if (!child->SendGetCompileResult(shader.mId, &ret)) {
+ ret = {};
+ }
+ return ret;
+ }();
+ }
+ return shader.mResult;
+}
+
+const webgl::LinkResult& ClientWebGLContext::GetLinkResult(
+ const WebGLProgramJS& prog) const {
+ if (prog.mResult->pending) {
+ const auto notLost =
+ mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
+ if (!notLost) return *(prog.mResult);
+
+ *(prog.mResult) = [&]() {
+ const auto& inProcess = mNotLost->inProcess;
+ if (inProcess) {
+ return inProcess->GetLinkResult(prog.mId);
+ }
+ const auto& child = mNotLost->outOfProcess;
+ child->FlushPendingCmds();
+ webgl::LinkResult ret;
+ if (!child->SendGetLinkResult(prog.mId, &ret)) {
+ ret = {};
+ }
+ return ret;
+ }();
+
+ prog.mUniformBlockBindings.resize(
+ prog.mResult->active.activeUniformBlocks.size());
+
+ auto& state = State();
+ if (state.mCurrentProgram == &prog && prog.mResult->success) {
+ state.mActiveLinkResult = prog.mResult;
+ }
+ }
+ return *(prog.mResult);
+}
+
+#undef RPROC
+
+// ---------------------------
+
+bool ClientWebGLContext::ValidateArrayBufferView(
+ const dom::ArrayBufferView& view, GLuint elemOffset,
+ GLuint elemCountOverride, const GLenum errorEnum, uint8_t** const out_bytes,
+ size_t* const out_byteLen) const {
+ view.ComputeState();
+ uint8_t* const bytes = view.Data();
+ const size_t byteLen = view.Length();
+
+ const auto& elemSize = SizeOfViewElem(view);
+
+ size_t elemCount = byteLen / elemSize;
+ if (elemOffset > elemCount) {
+ EnqueueError(errorEnum, "Invalid offset into ArrayBufferView.");
+ return false;
+ }
+ elemCount -= elemOffset;
+
+ if (elemCountOverride) {
+ if (elemCountOverride > elemCount) {
+ EnqueueError(errorEnum, "Invalid sub-length for ArrayBufferView.");
+ return false;
+ }
+ elemCount = elemCountOverride;
+ }
+
+ *out_bytes = bytes + (elemOffset * elemSize);
+ *out_byteLen = elemCount * elemSize;
+ return true;
+}
+
+// ---------------------------
+
+webgl::ObjectJS::ObjectJS(const ClientWebGLContext& webgl)
+ : mGeneration(webgl.mNotLost), mId(webgl.mNotLost->state.NextId()) {}
+
+// -
+
+WebGLFramebufferJS::WebGLFramebufferJS(const ClientWebGLContext& webgl,
+ bool opaque)
+ : webgl::ObjectJS(webgl), mOpaque(opaque) {
+ (void)mAttachments[LOCAL_GL_DEPTH_ATTACHMENT];
+ (void)mAttachments[LOCAL_GL_STENCIL_ATTACHMENT];
+ if (!webgl.mIsWebGL2) {
+ (void)mAttachments[LOCAL_GL_DEPTH_STENCIL_ATTACHMENT];
+ }
+
+ EnsureColorAttachments();
+}
+
+void WebGLFramebufferJS::EnsureColorAttachments() {
+ const auto& webgl = Context();
+ const auto& limits = webgl->Limits();
+ auto maxColorDrawBuffers = limits.maxColorDrawBuffers;
+ if (!webgl->mIsWebGL2 &&
+ !webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers)) {
+ maxColorDrawBuffers = 1;
+ }
+ for (const auto i : IntegerRange(maxColorDrawBuffers)) {
+ (void)mAttachments[LOCAL_GL_COLOR_ATTACHMENT0 + i];
+ }
+}
+
+WebGLProgramJS::WebGLProgramJS(const ClientWebGLContext& webgl)
+ : webgl::ObjectJS(webgl),
+ mKeepAlive(std::make_shared<webgl::ProgramKeepAlive>(*this)),
+ mKeepAliveWeak(mKeepAlive) {
+ (void)mNextLink_Shaders[LOCAL_GL_VERTEX_SHADER];
+ (void)mNextLink_Shaders[LOCAL_GL_FRAGMENT_SHADER];
+
+ mResult = std::make_shared<webgl::LinkResult>();
+}
+
+WebGLShaderJS::WebGLShaderJS(const ClientWebGLContext& webgl, const GLenum type)
+ : webgl::ObjectJS(webgl),
+ mType(type),
+ mKeepAlive(std::make_shared<webgl::ShaderKeepAlive>(*this)),
+ mKeepAliveWeak(mKeepAlive) {}
+
+WebGLTransformFeedbackJS::WebGLTransformFeedbackJS(
+ const ClientWebGLContext& webgl)
+ : webgl::ObjectJS(webgl),
+ mAttribBuffers(webgl::kMaxTransformFeedbackSeparateAttribs) {}
+
+WebGLVertexArrayJS::WebGLVertexArrayJS(const ClientWebGLContext& webgl)
+ : webgl::ObjectJS(webgl), mAttribBuffers(webgl.Limits().maxVertexAttribs) {}
+
+// -
+
+#define _(WebGLType) \
+ JSObject* WebGLType##JS::WrapObject(JSContext* const cx, \
+ JS::Handle<JSObject*> givenProto) { \
+ return dom::WebGLType##_Binding::Wrap(cx, this, givenProto); \
+ }
+
+_(WebGLBuffer)
+_(WebGLFramebuffer)
+_(WebGLProgram)
+_(WebGLQuery)
+_(WebGLRenderbuffer)
+_(WebGLSampler)
+_(WebGLShader)
+_(WebGLSync)
+_(WebGLTexture)
+_(WebGLTransformFeedback)
+_(WebGLUniformLocation)
+//_(WebGLVertexArray) // The webidl is `WebGLVertexArrayObject` :(
+
+#undef _
+
+JSObject* WebGLVertexArrayJS::WrapObject(JSContext* const cx,
+ JS::Handle<JSObject*> givenProto) {
+ return dom::WebGLVertexArrayObject_Binding::Wrap(cx, this, givenProto);
+}
+
+bool WebGLActiveInfoJS::WrapObject(JSContext* const cx,
+ JS::Handle<JSObject*> givenProto,
+ JS::MutableHandle<JSObject*> reflector) {
+ return dom::WebGLActiveInfo_Binding::Wrap(cx, this, givenProto, reflector);
+}
+
+bool WebGLShaderPrecisionFormatJS::WrapObject(
+ JSContext* const cx, JS::Handle<JSObject*> givenProto,
+ JS::MutableHandle<JSObject*> reflector) {
+ return dom::WebGLShaderPrecisionFormat_Binding::Wrap(cx, this, givenProto,
+ reflector);
+}
+
+// ---------------------
+
+// Todo: Move this to RefPtr.h.
+template <typename T>
+void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
+ const RefPtr<T>& field, const char* name,
+ uint32_t flags) {
+ ImplCycleCollectionTraverse(callback, const_cast<RefPtr<T>&>(field), name,
+ flags);
+}
+
+// -
+
+template <typename T>
+void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
+ const std::vector<RefPtr<T>>& field,
+ const char* name, uint32_t flags) {
+ for (const auto& cur : field) {
+ ImplCycleCollectionTraverse(callback, cur, name, flags);
+ }
+}
+
+template <typename T>
+void ImplCycleCollectionUnlink(std::vector<RefPtr<T>>& field) {
+ field = {};
+}
+
+// -
+
+template <typename T, size_t N>
+void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
+ const std::array<RefPtr<T>, N>& field,
+ const char* name, uint32_t flags) {
+ for (const auto& cur : field) {
+ ImplCycleCollectionTraverse(callback, cur, name, flags);
+ }
+}
+
+template <typename T, size_t N>
+void ImplCycleCollectionUnlink(std::array<RefPtr<T>, N>& field) {
+ field = {};
+}
+
+// -
+
+template <typename T>
+void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& callback,
+ const std::unordered_map<GLenum, RefPtr<T>>& field, const char* name,
+ uint32_t flags) {
+ for (const auto& pair : field) {
+ ImplCycleCollectionTraverse(callback, pair.second, name, flags);
+ }
+}
+
+template <typename T>
+void ImplCycleCollectionUnlink(std::unordered_map<GLenum, RefPtr<T>>& field) {
+ field = {};
+}
+
+// -
+
+void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& callback,
+ const std::unordered_map<GLenum, WebGLFramebufferJS::Attachment>& field,
+ const char* name, uint32_t flags) {
+ for (const auto& pair : field) {
+ const auto& attach = pair.second;
+ ImplCycleCollectionTraverse(callback, attach.rb, name, flags);
+ ImplCycleCollectionTraverse(callback, attach.tex, name, flags);
+ }
+}
+
+void ImplCycleCollectionUnlink(
+ std::unordered_map<GLenum, WebGLFramebufferJS::Attachment>& field) {
+ field = {};
+}
+
+// -
+
+void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& callback,
+ const std::unordered_map<GLenum, WebGLProgramJS::Attachment>& field,
+ const char* name, uint32_t flags) {
+ for (const auto& pair : field) {
+ const auto& attach = pair.second;
+ ImplCycleCollectionTraverse(callback, attach.shader, name, flags);
+ }
+}
+
+void ImplCycleCollectionUnlink(
+ std::unordered_map<GLenum, WebGLProgramJS::Attachment>& field) {
+ field = {};
+}
+
+// -
+
+void ImplCycleCollectionUnlink(
+ const RefPtr<ClientWebGLExtensionLoseContext>& field) {
+ const_cast<RefPtr<ClientWebGLExtensionLoseContext>&>(field) = nullptr;
+}
+void ImplCycleCollectionUnlink(const RefPtr<WebGLProgramJS>& field) {
+ const_cast<RefPtr<WebGLProgramJS>&>(field) = nullptr;
+}
+void ImplCycleCollectionUnlink(const RefPtr<WebGLShaderJS>& field) {
+ const_cast<RefPtr<WebGLShaderJS>&>(field) = nullptr;
+}
+
+// ----------------------
+
+void ImplCycleCollectionTraverse(
+ nsCycleCollectionTraversalCallback& callback,
+ const std::shared_ptr<webgl::NotLostData>& field, const char* name,
+ uint32_t flags) {
+ if (!field) return;
+
+ ImplCycleCollectionTraverse(callback, field->extensions,
+ "NotLostData.extensions", flags);
+
+ const auto& state = field->state;
+
+ ImplCycleCollectionTraverse(callback, state.mDefaultTfo, "state.mDefaultTfo",
+ flags);
+ ImplCycleCollectionTraverse(callback, state.mDefaultVao, "state.mDefaultVao",
+ flags);
+
+ ImplCycleCollectionTraverse(callback, state.mCurrentProgram,
+ "state.mCurrentProgram", flags);
+
+ ImplCycleCollectionTraverse(callback, state.mBoundBufferByTarget,
+ "state.mBoundBufferByTarget", flags);
+ ImplCycleCollectionTraverse(callback, state.mBoundUbos, "state.mBoundUbos",
+ flags);
+ ImplCycleCollectionTraverse(callback, state.mBoundDrawFb,
+ "state.mBoundDrawFb", flags);
+ ImplCycleCollectionTraverse(callback, state.mBoundReadFb,
+ "state.mBoundReadFb", flags);
+ ImplCycleCollectionTraverse(callback, state.mBoundRb, "state.mBoundRb",
+ flags);
+ ImplCycleCollectionTraverse(callback, state.mBoundTfo, "state.mBoundTfo",
+ flags);
+ ImplCycleCollectionTraverse(callback, state.mBoundVao, "state.mBoundVao",
+ flags);
+ ImplCycleCollectionTraverse(callback, state.mCurrentQueryByTarget,
+ "state.state.mCurrentQueryByTarget", flags);
+
+ for (const auto& texUnit : state.mTexUnits) {
+ ImplCycleCollectionTraverse(callback, texUnit.sampler,
+ "state.mTexUnits[].sampler", flags);
+ ImplCycleCollectionTraverse(callback, texUnit.texByTarget,
+ "state.mTexUnits[].texByTarget", flags);
+ }
+}
+
+void ImplCycleCollectionUnlink(std::shared_ptr<webgl::NotLostData>& field) {
+ if (!field) return;
+ const auto keepAlive = field;
+ keepAlive->extensions = {};
+ keepAlive->state = {};
+ field = nullptr;
+}
+
+// -----------------------------------------------------
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLBufferJS)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLFramebufferJS, mAttachments)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLProgramJS, mNextLink_Shaders)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLQueryJS)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLRenderbufferJS)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLSamplerJS)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLShaderJS)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLSyncJS)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLTextureJS)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLTransformFeedbackJS, mAttribBuffers,
+ mActiveProgram)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLUniformLocationJS)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLVertexArrayJS, mIndexBuffer,
+ mAttribBuffers)
+
+// -
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ClientWebGLContext)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ClientWebGLContext)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ClientWebGLContext)
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(
+ ClientWebGLContext, mExtLoseContext, mNotLost,
+ // Don't forget nsICanvasRenderingContextInternal:
+ mCanvasElement, mOffscreenCanvas)
+
+// -----------------------------
+
+} // namespace mozilla