gecko-dev/dom/canvas/ClientWebGLContext.cpp
Cosmin Sabou 680b4364c7 Backed out 13 changesets (bug 1690111) for causing fetch related crashes.
Backed out changeset 5f2c25d194ad (bug 1690111)
Backed out changeset 76c408bcd053 (bug 1690111)
Backed out changeset 6d0649fdafff (bug 1690111)
Backed out changeset c1330b5e8c43 (bug 1690111)
Backed out changeset 5fa36d8fd2a5 (bug 1690111)
Backed out changeset daf7d747853a (bug 1690111)
Backed out changeset f70e09a7f5c6 (bug 1690111)
Backed out changeset 40c6d6eed7f8 (bug 1690111)
Backed out changeset 692f2a759573 (bug 1690111)
Backed out changeset 7140866dd9f6 (bug 1690111)
Backed out changeset 2865fe682139 (bug 1690111)
Backed out changeset 9dcd2416f8a5 (bug 1690111)
Backed out changeset 9c411bf84079 (bug 1690111)
2023-09-11 17:55:24 +03:00

6894 lines
212 KiB
C++

/* -*- 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(),
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;
auto& info = child->GetFlushedCmdInfo();
// 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) {
const auto tooManyFlushes = 10;
// If there are many flushed cmds, force synchronous IPC to avoid too many
// pending ipc messages.
if (info.flushesSinceLastCongestionCheck > tooManyFlushes) {
needsSync = true;
}
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);
}
// Reset flushesSinceLastCongestionCheck
info.flushesSinceLastCongestionCheck = 0;
info.congestionCheckGeneration++;
return Some(layers::SurfaceDescriptorRemoteTexture(*textureId, *ownerId));
}
if (!child->SendGetFrontBuffer(fb ? fb->mId : 0, vr, &ret)) return {};
// Reset flushesSinceLastCongestionCheck
info.flushesSinceLastCongestionCheck = 0;
info.congestionCheckGeneration++;
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(RFPTarget::WebGLRenderCapability);
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");
}
// Clear RemoteTextureOwnerId. HostWebGLContext is going to be replaced in
// WebGLParent.
if (mRemoteTextureOwnerId.isSome()) {
mRemoteTextureOwnerId = Nothing();
}
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(RFPTarget::WebGLRenderCapability);
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(RFPTarget::WebGLRenderInfo)) {
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(RFPTarget::WebGLRenderInfo) ||
!StaticPrefs::webgl_enable_debug_renderer_info();
case WebGLExtensionID::WEBGL_debug_shaders:
return ShouldResistFingerprinting(RFPTarget::WebGLRenderInfo);
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