Bug 1795372 - Keepalive mNotLost within FuncScope. r=gfx-reviewers,lsalzman
[gecko.git] / dom / canvas / ClientWebGLContext.cpp
blobe7f31f9894b4173e0a9efa96af21d9511ba7d76a
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "ClientWebGLContext.h"
8 #include <bitset>
10 #include "ClientWebGLExtensions.h"
11 #include "Layers.h"
12 #include "gfxCrashReporterUtils.h"
13 #include "HostWebGLContext.h"
14 #include "js/PropertyAndElement.h" // JS_DefineElement
15 #include "js/ScalarType.h" // js::Scalar::Type
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/ToJSValue.h"
18 #include "mozilla/dom/WebGLContextEvent.h"
19 #include "mozilla/dom/WorkerCommon.h"
20 #include "mozilla/EnumeratedRange.h"
21 #include "mozilla/gfx/gfxVars.h"
22 #include "mozilla/gfx/CanvasManagerChild.h"
23 #include "mozilla/ipc/Shmem.h"
24 #include "mozilla/gfx/Swizzle.h"
25 #include "mozilla/layers/CompositorBridgeChild.h"
26 #include "mozilla/layers/ImageBridgeChild.h"
27 #include "mozilla/layers/OOPCanvasRenderer.h"
28 #include "mozilla/layers/TextureClientSharedSurface.h"
29 #include "mozilla/layers/WebRenderUserData.h"
30 #include "mozilla/layers/WebRenderCanvasRenderer.h"
31 #include "mozilla/ResultVariant.h"
32 #include "mozilla/ScopeExit.h"
33 #include "mozilla/StaticPrefs_webgl.h"
34 #include "nsContentUtils.h"
35 #include "nsDisplayList.h"
36 #include "TexUnpackBlob.h"
37 #include "WebGLMethodDispatcher.h"
38 #include "WebGLChild.h"
39 #include "WebGLValidateStrings.h"
41 namespace mozilla {
43 namespace webgl {
44 std::string SanitizeRenderer(const std::string&);
45 } // namespace webgl
47 // -
49 webgl::NotLostData::NotLostData(ClientWebGLContext& _context)
50 : context(_context) {}
52 webgl::NotLostData::~NotLostData() {
53 if (outOfProcess) {
54 Unused << dom::WebGLChild::Send__delete__(outOfProcess.get());
58 // -
60 bool webgl::ObjectJS::ValidateForContext(
61 const ClientWebGLContext& targetContext, const char* const argName) const {
62 if (!IsForContext(targetContext)) {
63 targetContext.EnqueueError(
64 LOCAL_GL_INVALID_OPERATION,
65 "`%s` is from a different (or lost) WebGL context.", argName);
66 return false;
68 return true;
71 void webgl::ObjectJS::WarnInvalidUse(const ClientWebGLContext& targetContext,
72 const char* const argName) const {
73 if (!ValidateForContext(targetContext, argName)) return;
75 const auto errEnum = ErrorOnDeleted();
76 targetContext.EnqueueError(errEnum, "Object `%s` is already deleted.",
77 argName);
80 static bool GetJSScalarFromGLType(GLenum type,
81 js::Scalar::Type* const out_scalarType) {
82 switch (type) {
83 case LOCAL_GL_BYTE:
84 *out_scalarType = js::Scalar::Int8;
85 return true;
87 case LOCAL_GL_UNSIGNED_BYTE:
88 *out_scalarType = js::Scalar::Uint8;
89 return true;
91 case LOCAL_GL_SHORT:
92 *out_scalarType = js::Scalar::Int16;
93 return true;
95 case LOCAL_GL_HALF_FLOAT:
96 case LOCAL_GL_HALF_FLOAT_OES:
97 case LOCAL_GL_UNSIGNED_SHORT:
98 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
99 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
100 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
101 *out_scalarType = js::Scalar::Uint16;
102 return true;
104 case LOCAL_GL_UNSIGNED_INT:
105 case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
106 case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
107 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
108 case LOCAL_GL_UNSIGNED_INT_24_8:
109 *out_scalarType = js::Scalar::Uint32;
110 return true;
111 case LOCAL_GL_INT:
112 *out_scalarType = js::Scalar::Int32;
113 return true;
115 case LOCAL_GL_FLOAT:
116 *out_scalarType = js::Scalar::Float32;
117 return true;
119 default:
120 return false;
124 ClientWebGLContext::ClientWebGLContext(const bool webgl2)
125 : mIsWebGL2(webgl2),
126 mExtLoseContext(new ClientWebGLExtensionLoseContext(*this)) {}
128 ClientWebGLContext::~ClientWebGLContext() { RemovePostRefreshObserver(); }
130 void ClientWebGLContext::JsWarning(const std::string& utf8) const {
131 nsIGlobalObject* global = nullptr;
132 if (mCanvasElement) {
133 mozilla::dom::Document* doc = mCanvasElement->OwnerDoc();
134 if (doc) {
135 global = doc->GetScopeObject();
137 } else if (mOffscreenCanvas) {
138 global = mOffscreenCanvas->GetOwnerGlobal();
141 dom::AutoJSAPI api;
142 if (!api.Init(global)) {
143 return;
145 const auto& cx = api.cx();
146 JS::WarnUTF8(cx, "%s", utf8.c_str());
149 void AutoJsWarning(const std::string& utf8) {
150 if (NS_IsMainThread()) {
151 const AutoJSContext cx;
152 JS::WarnUTF8(cx, "%s", utf8.c_str());
153 return;
156 JSContext* cx = dom::GetCurrentWorkerThreadJSContext();
157 if (NS_WARN_IF(!cx)) {
158 return;
161 JS::WarnUTF8(cx, "%s", utf8.c_str());
164 // ---------
166 bool ClientWebGLContext::DispatchEvent(const nsAString& eventName) const {
167 const auto kCanBubble = CanBubble::eYes;
168 const auto kIsCancelable = Cancelable::eYes;
169 bool useDefaultHandler = true;
171 if (mCanvasElement) {
172 nsContentUtils::DispatchTrustedEvent(
173 mCanvasElement->OwnerDoc(), static_cast<nsIContent*>(mCanvasElement),
174 eventName, kCanBubble, kIsCancelable, &useDefaultHandler);
175 } else if (mOffscreenCanvas) {
176 // OffscreenCanvas case
177 RefPtr<dom::Event> event =
178 new dom::Event(mOffscreenCanvas, nullptr, nullptr);
179 event->InitEvent(eventName, kCanBubble, kIsCancelable);
180 event->SetTrusted(true);
181 useDefaultHandler = mOffscreenCanvas->DispatchEvent(
182 *event, dom::CallerType::System, IgnoreErrors());
184 return useDefaultHandler;
187 // -
189 void ClientWebGLContext::EmulateLoseContext() const {
190 const FuncScope funcScope(*this, "loseContext");
191 if (mLossStatus != webgl::LossStatus::Ready) {
192 JsWarning("loseContext: Already lost.");
193 if (!mNextError) {
194 mNextError = LOCAL_GL_INVALID_OPERATION;
196 return;
198 OnContextLoss(webgl::ContextLossReason::Manual);
201 void ClientWebGLContext::OnContextLoss(
202 const webgl::ContextLossReason reason) const {
203 JsWarning("WebGL context was lost.");
205 if (mNotLost) {
206 for (const auto& ext : mNotLost->extensions) {
207 if (!ext) continue;
208 ext->mContext = nullptr; // Detach.
210 mNotLost = {}; // Lost now!
211 mNextError = LOCAL_GL_CONTEXT_LOST_WEBGL;
214 switch (reason) {
215 case webgl::ContextLossReason::Guilty:
216 mLossStatus = webgl::LossStatus::LostForever;
217 break;
219 case webgl::ContextLossReason::None:
220 mLossStatus = webgl::LossStatus::Lost;
221 break;
223 case webgl::ContextLossReason::Manual:
224 mLossStatus = webgl::LossStatus::LostManually;
225 break;
228 const auto weak = WeakPtr<const ClientWebGLContext>(this);
229 const auto fnRun = [weak]() {
230 const auto strong = RefPtr<const ClientWebGLContext>(weak);
231 if (!strong) return;
232 strong->Event_webglcontextlost();
234 already_AddRefed<mozilla::CancelableRunnable> runnable =
235 NS_NewCancelableRunnableFunction("enqueue Event_webglcontextlost", fnRun);
236 NS_DispatchToCurrentThread(std::move(runnable));
239 void ClientWebGLContext::Event_webglcontextlost() const {
240 const bool useDefaultHandler = DispatchEvent(u"webglcontextlost"_ns);
241 if (useDefaultHandler) {
242 mLossStatus = webgl::LossStatus::LostForever;
245 if (mLossStatus == webgl::LossStatus::Lost) {
246 RestoreContext(webgl::LossStatus::Lost);
250 void ClientWebGLContext::RestoreContext(
251 const webgl::LossStatus requiredStatus) const {
252 if (requiredStatus != mLossStatus) {
253 JsWarning(
254 "restoreContext: Only valid iff context lost with loseContext().");
255 if (!mNextError) {
256 mNextError = LOCAL_GL_INVALID_OPERATION;
258 return;
260 MOZ_RELEASE_ASSERT(mLossStatus == webgl::LossStatus::Lost ||
261 mLossStatus == webgl::LossStatus::LostManually);
263 if (mAwaitingRestore) return;
264 mAwaitingRestore = true;
266 const auto weak = WeakPtr<const ClientWebGLContext>(this);
267 const auto fnRun = [weak]() {
268 const auto strong = RefPtr<const ClientWebGLContext>(weak);
269 if (!strong) return;
270 strong->Event_webglcontextrestored();
272 already_AddRefed<mozilla::CancelableRunnable> runnable =
273 NS_NewCancelableRunnableFunction("enqueue Event_webglcontextrestored",
274 fnRun);
275 NS_DispatchToCurrentThread(std::move(runnable));
278 void ClientWebGLContext::Event_webglcontextrestored() const {
279 mAwaitingRestore = false;
280 mLossStatus = webgl::LossStatus::Ready;
281 mNextError = 0;
283 uvec2 requestSize;
284 if (mCanvasElement) {
285 requestSize = {mCanvasElement->Width(), mCanvasElement->Height()};
286 } else if (mOffscreenCanvas) {
287 requestSize = {mOffscreenCanvas->Width(), mOffscreenCanvas->Height()};
288 } else {
289 MOZ_ASSERT_UNREACHABLE("no HTMLCanvasElement or OffscreenCanvas!");
290 return;
293 if (!requestSize.x) {
294 requestSize.x = 1;
296 if (!requestSize.y) {
297 requestSize.y = 1;
300 const auto mutThis = const_cast<ClientWebGLContext*>(
301 this); // TODO: Make context loss non-mutable.
302 if (!mutThis->CreateHostContext(requestSize)) {
303 mLossStatus = webgl::LossStatus::LostForever;
304 return;
307 (void)DispatchEvent(u"webglcontextrestored"_ns);
310 // ---------
312 void ClientWebGLContext::ThrowEvent_WebGLContextCreationError(
313 const std::string& text) const {
314 nsCString msg;
315 msg.AppendPrintf("Failed to create WebGL context: %s", text.c_str());
316 JsWarning(msg.BeginReading());
318 RefPtr<dom::EventTarget> target = mCanvasElement;
319 if (!target && mOffscreenCanvas) {
320 target = mOffscreenCanvas;
321 } else if (!target) {
322 return;
325 const auto kEventName = u"webglcontextcreationerror"_ns;
327 dom::WebGLContextEventInit eventInit;
328 // eventInit.mCancelable = true; // The spec says this, but it's silly.
329 eventInit.mStatusMessage = NS_ConvertASCIItoUTF16(text.c_str());
331 const RefPtr<dom::WebGLContextEvent> event =
332 dom::WebGLContextEvent::Constructor(target, kEventName, eventInit);
333 event->SetTrusted(true);
335 target->DispatchEvent(*event);
338 // -
340 // If we are running WebGL in this process then call the HostWebGLContext
341 // method directly. Otherwise, dispatch over IPC.
342 template <typename MethodType, MethodType method, typename... Args>
343 void ClientWebGLContext::Run(Args&&... args) const {
344 const auto notLost =
345 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
346 if (IsContextLost()) return;
348 const auto& inProcess = notLost->inProcess;
349 if (inProcess) {
350 return (inProcess.get()->*method)(std::forward<Args>(args)...);
353 const auto& child = notLost->outOfProcess;
355 const auto id = IdByMethod<MethodType, method>();
357 const auto size = webgl::SerializedSize(id, args...);
358 const auto maybeDest = child->AllocPendingCmdBytes(size);
359 if (!maybeDest) {
360 JsWarning("Failed to allocate internal command buffer.");
361 OnContextLoss(webgl::ContextLossReason::None);
362 return;
364 const auto& destBytes = *maybeDest;
365 webgl::Serialize(destBytes, id, args...);
368 // -------------------------------------------------------------------------
369 // Client-side helper methods. Dispatch to a Host method.
370 // -------------------------------------------------------------------------
372 #define RPROC(_METHOD) \
373 decltype(&HostWebGLContext::_METHOD), &HostWebGLContext::_METHOD
375 // ------------------------- Composition, etc -------------------------
377 void ClientWebGLContext::OnBeforePaintTransaction() { Present(nullptr); }
379 void ClientWebGLContext::EndComposition() {
380 // Mark ourselves as no longer invalidated.
381 MarkContextClean();
384 // -
386 layers::TextureType ClientWebGLContext::GetTexTypeForSwapChain() const {
387 const RefPtr<layers::ImageBridgeChild> imageBridge =
388 layers::ImageBridgeChild::GetSingleton();
389 return layers::TexTypeForWebgl(imageBridge);
392 void ClientWebGLContext::Present(WebGLFramebufferJS* const xrFb,
393 const bool webvr,
394 const webgl::SwapChainOptions& options) {
395 const auto texType = GetTexTypeForSwapChain();
396 Present(xrFb, texType, webvr, options);
399 // Fill in remote texture ids to SwapChainOptions if async present is enabled.
400 webgl::SwapChainOptions ClientWebGLContext::PrepareAsyncSwapChainOptions(
401 WebGLFramebufferJS* fb, bool webvr,
402 const webgl::SwapChainOptions& options) {
403 // Currently remote texture ids should only be set internally.
404 MOZ_ASSERT(!options.remoteTextureOwnerId.IsValid() &&
405 !options.remoteTextureId.IsValid());
406 auto& ownerId = fb ? fb->mRemoteTextureOwnerId : mRemoteTextureOwnerId;
407 auto& textureId = fb ? fb->mLastRemoteTextureId : mLastRemoteTextureId;
408 // Async present only works when out-of-process. It is not supported in WebVR.
409 // Allow it if it is either forced or if the pref is set.
410 if (!IsContextLost() && !mNotLost->inProcess && !webvr &&
411 (options.forceAsyncPresent ||
412 StaticPrefs::webgl_out_of_process_async_present())) {
413 if (!ownerId) {
414 ownerId = Some(layers::RemoteTextureOwnerId::GetNext());
416 textureId = Some(layers::RemoteTextureId::GetNext());
417 webgl::SwapChainOptions asyncOptions = options;
418 asyncOptions.remoteTextureOwnerId = *ownerId;
419 asyncOptions.remoteTextureId = *textureId;
420 return asyncOptions;
422 // Clear the current remote texture id so that we disable async.
423 textureId = Nothing();
424 return options;
427 void ClientWebGLContext::Present(WebGLFramebufferJS* const xrFb,
428 const layers::TextureType type,
429 const bool webvr,
430 const webgl::SwapChainOptions& options) {
431 if (!mIsCanvasDirty && !xrFb) return;
432 if (!xrFb) {
433 mIsCanvasDirty = false;
435 CancelAutoFlush();
436 webgl::SwapChainOptions asyncOptions =
437 PrepareAsyncSwapChainOptions(xrFb, webvr, options);
438 Run<RPROC(Present)>(xrFb ? xrFb->mId : 0, type, webvr, asyncOptions);
441 void ClientWebGLContext::CopyToSwapChain(
442 WebGLFramebufferJS* const fb, const webgl::SwapChainOptions& options) {
443 CancelAutoFlush();
444 const auto texType = GetTexTypeForSwapChain();
445 webgl::SwapChainOptions asyncOptions =
446 PrepareAsyncSwapChainOptions(fb, false, options);
447 Run<RPROC(CopyToSwapChain)>(fb ? fb->mId : 0, texType, asyncOptions);
450 void ClientWebGLContext::EndOfFrame() {
451 CancelAutoFlush();
452 Run<RPROC(EndOfFrame)>();
455 Maybe<layers::SurfaceDescriptor> ClientWebGLContext::GetFrontBuffer(
456 WebGLFramebufferJS* const fb, bool vr) {
457 const auto notLost = mNotLost;
458 if (IsContextLost()) return {};
460 const auto& inProcess = mNotLost->inProcess;
461 if (inProcess) {
462 return inProcess->GetFrontBuffer(fb ? fb->mId : 0, vr);
465 const auto& child = mNotLost->outOfProcess;
466 child->FlushPendingCmds();
468 Maybe<layers::SurfaceDescriptor> ret;
470 // If valid remote texture data was set for async present, then use it.
471 const auto& ownerId = fb ? fb->mRemoteTextureOwnerId : mRemoteTextureOwnerId;
472 const auto& textureId = fb ? fb->mLastRemoteTextureId : mLastRemoteTextureId;
473 if (ownerId && textureId) {
474 if (StaticPrefs::webgl_out_of_process_async_present_force_sync()) {
475 // Request the front buffer from IPDL to cause a sync, even though we
476 // will continue to use the remote texture descriptor after.
477 (void)child->SendGetFrontBuffer(fb ? fb->mId : 0, vr, &ret);
479 return Some(layers::SurfaceDescriptorRemoteTexture(*textureId, *ownerId));
482 if (!child->SendGetFrontBuffer(fb ? fb->mId : 0, vr, &ret)) return {};
484 return ret;
487 Maybe<layers::SurfaceDescriptor> ClientWebGLContext::PresentFrontBuffer(
488 WebGLFramebufferJS* const fb, const layers::TextureType type, bool webvr) {
489 Present(fb, type, webvr);
490 return GetFrontBuffer(fb, webvr);
493 void ClientWebGLContext::ClearVRSwapChain() { Run<RPROC(ClearVRSwapChain)>(); }
495 // -
497 bool ClientWebGLContext::UpdateWebRenderCanvasData(
498 nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
499 CanvasRenderer* renderer = aCanvasData->GetCanvasRenderer();
501 if (!mResetLayer && renderer) {
502 return true;
505 const auto& size = DrawingBufferSize();
507 if (!IsContextLost() && !renderer && mNotLost->mCanvasRenderer &&
508 mNotLost->mCanvasRenderer->GetSize() == gfx::IntSize(size.x, size.y) &&
509 aCanvasData->SetCanvasRenderer(mNotLost->mCanvasRenderer)) {
510 mNotLost->mCanvasRenderer->SetDirty();
511 mResetLayer = false;
512 return true;
515 renderer = aCanvasData->CreateCanvasRenderer();
516 if (!InitializeCanvasRenderer(aBuilder, renderer)) {
517 // Clear CanvasRenderer of WebRenderCanvasData
518 aCanvasData->ClearCanvasRenderer();
519 return false;
522 mNotLost->mCanvasRenderer = renderer;
524 MOZ_ASSERT(renderer);
525 mResetLayer = false;
527 return true;
530 bool ClientWebGLContext::InitializeCanvasRenderer(
531 nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) {
532 const FuncScope funcScope(*this, "<InitializeCanvasRenderer>");
533 if (IsContextLost()) return false;
535 layers::CanvasRendererData data;
536 data.mContext = this;
537 data.mOriginPos = gl::OriginPos::BottomLeft;
539 const auto& options = *mInitialOptions;
540 const auto& size = DrawingBufferSize();
542 if (IsContextLost()) return false;
544 data.mIsOpaque = !options.alpha;
545 data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
546 data.mSize = {size.x, size.y};
548 if (aBuilder->IsPaintingToWindow() && mCanvasElement) {
549 data.mDoPaintCallbacks = true;
552 aRenderer->Initialize(data);
553 aRenderer->SetDirty();
554 return true;
557 void ClientWebGLContext::UpdateCanvasParameters() {
558 if (!mOffscreenCanvas) {
559 return;
562 const auto& options = *mInitialOptions;
563 const auto& size = DrawingBufferSize();
565 mozilla::dom::OffscreenCanvasDisplayData data;
566 data.mOriginPos = gl::OriginPos::BottomLeft;
567 data.mIsOpaque = !options.alpha;
568 data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
569 data.mSize = {size.x, size.y};
570 data.mDoPaintCallbacks = false;
572 mOffscreenCanvas->UpdateDisplayData(data);
575 layers::LayersBackend ClientWebGLContext::GetCompositorBackendType() const {
576 if (mCanvasElement) {
577 return mCanvasElement->GetCompositorBackendType();
578 } else if (mOffscreenCanvas) {
579 return mOffscreenCanvas->GetCompositorBackendType();
582 return layers::LayersBackend::LAYERS_NONE;
585 mozilla::dom::Document* ClientWebGLContext::GetOwnerDoc() const {
586 MOZ_ASSERT(mCanvasElement);
587 if (!mCanvasElement) {
588 return nullptr;
590 return mCanvasElement->OwnerDoc();
593 void ClientWebGLContext::Commit() {
594 if (mOffscreenCanvas) {
595 mOffscreenCanvas->CommitFrameToCompositor();
599 void ClientWebGLContext::GetCanvas(
600 dom::Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& retval) {
601 if (mCanvasElement) {
602 MOZ_RELEASE_ASSERT(!mOffscreenCanvas, "GFX: Canvas is offscreen.");
604 if (mCanvasElement->IsInNativeAnonymousSubtree()) {
605 retval.SetNull();
606 } else {
607 retval.SetValue().SetAsHTMLCanvasElement() = mCanvasElement;
609 } else if (mOffscreenCanvas) {
610 retval.SetValue().SetAsOffscreenCanvas() = mOffscreenCanvas;
611 } else {
612 retval.SetNull();
616 void ClientWebGLContext::GetContextAttributes(
617 dom::Nullable<dom::WebGLContextAttributes>& retval) {
618 retval.SetNull();
619 const FuncScope funcScope(*this, "getContextAttributes");
620 if (IsContextLost()) return;
622 dom::WebGLContextAttributes& result = retval.SetValue();
624 const auto& options = mNotLost->info.options;
626 result.mAlpha.Construct(options.alpha);
627 result.mDepth = options.depth;
628 result.mStencil = options.stencil;
629 result.mAntialias.Construct(options.antialias);
630 result.mPremultipliedAlpha = options.premultipliedAlpha;
631 result.mPreserveDrawingBuffer = options.preserveDrawingBuffer;
632 result.mFailIfMajorPerformanceCaveat = options.failIfMajorPerformanceCaveat;
633 result.mPowerPreference = options.powerPreference;
636 // -----------------------
638 NS_IMETHODIMP
639 ClientWebGLContext::SetDimensions(const int32_t signedWidth,
640 const int32_t signedHeight) {
641 const FuncScope funcScope(*this, "<SetDimensions>");
642 MOZ_ASSERT(mInitialOptions);
644 if (mLossStatus != webgl::LossStatus::Ready) {
645 // Attempted resize of a lost context.
646 return NS_OK;
649 uvec2 size = {static_cast<uint32_t>(signedWidth),
650 static_cast<uint32_t>(signedHeight)};
651 if (!size.x) {
652 size.x = 1;
654 if (!size.y) {
655 size.y = 1;
657 const auto prevRequestedSize = mRequestedSize;
658 mRequestedSize = size;
660 mResetLayer = true; // Always treat this as resize.
662 if (mNotLost) {
663 auto& state = State();
665 auto curSize = prevRequestedSize;
666 if (state.mDrawingBufferSize) {
667 curSize = *state.mDrawingBufferSize;
669 if (size == curSize) return NS_OK; // MUST skip no-op resize
671 state.mDrawingBufferSize = Nothing();
672 Run<RPROC(Resize)>(size);
674 UpdateCanvasParameters();
675 MarkCanvasDirty();
676 return NS_OK;
679 // -
680 // Context (re-)creation
682 if (!CreateHostContext(size)) {
683 return NS_ERROR_FAILURE;
685 return NS_OK;
688 static bool IsWebglOutOfProcessEnabled() {
689 if (StaticPrefs::webgl_out_of_process_force()) {
690 return true;
692 if (!gfx::gfxVars::AllowWebglOop()) {
693 return false;
695 if (!NS_IsMainThread()) {
696 return StaticPrefs::webgl_out_of_process_worker();
698 return StaticPrefs::webgl_out_of_process();
701 static inline bool StartsWith(const std::string& haystack,
702 const std::string& needle) {
703 return haystack.find(needle) == 0;
706 bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
707 const auto pNotLost = std::make_shared<webgl::NotLostData>(*this);
708 auto& notLost = *pNotLost;
710 auto res = [&]() -> Result<Ok, std::string> {
711 auto options = *mInitialOptions;
712 if (StaticPrefs::webgl_disable_fail_if_major_performance_caveat()) {
713 options.failIfMajorPerformanceCaveat = false;
716 if (options.failIfMajorPerformanceCaveat) {
717 const auto backend = GetCompositorBackendType();
718 bool isCompositorSlow = false;
719 isCompositorSlow |= (backend == layers::LayersBackend::LAYERS_WR &&
720 gfx::gfxVars::UseSoftwareWebRender());
722 if (isCompositorSlow) {
723 return Err(
724 "failIfMajorPerformanceCaveat: Compositor is not"
725 " hardware-accelerated.");
729 const bool resistFingerprinting = ShouldResistFingerprinting();
730 const auto principalKey = GetPrincipalHashValue();
731 const auto initDesc = webgl::InitContextDesc{
732 mIsWebGL2, resistFingerprinting, requestedSize, options, principalKey};
734 // -
736 auto useOop = IsWebglOutOfProcessEnabled();
737 if (XRE_IsParentProcess()) {
738 useOop = false;
741 if (!useOop) {
742 notLost.inProcess =
743 HostWebGLContext::Create({this, nullptr}, initDesc, &notLost.info);
744 return Ok();
747 // -
749 ScopedGfxFeatureReporter reporter("IpcWebGL");
751 auto* const cm = gfx::CanvasManagerChild::Get();
752 if (NS_WARN_IF(!cm)) {
753 return Err("!CanvasManagerChild::Get()");
756 RefPtr<dom::WebGLChild> outOfProcess = new dom::WebGLChild(*this);
757 outOfProcess =
758 static_cast<dom::WebGLChild*>(cm->SendPWebGLConstructor(outOfProcess));
759 if (!outOfProcess) {
760 return Err("SendPWebGLConstructor failed");
763 if (!outOfProcess->SendInitialize(initDesc, &notLost.info)) {
764 return Err("WebGL actor Initialize failed");
767 notLost.outOfProcess = outOfProcess;
768 reporter.SetSuccessful();
769 return Ok();
770 }();
771 if (!res.isOk()) {
772 auto str = res.unwrapErr();
773 if (StartsWith(str, "failIfMajorPerformanceCaveat")) {
774 str +=
775 " (about:config override available:"
776 " webgl.disable-fail-if-major-performance-caveat)";
778 notLost.info.error = str;
780 if (!notLost.info.error.empty()) {
781 ThrowEvent_WebGLContextCreationError(notLost.info.error);
782 return false;
784 mNotLost = pNotLost;
785 UpdateCanvasParameters();
786 MarkCanvasDirty();
788 // Init state
789 const auto& limits = Limits();
790 auto& state = State();
791 state.mDefaultTfo = new WebGLTransformFeedbackJS(*this);
792 state.mDefaultVao = new WebGLVertexArrayJS(*this);
794 state.mBoundTfo = state.mDefaultTfo;
795 state.mBoundVao = state.mDefaultVao;
797 (void)state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER];
799 state.mTexUnits.resize(limits.maxTexUnits);
800 state.mBoundUbos.resize(limits.maxUniformBufferBindings);
803 webgl::TypedQuad initVal;
804 const float fData[4] = {0, 0, 0, 1};
805 memcpy(initVal.data.data(), fData, initVal.data.size());
806 state.mGenericVertexAttribs.resize(limits.maxVertexAttribs, initVal);
809 const auto& size = DrawingBufferSize();
810 state.mViewport = {0, 0, static_cast<int32_t>(size.x),
811 static_cast<int32_t>(size.y)};
812 state.mScissor = state.mViewport;
814 if (mIsWebGL2) {
815 // Insert keys to enable slots:
816 (void)state.mBoundBufferByTarget[LOCAL_GL_COPY_READ_BUFFER];
817 (void)state.mBoundBufferByTarget[LOCAL_GL_COPY_WRITE_BUFFER];
818 (void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_PACK_BUFFER];
819 (void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_UNPACK_BUFFER];
820 (void)state.mBoundBufferByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER];
821 (void)state.mBoundBufferByTarget[LOCAL_GL_UNIFORM_BUFFER];
823 (void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED];
824 //(void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE];
825 //// Same slot as ANY_SAMPLES_PASSED.
826 (void)state
827 .mCurrentQueryByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN];
830 return true;
833 // -------
835 uvec2 ClientWebGLContext::DrawingBufferSize() {
836 if (IsContextLost()) return {};
837 const auto notLost =
838 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
839 auto& state = State();
840 auto& size = state.mDrawingBufferSize;
842 if (!size) {
843 const auto& inProcess = mNotLost->inProcess;
844 if (inProcess) {
845 size = Some(inProcess->DrawingBufferSize());
846 } else {
847 const auto& child = mNotLost->outOfProcess;
848 child->FlushPendingCmds();
849 uvec2 actual = {};
850 if (!child->SendDrawingBufferSize(&actual)) return {};
851 size = Some(actual);
855 return *size;
858 void ClientWebGLContext::OnMemoryPressure() {
859 if (IsContextLost()) return;
861 const auto& inProcess = mNotLost->inProcess;
862 if (inProcess) {
863 return inProcess->OnMemoryPressure();
865 const auto& child = mNotLost->outOfProcess;
866 (void)child->SendOnMemoryPressure();
869 NS_IMETHODIMP
870 ClientWebGLContext::SetContextOptions(JSContext* cx,
871 JS::Handle<JS::Value> options,
872 ErrorResult& aRvForDictionaryInit) {
873 if (mInitialOptions && options.isNullOrUndefined()) return NS_OK;
875 dom::WebGLContextAttributes attributes;
876 if (!attributes.Init(cx, options)) {
877 aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED);
878 return NS_ERROR_UNEXPECTED;
881 WebGLContextOptions newOpts;
883 newOpts.stencil = attributes.mStencil;
884 newOpts.depth = attributes.mDepth;
885 newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha;
886 newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer;
887 newOpts.failIfMajorPerformanceCaveat =
888 attributes.mFailIfMajorPerformanceCaveat;
889 newOpts.xrCompatible = attributes.mXrCompatible;
890 newOpts.powerPreference = attributes.mPowerPreference;
891 newOpts.enableDebugRendererInfo =
892 StaticPrefs::webgl_enable_debug_renderer_info();
893 MOZ_ASSERT(mCanvasElement || mOffscreenCanvas);
894 newOpts.shouldResistFingerprinting = ShouldResistFingerprinting();
896 if (attributes.mAlpha.WasPassed()) {
897 newOpts.alpha = attributes.mAlpha.Value();
899 if (attributes.mAntialias.WasPassed()) {
900 newOpts.antialias = attributes.mAntialias.Value();
902 newOpts.ignoreColorSpace = true;
903 if (attributes.mColorSpace.WasPassed()) {
904 newOpts.ignoreColorSpace = false;
905 newOpts.colorSpace = attributes.mColorSpace.Value();
908 // Don't do antialiasing if we've disabled MSAA.
909 if (!StaticPrefs::webgl_msaa_samples()) {
910 newOpts.antialias = false;
913 // -
915 if (mInitialOptions && *mInitialOptions != newOpts) {
916 // Err if the options asked for aren't the same as what they were
917 // originally.
918 return NS_ERROR_FAILURE;
921 mXRCompatible = attributes.mXrCompatible;
923 mInitialOptions.emplace(newOpts);
924 return NS_OK;
927 void ClientWebGLContext::DidRefresh() { Run<RPROC(DidRefresh)>(); }
929 already_AddRefed<gfx::SourceSurface> ClientWebGLContext::GetSurfaceSnapshot(
930 gfxAlphaType* const out_alphaType) {
931 const FuncScope funcScope(*this, "<GetSurfaceSnapshot>");
932 if (IsContextLost()) return nullptr;
933 const auto notLost =
934 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
936 auto ret = BackBufferSnapshot();
937 if (!ret) return nullptr;
939 // -
941 const auto& options = mNotLost->info.options;
943 auto srcAlphaType = gfxAlphaType::Opaque;
944 if (options.alpha) {
945 if (options.premultipliedAlpha) {
946 srcAlphaType = gfxAlphaType::Premult;
947 } else {
948 srcAlphaType = gfxAlphaType::NonPremult;
952 if (out_alphaType) {
953 *out_alphaType = srcAlphaType;
954 } else {
955 // Expects Opaque or Premult
956 if (srcAlphaType == gfxAlphaType::NonPremult) {
957 const gfx::DataSourceSurface::ScopedMap map(
958 ret, gfx::DataSourceSurface::READ_WRITE);
959 MOZ_RELEASE_ASSERT(map.IsMapped(), "Failed to map snapshot surface!");
961 const auto& size = ret->GetSize();
962 const auto format = ret->GetFormat();
963 bool rv =
964 gfx::PremultiplyData(map.GetData(), map.GetStride(), format,
965 map.GetData(), map.GetStride(), format, size);
966 MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
970 return ret.forget();
973 RefPtr<gfx::SourceSurface> ClientWebGLContext::GetFrontBufferSnapshot(
974 const bool requireAlphaPremult) {
975 const FuncScope funcScope(*this, "<GetSurfaceSnapshot>");
976 if (IsContextLost()) return nullptr;
977 const auto notLost =
978 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
980 const auto& options = mNotLost->info.options;
982 const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
983 : gfx::SurfaceFormat::B8G8R8X8;
985 const auto fnNewSurf = [&](const uvec2 size) {
986 const auto stride = size.x * 4;
987 return RefPtr<gfx::DataSourceSurface>(
988 gfx::Factory::CreateDataSourceSurfaceWithStride({size.x, size.y},
989 surfFormat, stride,
990 /*zero=*/true));
993 const auto& inProcess = mNotLost->inProcess;
994 if (inProcess) {
995 const auto maybeSize = inProcess->FrontBufferSnapshotInto({});
996 if (!maybeSize) return nullptr;
997 const auto& surfSize = *maybeSize;
998 const auto stride = surfSize.x * 4;
999 const auto byteSize = stride * surfSize.y;
1000 const auto surf = fnNewSurf(surfSize);
1001 if (!surf) return nullptr;
1003 const gfx::DataSourceSurface::ScopedMap map(
1004 surf, gfx::DataSourceSurface::READ_WRITE);
1005 if (!map.IsMapped()) {
1006 MOZ_ASSERT(false);
1007 return nullptr;
1009 MOZ_RELEASE_ASSERT(map.GetStride() == static_cast<int64_t>(stride));
1010 auto range = Range<uint8_t>{map.GetData(), byteSize};
1011 if (!inProcess->FrontBufferSnapshotInto(Some(range))) {
1012 gfxCriticalNote << "ClientWebGLContext::GetFrontBufferSnapshot: "
1013 "FrontBufferSnapshotInto(some) failed after "
1014 "FrontBufferSnapshotInto(none)";
1015 return nullptr;
1017 if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
1018 bool rv = gfx::PremultiplyData(
1019 map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
1020 map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1021 surf->GetSize());
1022 MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
1023 } else {
1024 bool rv = gfx::SwizzleData(
1025 map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
1026 map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1027 surf->GetSize());
1028 MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
1031 return surf;
1033 const auto& child = mNotLost->outOfProcess;
1034 child->FlushPendingCmds();
1035 webgl::FrontBufferSnapshotIpc res;
1036 if (!child->SendGetFrontBufferSnapshot(&res)) {
1037 res = {};
1039 if (!res.shmem) return nullptr;
1041 const auto& surfSize = res.surfSize;
1042 const webgl::RaiiShmem shmem{child, res.shmem.ref()};
1043 if (!shmem) return nullptr;
1044 const auto& shmemBytes = shmem.ByteRange();
1045 if (!surfSize.x) return nullptr; // Zero means failure.
1047 const auto stride = surfSize.x * 4;
1048 const auto byteSize = stride * surfSize.y;
1050 const auto surf = fnNewSurf(surfSize);
1051 if (!surf) return nullptr;
1054 const gfx::DataSourceSurface::ScopedMap map(
1055 surf, gfx::DataSourceSurface::READ_WRITE);
1056 if (!map.IsMapped()) {
1057 MOZ_ASSERT(false);
1058 return nullptr;
1060 MOZ_RELEASE_ASSERT(shmemBytes.length() == byteSize);
1061 if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
1062 bool rv = gfx::PremultiplyData(
1063 shmemBytes.begin().get(), stride, gfx::SurfaceFormat::R8G8B8A8,
1064 map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1065 surf->GetSize());
1066 MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
1067 } else {
1068 bool rv = gfx::SwizzleData(shmemBytes.begin().get(), stride,
1069 gfx::SurfaceFormat::R8G8B8A8, map.GetData(),
1070 map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1071 surf->GetSize());
1072 MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
1075 return surf;
1078 RefPtr<gfx::DataSourceSurface> ClientWebGLContext::BackBufferSnapshot() {
1079 if (IsContextLost()) return nullptr;
1080 const auto notLost =
1081 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
1083 const auto& options = mNotLost->info.options;
1084 const auto& state = State();
1086 const auto drawFbWas = state.mBoundDrawFb;
1087 const auto readFbWas = state.mBoundReadFb;
1088 const auto pboWas =
1089 Find(state.mBoundBufferByTarget, LOCAL_GL_PIXEL_PACK_BUFFER);
1091 const auto size = DrawingBufferSize();
1093 // -
1095 BindFramebuffer(LOCAL_GL_FRAMEBUFFER, nullptr);
1096 if (pboWas) {
1097 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
1100 auto reset = MakeScopeExit([&] {
1101 if (drawFbWas == readFbWas) {
1102 BindFramebuffer(LOCAL_GL_FRAMEBUFFER, drawFbWas);
1103 } else {
1104 BindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, drawFbWas);
1105 BindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, readFbWas);
1107 if (pboWas) {
1108 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
1112 const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
1113 : gfx::SurfaceFormat::B8G8R8X8;
1114 const auto stride = size.x * 4;
1115 RefPtr<gfx::DataSourceSurface> surf =
1116 gfx::Factory::CreateDataSourceSurfaceWithStride(
1117 {size.x, size.y}, surfFormat, stride, /*zero=*/true);
1118 if (NS_WARN_IF(!surf)) {
1119 // Was this an OOM or alloc-limit? (500MB is our default resource size
1120 // limit)
1121 surf = gfx::Factory::CreateDataSourceSurfaceWithStride({1, 1}, surfFormat,
1122 4, /*zero=*/true);
1123 if (!surf) {
1124 // Still failed for a 1x1 size.
1125 gfxCriticalError() << "CreateDataSourceSurfaceWithStride(surfFormat="
1126 << surfFormat << ") failed.";
1128 return nullptr;
1132 const gfx::DataSourceSurface::ScopedMap map(
1133 surf, gfx::DataSourceSurface::READ_WRITE);
1134 if (!map.IsMapped()) {
1135 MOZ_ASSERT(false);
1136 return nullptr;
1138 MOZ_ASSERT(static_cast<uint32_t>(map.GetStride()) == stride);
1140 const auto desc = webgl::ReadPixelsDesc{{0, 0}, size};
1141 const auto range = Range<uint8_t>(map.GetData(), stride * size.y);
1142 if (!DoReadPixels(desc, range)) return nullptr;
1144 const auto begin = range.begin().get();
1146 std::vector<uint8_t> temp;
1147 temp.resize(stride);
1148 for (const auto i : IntegerRange(size.y / 2)) {
1149 const auto top = begin + stride * i;
1150 const auto bottom = begin + stride * (size.y - 1 - i);
1151 memcpy(temp.data(), top, stride);
1152 memcpy(top, bottom, stride);
1153 gfxUtils::ConvertBGRAtoRGBA(top, stride);
1155 memcpy(bottom, temp.data(), stride);
1156 gfxUtils::ConvertBGRAtoRGBA(bottom, stride);
1159 if (size.y % 2) {
1160 const auto middle = begin + stride * (size.y / 2);
1161 gfxUtils::ConvertBGRAtoRGBA(middle, stride);
1165 return surf;
1168 UniquePtr<uint8_t[]> ClientWebGLContext::GetImageBuffer(int32_t* out_format) {
1169 *out_format = 0;
1171 // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
1172 gfxAlphaType any;
1173 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
1174 if (!snapshot) return nullptr;
1176 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
1178 const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;
1179 return gfxUtils::GetImageBuffer(dataSurface, premultAlpha, out_format);
1182 NS_IMETHODIMP
1183 ClientWebGLContext::GetInputStream(const char* mimeType,
1184 const nsAString& encoderOptions,
1185 nsIInputStream** out_stream) {
1186 // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
1187 gfxAlphaType any;
1188 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
1189 if (!snapshot) return NS_ERROR_FAILURE;
1191 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
1192 const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;
1193 return gfxUtils::GetInputStream(dataSurface, premultAlpha, mimeType,
1194 encoderOptions, out_stream);
1197 // ------------------------- Client WebGL Objects -------------------------
1198 // ------------------------- Create/Destroy/Is -------------------------
1200 template <typename T>
1201 static already_AddRefed<T> AsAddRefed(T* ptr) {
1202 RefPtr<T> rp = ptr;
1203 return rp.forget();
1206 template <typename T>
1207 static RefPtr<T> AsRefPtr(T* ptr) {
1208 return {ptr};
1211 already_AddRefed<WebGLBufferJS> ClientWebGLContext::CreateBuffer() const {
1212 const FuncScope funcScope(*this, "createBuffer");
1213 if (IsContextLost()) return nullptr;
1215 auto ret = AsRefPtr(new WebGLBufferJS(*this));
1216 Run<RPROC(CreateBuffer)>(ret->mId);
1217 return ret.forget();
1220 already_AddRefed<WebGLFramebufferJS> ClientWebGLContext::CreateFramebuffer()
1221 const {
1222 const FuncScope funcScope(*this, "createFramebuffer");
1223 if (IsContextLost()) return nullptr;
1225 auto ret = AsRefPtr(new WebGLFramebufferJS(*this));
1226 Run<RPROC(CreateFramebuffer)>(ret->mId);
1227 return ret.forget();
1230 already_AddRefed<WebGLFramebufferJS>
1231 ClientWebGLContext::CreateOpaqueFramebuffer(
1232 const webgl::OpaqueFramebufferOptions& options) const {
1233 const FuncScope funcScope(*this, "createOpaqueFramebuffer");
1234 if (IsContextLost()) return nullptr;
1236 auto ret = AsRefPtr(new WebGLFramebufferJS(*this, true));
1238 const auto& inProcess = mNotLost->inProcess;
1239 if (inProcess) {
1240 if (!inProcess->CreateOpaqueFramebuffer(ret->mId, options)) {
1241 ret = nullptr;
1243 return ret.forget();
1245 const auto& child = mNotLost->outOfProcess;
1246 child->FlushPendingCmds();
1247 bool ok = false;
1248 if (!child->SendCreateOpaqueFramebuffer(ret->mId, options, &ok))
1249 return nullptr;
1250 if (!ok) return nullptr;
1251 return ret.forget();
1254 already_AddRefed<WebGLProgramJS> ClientWebGLContext::CreateProgram() const {
1255 const FuncScope funcScope(*this, "createProgram");
1256 if (IsContextLost()) return nullptr;
1258 auto ret = AsRefPtr(new WebGLProgramJS(*this));
1259 Run<RPROC(CreateProgram)>(ret->mId);
1260 return ret.forget();
1263 already_AddRefed<WebGLQueryJS> ClientWebGLContext::CreateQuery() const {
1264 const FuncScope funcScope(*this, "createQuery");
1265 if (IsContextLost()) return nullptr;
1267 auto ret = AsRefPtr(new WebGLQueryJS(*this));
1268 Run<RPROC(CreateQuery)>(ret->mId);
1269 return ret.forget();
1272 already_AddRefed<WebGLRenderbufferJS> ClientWebGLContext::CreateRenderbuffer()
1273 const {
1274 const FuncScope funcScope(*this, "createRenderbuffer");
1275 if (IsContextLost()) return nullptr;
1277 auto ret = AsRefPtr(new WebGLRenderbufferJS(*this));
1278 Run<RPROC(CreateRenderbuffer)>(ret->mId);
1279 return ret.forget();
1282 already_AddRefed<WebGLSamplerJS> ClientWebGLContext::CreateSampler() const {
1283 const FuncScope funcScope(*this, "createSampler");
1284 if (IsContextLost()) return nullptr;
1286 auto ret = AsRefPtr(new WebGLSamplerJS(*this));
1287 Run<RPROC(CreateSampler)>(ret->mId);
1288 return ret.forget();
1291 already_AddRefed<WebGLShaderJS> ClientWebGLContext::CreateShader(
1292 const GLenum type) const {
1293 const FuncScope funcScope(*this, "createShader");
1294 if (IsContextLost()) return nullptr;
1296 switch (type) {
1297 case LOCAL_GL_VERTEX_SHADER:
1298 case LOCAL_GL_FRAGMENT_SHADER:
1299 break;
1300 default:
1301 EnqueueError_ArgEnum("type", type);
1302 return nullptr;
1305 auto ret = AsRefPtr(new WebGLShaderJS(*this, type));
1306 Run<RPROC(CreateShader)>(ret->mId, ret->mType);
1307 return ret.forget();
1310 already_AddRefed<WebGLSyncJS> ClientWebGLContext::FenceSync(
1311 const GLenum condition, const GLbitfield flags) const {
1312 const FuncScope funcScope(*this, "fenceSync");
1313 if (IsContextLost()) return nullptr;
1315 if (condition != LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE) {
1316 EnqueueError_ArgEnum("condition", condition);
1317 return nullptr;
1320 if (flags) {
1321 EnqueueError(LOCAL_GL_INVALID_VALUE, "`flags` must be 0.");
1322 return nullptr;
1325 auto ret = AsRefPtr(new WebGLSyncJS(*this));
1326 Run<RPROC(CreateSync)>(ret->mId);
1328 auto& availRunnable = EnsureAvailabilityRunnable();
1329 availRunnable.mSyncs.push_back(ret.get());
1330 ret->mCanBeAvailable = false;
1332 return ret.forget();
1335 already_AddRefed<WebGLTextureJS> ClientWebGLContext::CreateTexture() const {
1336 const FuncScope funcScope(*this, "createTexture");
1337 if (IsContextLost()) return nullptr;
1339 auto ret = AsRefPtr(new WebGLTextureJS(*this));
1340 Run<RPROC(CreateTexture)>(ret->mId);
1341 return ret.forget();
1344 already_AddRefed<WebGLTransformFeedbackJS>
1345 ClientWebGLContext::CreateTransformFeedback() const {
1346 const FuncScope funcScope(*this, "createTransformFeedback");
1347 if (IsContextLost()) return nullptr;
1349 auto ret = AsRefPtr(new WebGLTransformFeedbackJS(*this));
1350 Run<RPROC(CreateTransformFeedback)>(ret->mId);
1351 return ret.forget();
1354 already_AddRefed<WebGLVertexArrayJS> ClientWebGLContext::CreateVertexArray()
1355 const {
1356 const FuncScope funcScope(*this, "createVertexArray");
1357 if (IsContextLost()) return nullptr;
1359 auto ret = AsRefPtr(new WebGLVertexArrayJS(*this));
1360 Run<RPROC(CreateVertexArray)>(ret->mId);
1361 return ret.forget();
1364 // -
1366 static bool ValidateOrSkipForDelete(const ClientWebGLContext& context,
1367 const webgl::ObjectJS* const obj) {
1368 if (!obj) return false;
1369 if (!obj->ValidateForContext(context, "obj")) return false;
1370 if (obj->IsDeleted()) return false;
1371 return true;
1374 void ClientWebGLContext::DeleteBuffer(WebGLBufferJS* const obj) {
1375 const FuncScope funcScope(*this, "deleteBuffer");
1376 if (IsContextLost()) return;
1377 if (!ValidateOrSkipForDelete(*this, obj)) return;
1378 auto& state = State();
1380 // Unbind from all bind points and bound containers
1382 // UBOs
1383 for (const auto i : IntegerRange(state.mBoundUbos.size())) {
1384 if (state.mBoundUbos[i] == obj) {
1385 BindBufferBase(LOCAL_GL_UNIFORM_BUFFER, i, nullptr);
1389 // TFO only if not active
1390 if (!state.mBoundTfo->mActiveOrPaused) {
1391 const auto& buffers = state.mBoundTfo->mAttribBuffers;
1392 for (const auto i : IntegerRange(buffers.size())) {
1393 if (buffers[i] == obj) {
1394 BindBufferBase(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, i, nullptr);
1399 // Generic/global bind points
1400 for (const auto& pair : state.mBoundBufferByTarget) {
1401 if (pair.second == obj) {
1402 BindBuffer(pair.first, nullptr);
1406 // VAO attachments
1407 if (state.mBoundVao->mIndexBuffer == obj) {
1408 BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, nullptr);
1411 const auto& vaoBuffers = state.mBoundVao->mAttribBuffers;
1412 Maybe<WebGLBufferJS*> toRestore;
1413 for (const auto i : IntegerRange(vaoBuffers.size())) {
1414 if (vaoBuffers[i] == obj) {
1415 if (!toRestore) {
1416 toRestore =
1417 Some(state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER].get());
1418 if (*toRestore) {
1419 BindBuffer(LOCAL_GL_ARRAY_BUFFER, nullptr);
1422 VertexAttribPointer(i, 4, LOCAL_GL_FLOAT, false, 0, 0);
1425 if (toRestore && *toRestore) {
1426 BindBuffer(LOCAL_GL_ARRAY_BUFFER, *toRestore);
1429 // -
1431 obj->mDeleteRequested = true;
1432 Run<RPROC(DeleteBuffer)>(obj->mId);
1435 void ClientWebGLContext::DeleteFramebuffer(WebGLFramebufferJS* const obj,
1436 bool canDeleteOpaque) {
1437 const FuncScope funcScope(*this, "deleteFramebuffer");
1438 if (IsContextLost()) return;
1439 if (!ValidateOrSkipForDelete(*this, obj)) return;
1440 if (!canDeleteOpaque && obj->mOpaque) {
1441 EnqueueError(
1442 LOCAL_GL_INVALID_OPERATION,
1443 "An opaque framebuffer's attachments cannot be inspected or changed.");
1444 return;
1446 const auto& state = State();
1448 // Unbind
1449 const auto fnDetach = [&](const GLenum target,
1450 const WebGLFramebufferJS* const fb) {
1451 if (obj == fb) {
1452 BindFramebuffer(target, nullptr);
1455 if (state.mBoundDrawFb == state.mBoundReadFb) {
1456 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1457 } else {
1458 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1459 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1462 obj->mDeleteRequested = true;
1463 Run<RPROC(DeleteFramebuffer)>(obj->mId);
1466 void ClientWebGLContext::DeleteProgram(WebGLProgramJS* const obj) const {
1467 const FuncScope funcScope(*this, "deleteProgram");
1468 if (IsContextLost()) return;
1469 if (!ValidateOrSkipForDelete(*this, obj)) return;
1471 // Don't unbind
1473 obj->mKeepAlive = nullptr;
1476 webgl::ProgramKeepAlive::~ProgramKeepAlive() {
1477 if (!mParent) return;
1478 const auto& context = mParent->Context();
1479 if (!context) return;
1480 context->DoDeleteProgram(*mParent);
1483 void ClientWebGLContext::DoDeleteProgram(WebGLProgramJS& obj) const {
1484 obj.mNextLink_Shaders = {};
1485 Run<RPROC(DeleteProgram)>(obj.mId);
1488 static GLenum QuerySlotTarget(const GLenum specificTarget);
1490 void ClientWebGLContext::DeleteQuery(WebGLQueryJS* const obj) {
1491 const FuncScope funcScope(*this, "deleteQuery");
1492 if (IsContextLost()) return;
1493 if (!ValidateOrSkipForDelete(*this, obj)) return;
1494 const auto& state = State();
1496 // Unbind if current
1498 if (obj->mTarget) {
1499 // Despite mTarget being set, we may not have called BeginQuery on this
1500 // object. QueryCounter may also set mTarget.
1501 const auto slotTarget = QuerySlotTarget(obj->mTarget);
1502 const auto curForTarget =
1503 MaybeFind(state.mCurrentQueryByTarget, slotTarget);
1505 if (curForTarget && *curForTarget == obj) {
1506 EndQuery(obj->mTarget);
1510 obj->mDeleteRequested = true;
1511 Run<RPROC(DeleteQuery)>(obj->mId);
1514 void ClientWebGLContext::DeleteRenderbuffer(WebGLRenderbufferJS* const obj) {
1515 const FuncScope funcScope(*this, "deleteRenderbuffer");
1516 if (IsContextLost()) return;
1517 if (!ValidateOrSkipForDelete(*this, obj)) return;
1518 const auto& state = State();
1520 // Unbind
1521 if (state.mBoundRb == obj) {
1522 BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nullptr);
1525 // Unbind from bound FBs
1526 const auto fnDetach = [&](const GLenum target,
1527 const WebGLFramebufferJS* const fb) {
1528 if (!fb) return;
1529 for (const auto& pair : fb->mAttachments) {
1530 if (pair.second.rb == obj) {
1531 FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
1532 nullptr);
1536 if (state.mBoundDrawFb == state.mBoundReadFb) {
1537 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1538 } else {
1539 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1540 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1543 obj->mDeleteRequested = true;
1544 Run<RPROC(DeleteRenderbuffer)>(obj->mId);
1547 void ClientWebGLContext::DeleteSampler(WebGLSamplerJS* const obj) {
1548 const FuncScope funcScope(*this, "deleteSampler");
1549 if (IsContextLost()) return;
1550 if (!ValidateOrSkipForDelete(*this, obj)) return;
1551 const auto& state = State();
1553 // Unbind
1554 for (const auto i : IntegerRange(state.mTexUnits.size())) {
1555 if (state.mTexUnits[i].sampler == obj) {
1556 BindSampler(i, nullptr);
1560 obj->mDeleteRequested = true;
1561 Run<RPROC(DeleteSampler)>(obj->mId);
1564 void ClientWebGLContext::DeleteShader(WebGLShaderJS* const obj) const {
1565 const FuncScope funcScope(*this, "deleteShader");
1566 if (IsContextLost()) return;
1567 if (!ValidateOrSkipForDelete(*this, obj)) return;
1569 // Don't unbind
1571 obj->mKeepAlive = nullptr;
1574 webgl::ShaderKeepAlive::~ShaderKeepAlive() {
1575 if (!mParent) return;
1576 const auto& context = mParent->Context();
1577 if (!context) return;
1578 context->DoDeleteShader(*mParent);
1581 void ClientWebGLContext::DoDeleteShader(const WebGLShaderJS& obj) const {
1582 Run<RPROC(DeleteShader)>(obj.mId);
1585 void ClientWebGLContext::DeleteSync(WebGLSyncJS* const obj) const {
1586 const FuncScope funcScope(*this, "deleteSync");
1587 if (IsContextLost()) return;
1588 if (!ValidateOrSkipForDelete(*this, obj)) return;
1590 // Nothing to unbind
1592 obj->mDeleteRequested = true;
1593 Run<RPROC(DeleteSync)>(obj->mId);
1596 void ClientWebGLContext::DeleteTexture(WebGLTextureJS* const obj) {
1597 const FuncScope funcScope(*this, "deleteTexture");
1598 if (IsContextLost()) return;
1599 if (!ValidateOrSkipForDelete(*this, obj)) return;
1600 auto& state = State();
1602 // Unbind
1603 const auto& target = obj->mTarget;
1604 if (target) {
1605 // Unbind from tex units
1606 Maybe<uint32_t> restoreTexUnit;
1607 for (const auto i : IntegerRange(state.mTexUnits.size())) {
1608 if (state.mTexUnits[i].texByTarget[target] == obj) {
1609 if (!restoreTexUnit) {
1610 restoreTexUnit = Some(state.mActiveTexUnit);
1612 ActiveTexture(LOCAL_GL_TEXTURE0 + i);
1613 BindTexture(target, nullptr);
1616 if (restoreTexUnit) {
1617 ActiveTexture(LOCAL_GL_TEXTURE0 + *restoreTexUnit);
1620 // Unbind from bound FBs
1621 const auto fnDetach = [&](const GLenum target,
1622 const WebGLFramebufferJS* const fb) {
1623 if (!fb) return;
1624 for (const auto& pair : fb->mAttachments) {
1625 if (pair.second.tex == obj) {
1626 FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
1627 nullptr);
1631 if (state.mBoundDrawFb == state.mBoundReadFb) {
1632 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1633 } else {
1634 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1635 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1639 obj->mDeleteRequested = true;
1640 Run<RPROC(DeleteTexture)>(obj->mId);
1643 void ClientWebGLContext::DeleteTransformFeedback(
1644 WebGLTransformFeedbackJS* const obj) {
1645 const FuncScope funcScope(*this, "deleteTransformFeedback");
1646 if (IsContextLost()) return;
1647 if (!ValidateOrSkipForDelete(*this, obj)) return;
1648 const auto& state = State();
1650 if (obj->mActiveOrPaused) {
1651 EnqueueError(LOCAL_GL_INVALID_OPERATION,
1652 "Transform Feedback object still active or paused.");
1653 return;
1656 // Unbind
1657 if (state.mBoundTfo == obj) {
1658 BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
1661 obj->mDeleteRequested = true;
1662 Run<RPROC(DeleteTransformFeedback)>(obj->mId);
1665 void ClientWebGLContext::DeleteVertexArray(WebGLVertexArrayJS* const obj) {
1666 const FuncScope funcScope(*this, "deleteVertexArray");
1667 if (IsContextLost()) return;
1668 if (!ValidateOrSkipForDelete(*this, obj)) return;
1669 const auto& state = State();
1671 // Unbind
1672 if (state.mBoundVao == obj) {
1673 BindVertexArray(nullptr);
1676 obj->mDeleteRequested = true;
1677 Run<RPROC(DeleteVertexArray)>(obj->mId);
1680 // -
1682 bool ClientWebGLContext::IsBuffer(const WebGLBufferJS* const obj) const {
1683 const FuncScope funcScope(*this, "isBuffer");
1684 if (IsContextLost()) return false;
1686 return obj && obj->IsUsable(*this) &&
1687 obj->mKind != webgl::BufferKind::Undefined;
1690 bool ClientWebGLContext::IsFramebuffer(
1691 const WebGLFramebufferJS* const obj) const {
1692 const FuncScope funcScope(*this, "isFramebuffer");
1693 if (IsContextLost()) return false;
1695 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1698 bool ClientWebGLContext::IsProgram(const WebGLProgramJS* const obj) const {
1699 const FuncScope funcScope(*this, "isProgram");
1700 if (IsContextLost()) return false;
1702 return obj && obj->IsUsable(*this);
1705 bool ClientWebGLContext::IsQuery(const WebGLQueryJS* const obj) const {
1706 const FuncScope funcScope(*this, "isQuery");
1707 if (IsContextLost()) return false;
1709 return obj && obj->IsUsable(*this) && obj->mTarget;
1712 bool ClientWebGLContext::IsRenderbuffer(
1713 const WebGLRenderbufferJS* const obj) const {
1714 const FuncScope funcScope(*this, "isRenderbuffer");
1715 if (IsContextLost()) return false;
1717 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1720 bool ClientWebGLContext::IsSampler(const WebGLSamplerJS* const obj) const {
1721 const FuncScope funcScope(*this, "isSampler");
1722 if (IsContextLost()) return false;
1724 return obj && obj->IsUsable(*this);
1727 bool ClientWebGLContext::IsShader(const WebGLShaderJS* const obj) const {
1728 const FuncScope funcScope(*this, "isShader");
1729 if (IsContextLost()) return false;
1731 return obj && obj->IsUsable(*this);
1734 bool ClientWebGLContext::IsSync(const WebGLSyncJS* const obj) const {
1735 const FuncScope funcScope(*this, "isSync");
1736 if (IsContextLost()) return false;
1738 return obj && obj->IsUsable(*this);
1741 bool ClientWebGLContext::IsTexture(const WebGLTextureJS* const obj) const {
1742 const FuncScope funcScope(*this, "isTexture");
1743 if (IsContextLost()) return false;
1745 return obj && obj->IsUsable(*this) && obj->mTarget;
1748 bool ClientWebGLContext::IsTransformFeedback(
1749 const WebGLTransformFeedbackJS* const obj) const {
1750 const FuncScope funcScope(*this, "isTransformFeedback");
1751 if (IsContextLost()) return false;
1753 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1756 bool ClientWebGLContext::IsVertexArray(
1757 const WebGLVertexArrayJS* const obj) const {
1758 const FuncScope funcScope(*this, "isVertexArray");
1759 if (IsContextLost()) return false;
1761 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1764 // ------------------------- GL State -------------------------
1766 void ClientWebGLContext::SetEnabledI(GLenum cap, Maybe<GLuint> i,
1767 bool val) const {
1768 Run<RPROC(SetEnabled)>(cap, i, val);
1771 bool ClientWebGLContext::IsEnabled(GLenum cap) const {
1772 const FuncScope funcScope(*this, "isEnabled");
1773 const auto notLost = mNotLost;
1774 if (IsContextLost()) return false;
1776 const auto& inProcess = notLost->inProcess;
1777 if (inProcess) {
1778 return inProcess->IsEnabled(cap);
1780 const auto& child = notLost->outOfProcess;
1781 child->FlushPendingCmds();
1782 bool ret = {};
1783 if (!child->SendIsEnabled(cap, &ret)) return false;
1784 return ret;
1787 void ClientWebGLContext::GetInternalformatParameter(
1788 JSContext* cx, GLenum target, GLenum internalformat, GLenum pname,
1789 JS::MutableHandle<JS::Value> retval, ErrorResult& rv) {
1790 const FuncScope funcScope(*this, "getInternalformatParameter");
1791 retval.set(JS::NullValue());
1792 const auto notLost =
1793 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
1794 if (IsContextLost()) return;
1796 const auto& inProcessContext = notLost->inProcess;
1797 Maybe<std::vector<int32_t>> maybe;
1798 if (inProcessContext) {
1799 maybe = inProcessContext->GetInternalformatParameter(target, internalformat,
1800 pname);
1801 } else {
1802 const auto& child = notLost->outOfProcess;
1803 child->FlushPendingCmds();
1804 if (!child->SendGetInternalformatParameter(target, internalformat, pname,
1805 &maybe)) {
1806 return;
1810 if (!maybe) {
1811 return;
1813 // zero-length array indicates out-of-memory
1814 JSObject* obj =
1815 dom::Int32Array::Create(cx, this, maybe->size(), maybe->data());
1816 if (!obj) {
1817 rv = NS_ERROR_OUT_OF_MEMORY;
1819 retval.setObjectOrNull(obj);
1822 static JS::Value StringValue(JSContext* cx, const std::string& str,
1823 ErrorResult& er) {
1824 JSString* jsStr = JS_NewStringCopyN(cx, str.data(), str.size());
1825 if (!jsStr) {
1826 er.Throw(NS_ERROR_OUT_OF_MEMORY);
1827 return JS::NullValue();
1830 return JS::StringValue(jsStr);
1833 template <typename T>
1834 bool ToJSValueOrNull(JSContext* const cx, const RefPtr<T>& ptr,
1835 JS::MutableHandle<JS::Value> retval) {
1836 if (!ptr) {
1837 retval.set(JS::NullValue());
1838 return true;
1840 return dom::ToJSValue(cx, ptr, retval);
1843 template <typename T, typename U, typename S>
1844 static JS::Value CreateAs(JSContext* cx, nsWrapperCache* creator, const S& src,
1845 ErrorResult& rv) {
1846 const auto obj =
1847 T::Create(cx, creator, src.size(), reinterpret_cast<U>(src.data()));
1848 if (!obj) {
1849 rv = NS_ERROR_OUT_OF_MEMORY;
1851 return JS::ObjectOrNullValue(obj);
1854 template <typename T, typename S>
1855 static JS::Value Create(JSContext* cx, nsWrapperCache* creator, const S& src,
1856 ErrorResult& rv) {
1857 return CreateAs<T, decltype(&src[0]), S>(cx, creator, src, rv);
1860 Maybe<double> ClientWebGLContext::GetNumber(const GLenum pname) {
1861 MOZ_ASSERT(!IsContextLost());
1863 const auto& inProcess = mNotLost->inProcess;
1864 if (inProcess) {
1865 return inProcess->GetNumber(pname);
1868 const auto& child = mNotLost->outOfProcess;
1869 child->FlushPendingCmds();
1871 Maybe<double> ret;
1872 if (!child->SendGetNumber(pname, &ret)) {
1873 ret.reset();
1875 return ret;
1878 Maybe<std::string> ClientWebGLContext::GetString(const GLenum pname) {
1879 MOZ_ASSERT(!IsContextLost());
1881 const auto& inProcess = mNotLost->inProcess;
1882 if (inProcess) {
1883 return inProcess->GetString(pname);
1886 const auto& child = mNotLost->outOfProcess;
1887 child->FlushPendingCmds();
1889 Maybe<std::string> ret;
1890 if (!child->SendGetString(pname, &ret)) {
1891 ret.reset();
1893 return ret;
1896 void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
1897 JS::MutableHandle<JS::Value> retval,
1898 ErrorResult& rv, const bool debug) {
1899 retval.set(JS::NullValue());
1900 const FuncScope funcScope(*this, "getParameter");
1901 if (IsContextLost()) return;
1902 const auto& limits = Limits();
1903 const auto& state = State();
1905 // -
1907 const auto fnSetRetval_Buffer = [&](const GLenum target) {
1908 const auto buffer = *MaybeFind(state.mBoundBufferByTarget, target);
1909 (void)ToJSValueOrNull(cx, buffer, retval);
1911 const auto fnSetRetval_Tex = [&](const GLenum texTarget) {
1912 const auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
1913 const auto tex = Find(texUnit.texByTarget, texTarget, nullptr);
1914 (void)ToJSValueOrNull(cx, tex, retval);
1917 switch (pname) {
1918 case LOCAL_GL_ARRAY_BUFFER_BINDING:
1919 fnSetRetval_Buffer(LOCAL_GL_ARRAY_BUFFER);
1920 return;
1922 case LOCAL_GL_CURRENT_PROGRAM:
1923 (void)ToJSValueOrNull(cx, state.mCurrentProgram, retval);
1924 return;
1926 case LOCAL_GL_ELEMENT_ARRAY_BUFFER_BINDING:
1927 (void)ToJSValueOrNull(cx, state.mBoundVao->mIndexBuffer, retval);
1928 return;
1930 case LOCAL_GL_FRAMEBUFFER_BINDING:
1931 (void)ToJSValueOrNull(cx, state.mBoundDrawFb, retval);
1932 return;
1934 case LOCAL_GL_RENDERBUFFER_BINDING:
1935 (void)ToJSValueOrNull(cx, state.mBoundRb, retval);
1936 return;
1938 case LOCAL_GL_TEXTURE_BINDING_2D:
1939 fnSetRetval_Tex(LOCAL_GL_TEXTURE_2D);
1940 return;
1942 case LOCAL_GL_TEXTURE_BINDING_CUBE_MAP:
1943 fnSetRetval_Tex(LOCAL_GL_TEXTURE_CUBE_MAP);
1944 return;
1946 case LOCAL_GL_VERTEX_ARRAY_BINDING: {
1947 if (!mIsWebGL2 &&
1948 !IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object))
1949 break;
1951 auto ret = state.mBoundVao;
1952 if (ret == state.mDefaultVao) {
1953 ret = nullptr;
1955 (void)ToJSValueOrNull(cx, ret, retval);
1956 return;
1959 case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
1960 retval.set(JS::NumberValue(limits.maxTexUnits));
1961 return;
1962 case LOCAL_GL_MAX_TEXTURE_SIZE:
1963 retval.set(JS::NumberValue(limits.maxTex2dSize));
1964 return;
1965 case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE:
1966 retval.set(JS::NumberValue(limits.maxTexCubeSize));
1967 return;
1968 case LOCAL_GL_MAX_VERTEX_ATTRIBS:
1969 retval.set(JS::NumberValue(limits.maxVertexAttribs));
1970 return;
1972 case LOCAL_GL_MAX_VIEWS_OVR:
1973 if (IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) {
1974 retval.set(JS::NumberValue(limits.maxMultiviewLayers));
1975 return;
1977 break;
1979 case LOCAL_GL_PACK_ALIGNMENT:
1980 retval.set(JS::NumberValue(state.mPixelPackState.alignmentInTypeElems));
1981 return;
1982 case LOCAL_GL_UNPACK_ALIGNMENT:
1983 retval.set(JS::NumberValue(state.mPixelUnpackState.alignmentInTypeElems));
1984 return;
1986 case dom::WebGLRenderingContext_Binding::UNPACK_FLIP_Y_WEBGL:
1987 retval.set(JS::BooleanValue(state.mPixelUnpackState.flipY));
1988 return;
1989 case dom::WebGLRenderingContext_Binding::UNPACK_PREMULTIPLY_ALPHA_WEBGL:
1990 retval.set(JS::BooleanValue(state.mPixelUnpackState.premultiplyAlpha));
1991 return;
1992 case dom::WebGLRenderingContext_Binding::UNPACK_COLORSPACE_CONVERSION_WEBGL:
1993 retval.set(JS::NumberValue(state.mPixelUnpackState.colorspaceConversion));
1994 return;
1996 // -
1997 // Array returns
1999 // 2 floats
2000 case LOCAL_GL_DEPTH_RANGE:
2001 retval.set(Create<dom::Float32Array>(cx, this, state.mDepthRange, rv));
2002 return;
2004 case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
2005 retval.set(
2006 Create<dom::Float32Array>(cx, this, limits.pointSizeRange, rv));
2007 return;
2009 case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE:
2010 retval.set(
2011 Create<dom::Float32Array>(cx, this, limits.lineWidthRange, rv));
2012 return;
2014 // 4 floats
2015 case LOCAL_GL_COLOR_CLEAR_VALUE:
2016 retval.set(Create<dom::Float32Array>(cx, this, state.mClearColor, rv));
2017 return;
2019 case LOCAL_GL_BLEND_COLOR:
2020 retval.set(Create<dom::Float32Array>(cx, this, state.mBlendColor, rv));
2021 return;
2023 // 2 ints
2024 case LOCAL_GL_MAX_VIEWPORT_DIMS: {
2025 const auto dims =
2026 std::array<uint32_t, 2>{limits.maxViewportDim, limits.maxViewportDim};
2027 retval.set(CreateAs<dom::Int32Array, const int32_t*>(cx, this, dims, rv));
2028 return;
2031 // 4 ints
2032 case LOCAL_GL_SCISSOR_BOX:
2033 retval.set(Create<dom::Int32Array>(cx, this, state.mScissor, rv));
2034 return;
2036 case LOCAL_GL_VIEWPORT:
2037 retval.set(Create<dom::Int32Array>(cx, this, state.mViewport, rv));
2038 return;
2040 // any
2041 case LOCAL_GL_COMPRESSED_TEXTURE_FORMATS:
2042 retval.set(Create<dom::Uint32Array>(cx, this,
2043 state.mCompressedTextureFormats, rv));
2044 return;
2047 if (mIsWebGL2) {
2048 switch (pname) {
2049 case LOCAL_GL_COPY_READ_BUFFER_BINDING:
2050 fnSetRetval_Buffer(LOCAL_GL_COPY_READ_BUFFER);
2051 return;
2053 case LOCAL_GL_COPY_WRITE_BUFFER_BINDING:
2054 fnSetRetval_Buffer(LOCAL_GL_COPY_WRITE_BUFFER);
2055 return;
2057 case LOCAL_GL_DRAW_FRAMEBUFFER_BINDING:
2058 (void)ToJSValueOrNull(cx, state.mBoundDrawFb, retval);
2059 return;
2061 case LOCAL_GL_PIXEL_PACK_BUFFER_BINDING:
2062 fnSetRetval_Buffer(LOCAL_GL_PIXEL_PACK_BUFFER);
2063 return;
2065 case LOCAL_GL_PIXEL_UNPACK_BUFFER_BINDING:
2066 fnSetRetval_Buffer(LOCAL_GL_PIXEL_UNPACK_BUFFER);
2067 return;
2069 case LOCAL_GL_READ_FRAMEBUFFER_BINDING:
2070 (void)ToJSValueOrNull(cx, state.mBoundReadFb, retval);
2071 return;
2073 case LOCAL_GL_SAMPLER_BINDING: {
2074 const auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
2075 (void)ToJSValueOrNull(cx, texUnit.sampler, retval);
2076 return;
2079 case LOCAL_GL_TEXTURE_BINDING_2D_ARRAY:
2080 fnSetRetval_Tex(LOCAL_GL_TEXTURE_2D_ARRAY);
2081 return;
2083 case LOCAL_GL_TEXTURE_BINDING_3D:
2084 fnSetRetval_Tex(LOCAL_GL_TEXTURE_3D);
2085 return;
2087 case LOCAL_GL_TRANSFORM_FEEDBACK_BINDING: {
2088 auto ret = state.mBoundTfo;
2089 if (ret == state.mDefaultTfo) {
2090 ret = nullptr;
2092 (void)ToJSValueOrNull(cx, ret, retval);
2093 return;
2096 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
2097 fnSetRetval_Buffer(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER);
2098 return;
2100 case LOCAL_GL_UNIFORM_BUFFER_BINDING:
2101 fnSetRetval_Buffer(LOCAL_GL_UNIFORM_BUFFER);
2102 return;
2104 case LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS:
2105 retval.set(
2106 JS::NumberValue(webgl::kMaxTransformFeedbackSeparateAttribs));
2107 return;
2108 case LOCAL_GL_MAX_UNIFORM_BUFFER_BINDINGS:
2109 retval.set(JS::NumberValue(limits.maxUniformBufferBindings));
2110 return;
2111 case LOCAL_GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT:
2112 retval.set(JS::NumberValue(limits.uniformBufferOffsetAlignment));
2113 return;
2114 case LOCAL_GL_MAX_3D_TEXTURE_SIZE:
2115 retval.set(JS::NumberValue(limits.maxTex3dSize));
2116 return;
2117 case LOCAL_GL_MAX_ARRAY_TEXTURE_LAYERS:
2118 retval.set(JS::NumberValue(limits.maxTexArrayLayers));
2119 return;
2121 case LOCAL_GL_PACK_ROW_LENGTH:
2122 retval.set(JS::NumberValue(state.mPixelPackState.rowLength));
2123 return;
2124 case LOCAL_GL_PACK_SKIP_PIXELS:
2125 retval.set(JS::NumberValue(state.mPixelPackState.skipPixels));
2126 return;
2127 case LOCAL_GL_PACK_SKIP_ROWS:
2128 retval.set(JS::NumberValue(state.mPixelPackState.skipRows));
2129 return;
2131 case LOCAL_GL_UNPACK_IMAGE_HEIGHT:
2132 retval.set(JS::NumberValue(state.mPixelUnpackState.imageHeight));
2133 return;
2134 case LOCAL_GL_UNPACK_ROW_LENGTH:
2135 retval.set(JS::NumberValue(state.mPixelUnpackState.rowLength));
2136 return;
2137 case LOCAL_GL_UNPACK_SKIP_IMAGES:
2138 retval.set(JS::NumberValue(state.mPixelUnpackState.skipImages));
2139 return;
2140 case LOCAL_GL_UNPACK_SKIP_PIXELS:
2141 retval.set(JS::NumberValue(state.mPixelUnpackState.skipPixels));
2142 return;
2143 case LOCAL_GL_UNPACK_SKIP_ROWS:
2144 retval.set(JS::NumberValue(state.mPixelUnpackState.skipRows));
2145 return;
2146 } // switch pname
2147 } // if webgl2
2149 // -
2151 if (!debug) {
2152 const auto GetUnmaskedRenderer = [&]() {
2153 const auto prefLock = StaticPrefs::webgl_override_unmasked_renderer();
2154 if (!prefLock->IsEmpty()) {
2155 return Some(ToString(*prefLock));
2157 return GetString(LOCAL_GL_RENDERER);
2160 const auto GetUnmaskedVendor = [&]() {
2161 const auto prefLock = StaticPrefs::webgl_override_unmasked_vendor();
2162 if (!prefLock->IsEmpty()) {
2163 return Some(ToString(*prefLock));
2165 return GetString(LOCAL_GL_VENDOR);
2168 // -
2170 Maybe<std::string> ret;
2172 switch (pname) {
2173 case LOCAL_GL_VENDOR:
2174 ret = Some(std::string{"Mozilla"});
2175 break;
2177 case LOCAL_GL_RENDERER: {
2178 bool allowRenderer = StaticPrefs::webgl_enable_renderer_query();
2179 if (nsContentUtils::ShouldResistFingerprinting()) {
2180 allowRenderer = false;
2182 if (allowRenderer) {
2183 ret = GetUnmaskedRenderer();
2184 if (ret) {
2185 ret = Some(webgl::SanitizeRenderer(*ret));
2188 if (!ret) {
2189 ret = Some(std::string{"Mozilla"});
2191 break;
2194 case LOCAL_GL_VERSION:
2195 if (mIsWebGL2) {
2196 ret = Some(std::string{"WebGL 2.0"});
2197 } else {
2198 ret = Some(std::string{"WebGL 1.0"});
2200 break;
2202 case LOCAL_GL_SHADING_LANGUAGE_VERSION:
2203 if (mIsWebGL2) {
2204 ret = Some(std::string{"WebGL GLSL ES 3.00"});
2205 } else {
2206 ret = Some(std::string{"WebGL GLSL ES 1.0"});
2208 break;
2210 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
2211 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL: {
2212 if (!IsExtensionEnabled(WebGLExtensionID::WEBGL_debug_renderer_info)) {
2213 EnqueueError_ArgEnum("pname", pname);
2214 return;
2217 switch (pname) {
2218 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
2219 ret = GetUnmaskedRenderer();
2220 if (ret && StaticPrefs::webgl_sanitize_unmasked_renderer()) {
2221 *ret = webgl::SanitizeRenderer(*ret);
2223 break;
2225 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
2226 ret = GetUnmaskedVendor();
2227 break;
2229 default:
2230 MOZ_CRASH();
2232 break;
2235 default:
2236 break;
2239 if (ret) {
2240 retval.set(StringValue(cx, *ret, rv));
2241 return;
2243 } // if (!debug)
2245 // -
2247 bool debugOnly = false;
2248 bool asString = false;
2250 switch (pname) {
2251 case LOCAL_GL_EXTENSIONS:
2252 case LOCAL_GL_RENDERER:
2253 case LOCAL_GL_VENDOR:
2254 case LOCAL_GL_VERSION:
2255 case dom::MOZ_debug_Binding::WSI_INFO:
2256 debugOnly = true;
2257 asString = true;
2258 break;
2260 case dom::MOZ_debug_Binding::DOES_INDEX_VALIDATION:
2261 debugOnly = true;
2262 break;
2264 default:
2265 break;
2268 if (debugOnly && !debug) {
2269 EnqueueError_ArgEnum("pname", pname);
2270 return;
2273 // -
2275 if (asString) {
2276 const auto maybe = GetString(pname);
2277 if (maybe) {
2278 auto str = *maybe;
2279 if (pname == dom::MOZ_debug_Binding::WSI_INFO) {
2280 nsPrintfCString more("\nIsWebglOutOfProcessEnabled: %i",
2281 int(IsWebglOutOfProcessEnabled()));
2282 str += more.BeginReading();
2284 retval.set(StringValue(cx, str.c_str(), rv));
2286 } else {
2287 const auto maybe = GetNumber(pname);
2288 if (maybe) {
2289 switch (pname) {
2290 // WebGL 1:
2291 case LOCAL_GL_BLEND:
2292 case LOCAL_GL_CULL_FACE:
2293 case LOCAL_GL_DEPTH_TEST:
2294 case LOCAL_GL_DEPTH_WRITEMASK:
2295 case LOCAL_GL_DITHER:
2296 case LOCAL_GL_POLYGON_OFFSET_FILL:
2297 case LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE:
2298 case LOCAL_GL_SAMPLE_COVERAGE:
2299 case LOCAL_GL_SAMPLE_COVERAGE_INVERT:
2300 case LOCAL_GL_SCISSOR_TEST:
2301 case LOCAL_GL_STENCIL_TEST:
2302 // WebGL 2:
2303 case LOCAL_GL_RASTERIZER_DISCARD:
2304 case LOCAL_GL_TRANSFORM_FEEDBACK_ACTIVE:
2305 case LOCAL_GL_TRANSFORM_FEEDBACK_PAUSED:
2306 retval.set(JS::BooleanValue(*maybe));
2307 break;
2309 // 4 bools
2310 case LOCAL_GL_COLOR_WRITEMASK: {
2311 const auto mask = uint8_t(*maybe);
2312 const auto bs = std::bitset<4>(mask);
2313 const auto src = std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]};
2314 JS::Rooted<JS::Value> arr(cx);
2315 if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
2316 rv = NS_ERROR_OUT_OF_MEMORY;
2318 retval.set(arr);
2319 return;
2322 default:
2323 retval.set(JS::NumberValue(*maybe));
2324 break;
2330 void ClientWebGLContext::GetBufferParameter(
2331 JSContext* cx, GLenum target, GLenum pname,
2332 JS::MutableHandle<JS::Value> retval) const {
2333 retval.set(JS::NullValue());
2334 if (IsContextLost()) return;
2336 const auto maybe = [&]() {
2337 const auto& inProcess = mNotLost->inProcess;
2338 if (inProcess) {
2339 return inProcess->GetBufferParameter(target, pname);
2341 const auto& child = mNotLost->outOfProcess;
2342 child->FlushPendingCmds();
2343 Maybe<double> ret;
2344 if (!child->SendGetBufferParameter(target, pname, &ret)) {
2345 ret.reset();
2347 return ret;
2348 }();
2349 if (maybe) {
2350 retval.set(JS::NumberValue(*maybe));
2354 bool IsFramebufferTarget(const bool isWebgl2, const GLenum target) {
2355 switch (target) {
2356 case LOCAL_GL_FRAMEBUFFER:
2357 return true;
2359 case LOCAL_GL_DRAW_FRAMEBUFFER:
2360 case LOCAL_GL_READ_FRAMEBUFFER:
2361 return isWebgl2;
2363 default:
2364 return false;
2368 void ClientWebGLContext::GetFramebufferAttachmentParameter(
2369 JSContext* const cx, const GLenum target, const GLenum attachment,
2370 const GLenum pname, JS::MutableHandle<JS::Value> retval,
2371 ErrorResult& rv) const {
2372 retval.set(JS::NullValue());
2373 const FuncScope funcScope(*this, "getFramebufferAttachmentParameter");
2374 if (IsContextLost()) return;
2376 const auto& state = State();
2378 if (!IsFramebufferTarget(mIsWebGL2, target)) {
2379 EnqueueError_ArgEnum("target", target);
2380 return;
2382 auto fb = state.mBoundDrawFb;
2383 if (target == LOCAL_GL_READ_FRAMEBUFFER) {
2384 fb = state.mBoundReadFb;
2387 const auto fnGet = [&](const GLenum pname) {
2388 const auto fbId = fb ? fb->mId : 0;
2390 const auto& inProcess = mNotLost->inProcess;
2391 if (inProcess) {
2392 return inProcess->GetFramebufferAttachmentParameter(fbId, attachment,
2393 pname);
2395 const auto& child = mNotLost->outOfProcess;
2396 child->FlushPendingCmds();
2397 Maybe<double> ret;
2398 if (!child->SendGetFramebufferAttachmentParameter(fbId, attachment, pname,
2399 &ret)) {
2400 ret.reset();
2402 return ret;
2405 if (fb) {
2406 if (fb->mOpaque) {
2407 EnqueueError(LOCAL_GL_INVALID_OPERATION,
2408 "An opaque framebuffer's attachments cannot be inspected or "
2409 "changed.");
2410 return;
2412 auto attachmentSlotEnum = attachment;
2413 if (mIsWebGL2 && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
2414 // In webgl2, DEPTH_STENCIL is valid iff the DEPTH and STENCIL images
2415 // match, so check if the server errors.
2416 const auto maybe = fnGet(LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE);
2417 if (!maybe) return;
2418 attachmentSlotEnum = LOCAL_GL_DEPTH_ATTACHMENT;
2421 const auto maybeSlot = fb->GetAttachment(attachmentSlotEnum);
2422 if (!maybeSlot) {
2423 EnqueueError_ArgEnum("attachment", attachment);
2424 return;
2426 const auto& attached = *maybeSlot;
2428 // -
2430 if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) {
2431 if (attached.rb) {
2432 (void)ToJSValueOrNull(cx, attached.rb, retval);
2433 } else {
2434 if (!mIsWebGL2 && !attached.tex) {
2435 EnqueueError_ArgEnum("pname", pname);
2436 return;
2438 (void)ToJSValueOrNull(cx, attached.tex, retval);
2440 return;
2444 const auto maybe = fnGet(pname);
2445 if (maybe) {
2446 retval.set(JS::NumberValue(*maybe));
2450 void ClientWebGLContext::GetRenderbufferParameter(
2451 JSContext* cx, GLenum target, GLenum pname,
2452 JS::MutableHandle<JS::Value> retval) const {
2453 retval.set(JS::NullValue());
2454 const FuncScope funcScope(*this, "getRenderbufferParameter");
2455 if (IsContextLost()) return;
2457 if (target != LOCAL_GL_RENDERBUFFER) {
2458 EnqueueError_ArgEnum("target", target);
2459 return;
2462 const auto& state = State();
2463 const auto& rb = state.mBoundRb;
2464 const auto rbId = rb ? rb->mId : 0;
2465 const auto maybe = [&]() {
2466 const auto& inProcess = mNotLost->inProcess;
2467 if (inProcess) {
2468 return inProcess->GetRenderbufferParameter(rbId, pname);
2470 const auto& child = mNotLost->outOfProcess;
2471 child->FlushPendingCmds();
2472 Maybe<double> ret;
2473 if (!child->SendGetRenderbufferParameter(rbId, pname, &ret)) {
2474 ret.reset();
2476 return ret;
2477 }();
2478 if (maybe) {
2479 retval.set(JS::NumberValue(*maybe));
2483 void ClientWebGLContext::GetIndexedParameter(
2484 JSContext* cx, GLenum target, GLuint index,
2485 JS::MutableHandle<JS::Value> retval, ErrorResult& rv) const {
2486 retval.set(JS::NullValue());
2487 const FuncScope funcScope(*this, "getIndexedParameter");
2488 if (IsContextLost()) return;
2489 auto keepalive = mNotLost;
2491 const auto& state = State();
2493 switch (target) {
2494 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: {
2495 const auto& list = state.mBoundTfo->mAttribBuffers;
2496 if (index >= list.size()) {
2497 EnqueueError(LOCAL_GL_INVALID_VALUE,
2498 "`index` (%u) >= MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS",
2499 index);
2500 return;
2502 (void)ToJSValueOrNull(cx, list[index], retval);
2503 return;
2506 case LOCAL_GL_UNIFORM_BUFFER_BINDING: {
2507 const auto& list = state.mBoundUbos;
2508 if (index >= list.size()) {
2509 EnqueueError(LOCAL_GL_INVALID_VALUE,
2510 "`index` (%u) >= MAX_UNIFORM_BUFFER_BINDINGS", index);
2511 return;
2513 (void)ToJSValueOrNull(cx, list[index], retval);
2514 return;
2518 const auto maybe = [&]() {
2519 const auto& inProcess = mNotLost->inProcess;
2520 if (inProcess) {
2521 return inProcess->GetIndexedParameter(target, index);
2523 const auto& child = mNotLost->outOfProcess;
2524 child->FlushPendingCmds();
2525 Maybe<double> ret;
2526 if (!child->SendGetIndexedParameter(target, index, &ret)) {
2527 ret.reset();
2529 return ret;
2530 }();
2531 if (maybe) {
2532 switch (target) {
2533 case LOCAL_GL_COLOR_WRITEMASK: {
2534 const auto bs = std::bitset<4>(*maybe);
2535 const auto src = std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]};
2536 JS::Rooted<JS::Value> arr(cx);
2537 if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
2538 rv = NS_ERROR_OUT_OF_MEMORY;
2540 retval.set(arr);
2541 return;
2544 default:
2545 retval.set(JS::NumberValue(*maybe));
2546 return;
2551 void ClientWebGLContext::GetUniform(JSContext* const cx,
2552 const WebGLProgramJS& prog,
2553 const WebGLUniformLocationJS& loc,
2554 JS::MutableHandle<JS::Value> retval) {
2555 retval.set(JS::NullValue());
2556 const FuncScope funcScope(*this, "getUniform");
2557 if (IsContextLost()) return;
2558 if (!prog.ValidateUsable(*this, "prog")) return;
2559 if (!loc.ValidateUsable(*this, "loc")) return;
2561 const auto& activeLinkResult = GetActiveLinkResult();
2562 if (!activeLinkResult) {
2563 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No active linked Program.");
2564 return;
2566 const auto& reqLinkInfo = loc.mParent.lock();
2567 if (reqLinkInfo.get() != activeLinkResult) {
2568 EnqueueError(LOCAL_GL_INVALID_OPERATION,
2569 "UniformLocation is not from the current active Program.");
2570 return;
2573 const auto res = [&]() {
2574 const auto& inProcess = mNotLost->inProcess;
2575 if (inProcess) {
2576 return inProcess->GetUniform(prog.mId, loc.mLocation);
2578 const auto& child = mNotLost->outOfProcess;
2579 child->FlushPendingCmds();
2580 webgl::GetUniformData ret;
2581 if (!child->SendGetUniform(prog.mId, loc.mLocation, &ret)) {
2582 ret = {};
2584 return ret;
2585 }();
2586 if (!res.type) return;
2588 const auto elemCount = ElemTypeComponents(res.type);
2589 MOZ_ASSERT(elemCount);
2591 switch (res.type) {
2592 case LOCAL_GL_BOOL:
2593 retval.set(JS::BooleanValue(res.data[0]));
2594 return;
2596 case LOCAL_GL_FLOAT: {
2597 const auto ptr = reinterpret_cast<const float*>(res.data);
2598 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2599 return;
2601 case LOCAL_GL_INT: {
2602 const auto ptr = reinterpret_cast<const int32_t*>(res.data);
2603 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2604 return;
2606 case LOCAL_GL_UNSIGNED_INT:
2607 case LOCAL_GL_SAMPLER_2D:
2608 case LOCAL_GL_SAMPLER_3D:
2609 case LOCAL_GL_SAMPLER_CUBE:
2610 case LOCAL_GL_SAMPLER_2D_SHADOW:
2611 case LOCAL_GL_SAMPLER_2D_ARRAY:
2612 case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
2613 case LOCAL_GL_SAMPLER_CUBE_SHADOW:
2614 case LOCAL_GL_INT_SAMPLER_2D:
2615 case LOCAL_GL_INT_SAMPLER_3D:
2616 case LOCAL_GL_INT_SAMPLER_CUBE:
2617 case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
2618 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
2619 case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
2620 case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
2621 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: {
2622 const auto ptr = reinterpret_cast<const uint32_t*>(res.data);
2623 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2624 return;
2627 // -
2629 case LOCAL_GL_BOOL_VEC2:
2630 case LOCAL_GL_BOOL_VEC3:
2631 case LOCAL_GL_BOOL_VEC4: {
2632 const auto intArr = reinterpret_cast<const int32_t*>(res.data);
2633 bool boolArr[4] = {};
2634 for (const auto i : IntegerRange(elemCount)) {
2635 boolArr[i] = bool(intArr[i]);
2637 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, boolArr, elemCount, retval));
2638 return;
2641 case LOCAL_GL_FLOAT_VEC2:
2642 case LOCAL_GL_FLOAT_VEC3:
2643 case LOCAL_GL_FLOAT_VEC4:
2644 case LOCAL_GL_FLOAT_MAT2:
2645 case LOCAL_GL_FLOAT_MAT3:
2646 case LOCAL_GL_FLOAT_MAT4:
2647 case LOCAL_GL_FLOAT_MAT2x3:
2648 case LOCAL_GL_FLOAT_MAT2x4:
2649 case LOCAL_GL_FLOAT_MAT3x2:
2650 case LOCAL_GL_FLOAT_MAT3x4:
2651 case LOCAL_GL_FLOAT_MAT4x2:
2652 case LOCAL_GL_FLOAT_MAT4x3: {
2653 const auto ptr = reinterpret_cast<const float*>(res.data);
2654 JSObject* obj = dom::Float32Array::Create(cx, this, elemCount, ptr);
2655 MOZ_ASSERT(obj);
2656 retval.set(JS::ObjectOrNullValue(obj));
2657 return;
2660 case LOCAL_GL_INT_VEC2:
2661 case LOCAL_GL_INT_VEC3:
2662 case LOCAL_GL_INT_VEC4: {
2663 const auto ptr = reinterpret_cast<const int32_t*>(res.data);
2664 JSObject* obj = dom::Int32Array::Create(cx, this, elemCount, ptr);
2665 MOZ_ASSERT(obj);
2666 retval.set(JS::ObjectOrNullValue(obj));
2667 return;
2670 case LOCAL_GL_UNSIGNED_INT_VEC2:
2671 case LOCAL_GL_UNSIGNED_INT_VEC3:
2672 case LOCAL_GL_UNSIGNED_INT_VEC4: {
2673 const auto ptr = reinterpret_cast<const uint32_t*>(res.data);
2674 JSObject* obj = dom::Uint32Array::Create(cx, this, elemCount, ptr);
2675 MOZ_ASSERT(obj);
2676 retval.set(JS::ObjectOrNullValue(obj));
2677 return;
2680 default:
2681 MOZ_CRASH("GFX: Invalid elemType.");
2685 already_AddRefed<WebGLShaderPrecisionFormatJS>
2686 ClientWebGLContext::GetShaderPrecisionFormat(const GLenum shadertype,
2687 const GLenum precisiontype) {
2688 if (IsContextLost()) return nullptr;
2689 const auto info = [&]() {
2690 const auto& inProcess = mNotLost->inProcess;
2691 if (inProcess) {
2692 return inProcess->GetShaderPrecisionFormat(shadertype, precisiontype);
2694 const auto& child = mNotLost->outOfProcess;
2695 child->FlushPendingCmds();
2696 Maybe<webgl::ShaderPrecisionFormat> ret;
2697 if (!child->SendGetShaderPrecisionFormat(shadertype, precisiontype, &ret)) {
2698 ret.reset();
2700 return ret;
2701 }();
2703 if (!info) return nullptr;
2704 return AsAddRefed(new WebGLShaderPrecisionFormatJS(*info));
2707 void ClientWebGLContext::BlendColor(GLclampf r, GLclampf g, GLclampf b,
2708 GLclampf a) {
2709 const FuncScope funcScope(*this, "blendColor");
2710 if (IsContextLost()) return;
2711 auto& state = State();
2713 auto& cache = state.mBlendColor;
2714 cache[0] = r;
2715 cache[1] = g;
2716 cache[2] = b;
2717 cache[3] = a;
2719 Run<RPROC(BlendColor)>(r, g, b, a);
2722 void ClientWebGLContext::BlendEquationSeparateI(Maybe<GLuint> i, GLenum modeRGB,
2723 GLenum modeAlpha) {
2724 Run<RPROC(BlendEquationSeparate)>(i, modeRGB, modeAlpha);
2727 void ClientWebGLContext::BlendFuncSeparateI(Maybe<GLuint> i, GLenum srcRGB,
2728 GLenum dstRGB, GLenum srcAlpha,
2729 GLenum dstAlpha) {
2730 Run<RPROC(BlendFuncSeparate)>(i, srcRGB, dstRGB, srcAlpha, dstAlpha);
2733 GLenum ClientWebGLContext::CheckFramebufferStatus(GLenum target) {
2734 if (IsContextLost()) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
2736 const auto& inProcess = mNotLost->inProcess;
2737 if (inProcess) {
2738 return inProcess->CheckFramebufferStatus(target);
2740 const auto& child = mNotLost->outOfProcess;
2741 child->FlushPendingCmds();
2742 GLenum ret = 0;
2743 if (!child->SendCheckFramebufferStatus(target, &ret)) {
2744 ret = 0;
2746 return ret;
2749 void ClientWebGLContext::Clear(GLbitfield mask) {
2750 Run<RPROC(Clear)>(mask);
2752 AfterDrawCall();
2755 // -
2757 void ClientWebGLContext::ClearBufferTv(const GLenum buffer,
2758 const GLint drawBuffer,
2759 const webgl::AttribBaseType type,
2760 const Range<const uint8_t>& view,
2761 const GLuint srcElemOffset) {
2762 const FuncScope funcScope(*this, "clearBufferu?[fi]v");
2763 if (IsContextLost()) return;
2765 const auto byteOffset = CheckedInt<size_t>(srcElemOffset) * sizeof(float);
2766 if (!byteOffset.isValid() || byteOffset.value() > view.length()) {
2767 EnqueueError(LOCAL_GL_INVALID_VALUE, "`srcOffset` too large for `values`.");
2768 return;
2770 webgl::TypedQuad data;
2771 data.type = type;
2773 auto dataSize = data.data.size();
2774 switch (buffer) {
2775 case LOCAL_GL_COLOR:
2776 break;
2778 case LOCAL_GL_DEPTH:
2779 dataSize = sizeof(float);
2780 break;
2782 case LOCAL_GL_STENCIL:
2783 dataSize = sizeof(int32_t);
2784 break;
2786 default:
2787 EnqueueError_ArgEnum("buffer", buffer);
2788 return;
2791 const auto requiredBytes = byteOffset + dataSize;
2792 if (!requiredBytes.isValid() || requiredBytes.value() > view.length()) {
2793 EnqueueError(LOCAL_GL_INVALID_VALUE, "`values` too small.");
2794 return;
2797 memcpy(data.data.data(), view.begin().get() + byteOffset.value(), dataSize);
2798 Run<RPROC(ClearBufferTv)>(buffer, drawBuffer, data);
2800 AfterDrawCall();
2803 void ClientWebGLContext::ClearBufferfi(GLenum buffer, GLint drawBuffer,
2804 GLfloat depth, GLint stencil) {
2805 Run<RPROC(ClearBufferfi)>(buffer, drawBuffer, depth, stencil);
2807 AfterDrawCall();
2810 // -
2812 void ClientWebGLContext::ClearColor(GLclampf r, GLclampf g, GLclampf b,
2813 GLclampf a) {
2814 const FuncScope funcScope(*this, "clearColor");
2815 if (IsContextLost()) return;
2816 auto& state = State();
2818 auto& cache = state.mClearColor;
2819 cache[0] = r;
2820 cache[1] = g;
2821 cache[2] = b;
2822 cache[3] = a;
2824 Run<RPROC(ClearColor)>(r, g, b, a);
2827 void ClientWebGLContext::ClearDepth(GLclampf v) { Run<RPROC(ClearDepth)>(v); }
2829 void ClientWebGLContext::ClearStencil(GLint v) { Run<RPROC(ClearStencil)>(v); }
2831 void ClientWebGLContext::ColorMaskI(Maybe<GLuint> i, bool r, bool g, bool b,
2832 bool a) const {
2833 const FuncScope funcScope(*this, "colorMask");
2834 if (IsContextLost()) return;
2836 const uint8_t mask =
2837 uint8_t(r << 0) | uint8_t(g << 1) | uint8_t(b << 2) | uint8_t(a << 3);
2838 Run<RPROC(ColorMask)>(i, mask);
2841 void ClientWebGLContext::CullFace(GLenum face) { Run<RPROC(CullFace)>(face); }
2843 void ClientWebGLContext::DepthFunc(GLenum func) { Run<RPROC(DepthFunc)>(func); }
2845 void ClientWebGLContext::DepthMask(WebGLboolean b) { Run<RPROC(DepthMask)>(b); }
2847 void ClientWebGLContext::DepthRange(GLclampf zNear, GLclampf zFar) {
2848 const FuncScope funcScope(*this, "depthRange");
2849 if (IsContextLost()) return;
2850 auto& state = State();
2852 state.mDepthRange = {zNear, zFar};
2854 Run<RPROC(DepthRange)>(zNear, zFar);
2857 void ClientWebGLContext::Flush(const bool flushGl) {
2858 const FuncScope funcScope(*this, "flush");
2859 const auto notLost = mNotLost;
2860 if (IsContextLost()) return;
2862 if (flushGl) {
2863 Run<RPROC(Flush)>();
2866 if (notLost->inProcess) return;
2867 const auto& child = mNotLost->outOfProcess;
2868 child->FlushPendingCmds();
2871 void ClientWebGLContext::Finish() {
2872 if (IsContextLost()) return;
2874 const auto& inProcess = mNotLost->inProcess;
2875 if (inProcess) {
2876 inProcess->Finish();
2877 return;
2879 const auto& child = mNotLost->outOfProcess;
2880 child->FlushPendingCmds();
2881 (void)child->SendFinish();
2884 void ClientWebGLContext::FrontFace(GLenum mode) { Run<RPROC(FrontFace)>(mode); }
2886 GLenum ClientWebGLContext::GetError() {
2887 const auto notLost = mNotLost;
2888 if (mNextError) {
2889 const auto ret = mNextError;
2890 mNextError = 0;
2891 return ret;
2893 if (IsContextLost()) return 0;
2895 const auto& inProcess = notLost->inProcess;
2896 if (inProcess) {
2897 return inProcess->GetError();
2899 const auto& child = notLost->outOfProcess;
2900 child->FlushPendingCmds();
2901 GLenum ret = 0;
2902 if (!child->SendGetError(&ret)) {
2903 ret = 0;
2905 return ret;
2908 void ClientWebGLContext::Hint(GLenum target, GLenum mode) {
2909 Run<RPROC(Hint)>(target, mode);
2912 void ClientWebGLContext::LineWidth(GLfloat width) {
2913 Run<RPROC(LineWidth)>(width);
2916 Maybe<webgl::ErrorInfo> SetPixelUnpack(
2917 const bool isWebgl2, webgl::PixelUnpackStateWebgl* const unpacking,
2918 const GLenum pname, const GLint param);
2920 void ClientWebGLContext::PixelStorei(const GLenum pname, const GLint iparam) {
2921 const FuncScope funcScope(*this, "pixelStorei");
2922 if (IsContextLost()) return;
2923 if (!ValidateNonNegative("param", iparam)) return;
2924 const auto param = static_cast<uint32_t>(iparam);
2926 auto& state = State();
2927 auto& packState = state.mPixelPackState;
2928 switch (pname) {
2929 case LOCAL_GL_PACK_ALIGNMENT:
2930 switch (param) {
2931 case 1:
2932 case 2:
2933 case 4:
2934 case 8:
2935 break;
2936 default:
2937 EnqueueError(LOCAL_GL_INVALID_VALUE,
2938 "PACK_ALIGNMENT must be one of [1,2,4,8], was %i.",
2939 iparam);
2940 return;
2942 packState.alignmentInTypeElems = param;
2943 return;
2945 case LOCAL_GL_PACK_ROW_LENGTH:
2946 if (!mIsWebGL2) break;
2947 packState.rowLength = param;
2948 return;
2950 case LOCAL_GL_PACK_SKIP_PIXELS:
2951 if (!mIsWebGL2) break;
2952 packState.skipPixels = param;
2953 return;
2955 case LOCAL_GL_PACK_SKIP_ROWS:
2956 if (!mIsWebGL2) break;
2957 packState.skipRows = param;
2958 return;
2960 case dom::MOZ_debug_Binding::UNPACK_REQUIRE_FASTPATH:
2961 if (!IsSupported(WebGLExtensionID::MOZ_debug)) {
2962 EnqueueError_ArgEnum("pname", pname);
2963 return;
2965 break;
2967 default:
2968 break;
2971 const auto err =
2972 SetPixelUnpack(mIsWebGL2, &state.mPixelUnpackState, pname, iparam);
2973 if (err) {
2974 EnqueueError(*err);
2975 return;
2979 void ClientWebGLContext::PolygonOffset(GLfloat factor, GLfloat units) {
2980 Run<RPROC(PolygonOffset)>(factor, units);
2983 void ClientWebGLContext::SampleCoverage(GLclampf value, WebGLboolean invert) {
2984 Run<RPROC(SampleCoverage)>(value, invert);
2987 void ClientWebGLContext::Scissor(GLint x, GLint y, GLsizei width,
2988 GLsizei height) {
2989 const FuncScope funcScope(*this, "scissor");
2990 if (IsContextLost()) return;
2991 auto& state = State();
2993 if (!ValidateNonNegative("width", width) ||
2994 !ValidateNonNegative("height", height)) {
2995 return;
2998 state.mScissor = {x, y, width, height};
3000 Run<RPROC(Scissor)>(x, y, width, height);
3003 void ClientWebGLContext::StencilFuncSeparate(GLenum face, GLenum func,
3004 GLint ref, GLuint mask) {
3005 Run<RPROC(StencilFuncSeparate)>(face, func, ref, mask);
3008 void ClientWebGLContext::StencilMaskSeparate(GLenum face, GLuint mask) {
3009 Run<RPROC(StencilMaskSeparate)>(face, mask);
3012 void ClientWebGLContext::StencilOpSeparate(GLenum face, GLenum sfail,
3013 GLenum dpfail, GLenum dppass) {
3014 Run<RPROC(StencilOpSeparate)>(face, sfail, dpfail, dppass);
3017 void ClientWebGLContext::Viewport(GLint x, GLint y, GLsizei width,
3018 GLsizei height) {
3019 const FuncScope funcScope(*this, "viewport");
3020 if (IsContextLost()) return;
3021 auto& state = State();
3023 if (!ValidateNonNegative("width", width) ||
3024 !ValidateNonNegative("height", height)) {
3025 return;
3028 state.mViewport = {x, y, width, height};
3030 Run<RPROC(Viewport)>(x, y, width, height);
3033 // ------------------------- Buffer Objects -------------------------
3035 Maybe<const webgl::ErrorInfo> ValidateBindBuffer(
3036 const GLenum target, const webgl::BufferKind curKind) {
3037 if (curKind == webgl::BufferKind::Undefined) return {};
3039 auto requiredKind = webgl::BufferKind::NonIndex;
3040 switch (target) {
3041 case LOCAL_GL_COPY_READ_BUFFER:
3042 case LOCAL_GL_COPY_WRITE_BUFFER:
3043 return {}; // Always ok
3045 case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
3046 requiredKind = webgl::BufferKind::Index;
3047 break;
3049 default:
3050 break;
3053 if (curKind != requiredKind) {
3054 const auto fnKindStr = [&](const webgl::BufferKind kind) {
3055 if (kind == webgl::BufferKind::Index) return "ELEMENT_ARRAY_BUFFER";
3056 return "non-ELEMENT_ARRAY_BUFFER";
3058 const auto info = nsPrintfCString(
3059 "Buffer previously bound to %s cannot be now bound to %s.",
3060 fnKindStr(curKind), fnKindStr(requiredKind));
3061 return Some(
3062 webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION, info.BeginReading()});
3065 return {};
3068 Maybe<webgl::ErrorInfo> CheckBindBufferRange(
3069 const GLenum target, const GLuint index, const bool isBuffer,
3070 const uint64_t offset, const uint64_t size, const webgl::Limits& limits) {
3071 const auto fnSome = [&](const GLenum type, const nsACString& info) {
3072 return Some(webgl::ErrorInfo{type, info.BeginReading()});
3075 switch (target) {
3076 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
3077 if (index >= webgl::kMaxTransformFeedbackSeparateAttribs) {
3078 const auto info = nsPrintfCString(
3079 "`index` (%u) must be less than "
3080 "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS (%u).",
3081 index, webgl::kMaxTransformFeedbackSeparateAttribs);
3082 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3085 if (offset % 4 != 0 || size % 4 != 0) {
3086 const auto info =
3087 nsPrintfCString("`offset` (%" PRIu64 ") and `size` (%" PRIu64
3088 ") must both be aligned to 4 for"
3089 " TRANSFORM_FEEDBACK_BUFFER.",
3090 offset, size);
3091 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3093 break;
3095 case LOCAL_GL_UNIFORM_BUFFER:
3096 if (index >= limits.maxUniformBufferBindings) {
3097 const auto info = nsPrintfCString(
3098 "`index` (%u) must be less than MAX_UNIFORM_BUFFER_BINDINGS (%u).",
3099 index, limits.maxUniformBufferBindings);
3100 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3103 if (offset % limits.uniformBufferOffsetAlignment != 0) {
3104 const auto info =
3105 nsPrintfCString("`offset` (%" PRIu64
3106 ") must be aligned to "
3107 "UNIFORM_BUFFER_OFFSET_ALIGNMENT (%u).",
3108 offset, limits.uniformBufferOffsetAlignment);
3109 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3111 break;
3113 default: {
3114 const auto info =
3115 nsPrintfCString("Unrecognized `target`: 0x%04x", target);
3116 return fnSome(LOCAL_GL_INVALID_ENUM, info);
3120 return {};
3123 // -
3125 void ClientWebGLContext::BindBuffer(const GLenum target,
3126 WebGLBufferJS* const buffer) {
3127 const FuncScope funcScope(*this, "bindBuffer");
3128 if (IsContextLost()) return;
3129 if (buffer && !buffer->ValidateUsable(*this, "buffer")) return;
3131 // -
3132 // Check for INVALID_ENUM
3134 auto& state = State();
3135 auto* slot = &(state.mBoundVao->mIndexBuffer);
3136 if (target != LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
3137 const auto itr = state.mBoundBufferByTarget.find(target);
3138 if (itr == state.mBoundBufferByTarget.end()) {
3139 EnqueueError_ArgEnum("target", target);
3140 return;
3142 slot = &(itr->second);
3145 // -
3147 auto kind = webgl::BufferKind::Undefined;
3148 if (buffer) {
3149 kind = buffer->mKind;
3151 const auto err = ValidateBindBuffer(target, kind);
3152 if (err) {
3153 EnqueueError(err->type, "%s", err->info.c_str());
3154 return;
3157 // -
3158 // Validation complete
3160 if (buffer && buffer->mKind == webgl::BufferKind::Undefined) {
3161 if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
3162 buffer->mKind = webgl::BufferKind::Index;
3163 } else {
3164 buffer->mKind = webgl::BufferKind::NonIndex;
3167 *slot = buffer;
3169 // -
3171 Run<RPROC(BindBuffer)>(target, buffer ? buffer->mId : 0);
3174 // -
3176 void ClientWebGLContext::BindBufferRangeImpl(const GLenum target,
3177 const GLuint index,
3178 WebGLBufferJS* const buffer,
3179 const uint64_t offset,
3180 const uint64_t size) {
3181 if (buffer && !buffer->ValidateUsable(*this, "buffer")) return;
3182 auto& state = State();
3184 // -
3186 const auto& limits = Limits();
3187 auto err =
3188 CheckBindBufferRange(target, index, bool(buffer), offset, size, limits);
3189 if (err) {
3190 EnqueueError(err->type, "%s", err->info.c_str());
3191 return;
3194 // -
3196 auto kind = webgl::BufferKind::Undefined;
3197 if (buffer) {
3198 kind = buffer->mKind;
3200 err = ValidateBindBuffer(target, kind);
3201 if (err) {
3202 EnqueueError(err->type, "%s", err->info.c_str());
3203 return;
3206 if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) {
3207 if (state.mTfActiveAndNotPaused) {
3208 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3209 "Cannot change TRANSFORM_FEEDBACK_BUFFER while "
3210 "TransformFeedback is active and not paused.");
3211 return;
3215 // -
3216 // Validation complete
3218 if (buffer && buffer->mKind == webgl::BufferKind::Undefined) {
3219 buffer->mKind = webgl::BufferKind::NonIndex;
3222 // -
3224 switch (target) {
3225 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
3226 state.mBoundTfo->mAttribBuffers[index] = buffer;
3227 break;
3229 case LOCAL_GL_UNIFORM_BUFFER:
3230 state.mBoundUbos[index] = buffer;
3231 break;
3233 default:
3234 MOZ_CRASH("Bad `target`");
3236 state.mBoundBufferByTarget[target] = buffer;
3238 // -
3240 Run<RPROC(BindBufferRange)>(target, index, buffer ? buffer->mId : 0, offset,
3241 size);
3244 void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset,
3245 const dom::ArrayBufferView& dstData,
3246 GLuint dstElemOffset,
3247 GLuint dstElemCountOverride) {
3248 const FuncScope funcScope(*this, "getBufferSubData");
3249 if (IsContextLost()) return;
3250 const auto notLost =
3251 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
3252 if (!ValidateNonNegative("srcByteOffset", srcByteOffset)) return;
3254 uint8_t* bytes;
3255 size_t byteLen;
3256 if (!ValidateArrayBufferView(dstData, dstElemOffset, dstElemCountOverride,
3257 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
3258 return;
3260 const auto destView = Range<uint8_t>{bytes, byteLen};
3262 const auto& inProcessContext = notLost->inProcess;
3263 if (inProcessContext) {
3264 inProcessContext->GetBufferSubData(target, srcByteOffset, destView);
3265 return;
3268 const auto& child = notLost->outOfProcess;
3269 child->FlushPendingCmds();
3270 mozilla::ipc::Shmem rawShmem;
3271 if (!child->SendGetBufferSubData(target, srcByteOffset, destView.length(),
3272 &rawShmem)) {
3273 return;
3275 const webgl::RaiiShmem shmem{child, rawShmem};
3276 if (!shmem) {
3277 EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "Failed to map in sub data buffer.");
3278 return;
3281 const auto shmemView = shmem.ByteRange();
3282 MOZ_RELEASE_ASSERT(shmemView.length() == 1 + destView.length());
3284 const auto ok = bool(*(shmemView.begin().get()));
3285 const auto srcView =
3286 Range<const uint8_t>{shmemView.begin() + 1, shmemView.end()};
3287 if (ok) {
3288 Memcpy(destView.begin(), srcView.begin(), srcView.length());
3292 ////
3294 void ClientWebGLContext::BufferData(GLenum target, WebGLsizeiptr rawSize,
3295 GLenum usage) {
3296 const FuncScope funcScope(*this, "bufferData");
3297 if (!ValidateNonNegative("size", rawSize)) return;
3299 const auto size = MaybeAs<size_t>(rawSize);
3300 if (!size) {
3301 EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "`size` too large for platform.");
3302 return;
3305 const auto data = RawBuffer<>{*size};
3306 Run<RPROC(BufferData)>(target, data, usage);
3309 void ClientWebGLContext::BufferData(
3310 GLenum target, const dom::Nullable<dom::ArrayBuffer>& maybeSrc,
3311 GLenum usage) {
3312 const FuncScope funcScope(*this, "bufferData");
3313 if (!ValidateNonNull("src", maybeSrc)) return;
3314 const auto& src = maybeSrc.Value();
3316 src.ComputeState();
3317 const auto range = Range<const uint8_t>{src.Data(), src.Length()};
3318 Run<RPROC(BufferData)>(target, RawBuffer<>(range), usage);
3321 void ClientWebGLContext::BufferData(GLenum target,
3322 const dom::ArrayBufferView& src,
3323 GLenum usage, GLuint srcElemOffset,
3324 GLuint srcElemCountOverride) {
3325 const FuncScope funcScope(*this, "bufferData");
3326 uint8_t* bytes;
3327 size_t byteLen;
3328 if (!ValidateArrayBufferView(src, srcElemOffset, srcElemCountOverride,
3329 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
3330 return;
3332 const auto range = Range<const uint8_t>{bytes, byteLen};
3333 Run<RPROC(BufferData)>(target, RawBuffer<>(range), usage);
3336 void ClientWebGLContext::RawBufferData(GLenum target, const uint8_t* srcBytes,
3337 size_t srcLen, GLenum usage) {
3338 const FuncScope funcScope(*this, "bufferData");
3340 const auto srcBuffer =
3341 srcBytes ? RawBuffer<>({srcBytes, srcLen}) : RawBuffer<>(srcLen);
3342 Run<RPROC(BufferData)>(target, srcBuffer, usage);
3345 ////
3347 void ClientWebGLContext::BufferSubData(GLenum target,
3348 WebGLsizeiptr dstByteOffset,
3349 const dom::ArrayBuffer& src) {
3350 const FuncScope funcScope(*this, "bufferSubData");
3351 src.ComputeState();
3352 const auto range = Range<const uint8_t>{src.Data(), src.Length()};
3353 Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(range));
3356 void ClientWebGLContext::BufferSubData(GLenum target,
3357 WebGLsizeiptr dstByteOffset,
3358 const dom::ArrayBufferView& src,
3359 GLuint srcElemOffset,
3360 GLuint srcElemCountOverride) {
3361 const FuncScope funcScope(*this, "bufferSubData");
3362 uint8_t* bytes;
3363 size_t byteLen;
3364 if (!ValidateArrayBufferView(src, srcElemOffset, srcElemCountOverride,
3365 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
3366 return;
3368 const auto range = Range<const uint8_t>{bytes, byteLen};
3369 Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(range));
3372 void ClientWebGLContext::CopyBufferSubData(GLenum readTarget,
3373 GLenum writeTarget,
3374 GLintptr readOffset,
3375 GLintptr writeOffset,
3376 GLsizeiptr size) {
3377 const FuncScope funcScope(*this, "copyBufferSubData");
3378 if (!ValidateNonNegative("readOffset", readOffset) ||
3379 !ValidateNonNegative("writeOffset", writeOffset) ||
3380 !ValidateNonNegative("size", size)) {
3381 return;
3383 Run<RPROC(CopyBufferSubData)>(
3384 readTarget, writeTarget, static_cast<uint64_t>(readOffset),
3385 static_cast<uint64_t>(writeOffset), static_cast<uint64_t>(size));
3388 // -------------------------- Framebuffer Objects --------------------------
3390 void ClientWebGLContext::BindFramebuffer(const GLenum target,
3391 WebGLFramebufferJS* const fb) {
3392 const FuncScope funcScope(*this, "bindFramebuffer");
3393 if (IsContextLost()) return;
3394 if (fb && !fb->ValidateUsable(*this, "fb")) return;
3396 if (!IsFramebufferTarget(mIsWebGL2, target)) {
3397 EnqueueError_ArgEnum("target", target);
3398 return;
3401 // -
3403 auto& state = State();
3405 switch (target) {
3406 case LOCAL_GL_FRAMEBUFFER:
3407 state.mBoundDrawFb = fb;
3408 state.mBoundReadFb = fb;
3409 break;
3411 case LOCAL_GL_DRAW_FRAMEBUFFER:
3412 state.mBoundDrawFb = fb;
3413 break;
3414 case LOCAL_GL_READ_FRAMEBUFFER:
3415 state.mBoundReadFb = fb;
3416 break;
3418 default:
3419 MOZ_CRASH();
3422 // -
3424 if (fb) {
3425 fb->mHasBeenBound = true;
3428 Run<RPROC(BindFramebuffer)>(target, fb ? fb->mId : 0);
3431 // -
3433 void ClientWebGLContext::FramebufferTexture2D(GLenum target, GLenum attachSlot,
3434 GLenum bindImageTarget,
3435 WebGLTextureJS* const tex,
3436 GLint mipLevel) const {
3437 const FuncScope funcScope(*this, "framebufferTexture2D");
3438 if (IsContextLost()) return;
3440 const auto bindTexTarget = ImageToTexTarget(bindImageTarget);
3441 uint32_t zLayer = 0;
3442 switch (bindTexTarget) {
3443 case LOCAL_GL_TEXTURE_2D:
3444 break;
3445 case LOCAL_GL_TEXTURE_CUBE_MAP:
3446 zLayer = bindImageTarget - LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X;
3447 break;
3448 default:
3449 EnqueueError_ArgEnum("imageTarget", bindImageTarget);
3450 return;
3453 if (!mIsWebGL2 &&
3454 !IsExtensionEnabled(WebGLExtensionID::OES_fbo_render_mipmap)) {
3455 if (mipLevel != 0) {
3456 EnqueueError(LOCAL_GL_INVALID_VALUE,
3457 "mipLevel != 0 requires OES_fbo_render_mipmap.");
3458 return;
3462 FramebufferAttach(target, attachSlot, bindImageTarget, nullptr, tex,
3463 static_cast<uint32_t>(mipLevel), zLayer, 0);
3466 Maybe<webgl::ErrorInfo> CheckFramebufferAttach(const GLenum bindImageTarget,
3467 const GLenum curTexTarget,
3468 const uint32_t mipLevel,
3469 const uint32_t zLayerBase,
3470 const uint32_t zLayerCount,
3471 const webgl::Limits& limits) {
3472 if (!curTexTarget) {
3473 return Some(
3474 webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3475 "`tex` not yet bound. Call bindTexture first."});
3478 auto texTarget = curTexTarget;
3479 if (bindImageTarget) {
3480 // FramebufferTexture2D
3481 const auto bindTexTarget = ImageToTexTarget(bindImageTarget);
3482 if (curTexTarget != bindTexTarget) {
3483 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3484 "`tex` cannot be rebound to a new target."});
3487 switch (bindTexTarget) {
3488 case LOCAL_GL_TEXTURE_2D:
3489 case LOCAL_GL_TEXTURE_CUBE_MAP:
3490 break;
3491 default:
3492 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_ENUM,
3493 "`tex` must have been bound to target "
3494 "TEXTURE_2D or TEXTURE_CUBE_MAP."});
3496 texTarget = bindTexTarget;
3497 } else {
3498 // FramebufferTextureLayer/Multiview
3499 switch (curTexTarget) {
3500 case LOCAL_GL_TEXTURE_2D_ARRAY:
3501 case LOCAL_GL_TEXTURE_3D:
3502 break;
3503 default:
3504 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3505 "`tex` must have been bound to target "
3506 "TEXTURE_2D_ARRAY or TEXTURE_3D."});
3509 MOZ_ASSERT(texTarget);
3510 uint32_t maxSize;
3511 uint32_t maxZ;
3512 switch (texTarget) {
3513 case LOCAL_GL_TEXTURE_2D:
3514 maxSize = limits.maxTex2dSize;
3515 maxZ = 1;
3516 break;
3517 case LOCAL_GL_TEXTURE_CUBE_MAP:
3518 maxSize = limits.maxTexCubeSize;
3519 maxZ = 6;
3520 break;
3521 case LOCAL_GL_TEXTURE_2D_ARRAY:
3522 maxSize = limits.maxTex2dSize;
3523 maxZ = limits.maxTexArrayLayers;
3524 break;
3525 case LOCAL_GL_TEXTURE_3D:
3526 maxSize = limits.maxTex3dSize;
3527 maxZ = limits.maxTex3dSize;
3528 break;
3529 default:
3530 MOZ_CRASH();
3532 const auto maxMipLevel = FloorLog2(maxSize);
3533 if (mipLevel > maxMipLevel) {
3534 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE,
3535 "`mipLevel` too large for texture target."});
3537 const auto requiredZLayers = CheckedInt<uint32_t>(zLayerBase) + zLayerCount;
3538 if (!requiredZLayers.isValid() || requiredZLayers.value() > maxZ) {
3539 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE,
3540 "`zLayer` too large for texture target."});
3543 return {};
3546 void ClientWebGLContext::FramebufferAttach(
3547 const GLenum target, const GLenum attachSlot, const GLenum bindImageTarget,
3548 WebGLRenderbufferJS* const rb, WebGLTextureJS* const tex,
3549 const uint32_t mipLevel, const uint32_t zLayerBase,
3550 const uint32_t numViewLayers) const {
3551 if (rb && !rb->ValidateUsable(*this, "rb")) return;
3552 if (tex && !tex->ValidateUsable(*this, "tex")) return;
3553 const auto& state = State();
3554 const auto& limits = Limits();
3556 if (!IsFramebufferTarget(mIsWebGL2, target)) {
3557 EnqueueError_ArgEnum("target", target);
3558 return;
3560 auto fb = state.mBoundDrawFb;
3561 if (target == LOCAL_GL_READ_FRAMEBUFFER) {
3562 fb = state.mBoundReadFb;
3564 if (!fb) {
3565 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No framebuffer bound.");
3566 return;
3569 if (fb->mOpaque) {
3570 EnqueueError(
3571 LOCAL_GL_INVALID_OPERATION,
3572 "An opaque framebuffer's attachments cannot be inspected or changed.");
3573 return;
3576 // -
3577 // Multiview-specific validation skipped by Host.
3579 if (tex && numViewLayers) {
3580 if (tex->mTarget != LOCAL_GL_TEXTURE_2D_ARRAY) {
3581 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3582 "`tex` must have been bound to target TEXTURE_2D_ARRAY.");
3583 return;
3585 if (numViewLayers > limits.maxMultiviewLayers) {
3586 EnqueueError(LOCAL_GL_INVALID_VALUE,
3587 "`numViews` (%u) must be <= MAX_VIEWS (%u).", numViewLayers,
3588 limits.maxMultiviewLayers);
3589 return;
3593 // -
3595 webgl::ObjectId id = 0;
3596 if (tex) {
3597 auto zLayerCount = numViewLayers;
3598 if (!zLayerCount) {
3599 zLayerCount = 1;
3601 const auto err =
3602 CheckFramebufferAttach(bindImageTarget, tex->mTarget, mipLevel,
3603 zLayerBase, zLayerCount, limits);
3604 if (err) {
3605 EnqueueError(err->type, "%s", err->info.c_str());
3606 return;
3608 id = tex->mId;
3609 } else if (rb) {
3610 if (!rb->mHasBeenBound) {
3611 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3612 "`rb` has not yet been bound with BindRenderbuffer.");
3613 return;
3615 id = rb->mId;
3618 // Ready!
3619 // But DEPTH_STENCIL in webgl2 is actually two slots!
3621 const auto fnAttachTo = [&](const GLenum actualAttachSlot) {
3622 const auto slot = fb->GetAttachment(actualAttachSlot);
3623 if (!slot) {
3624 EnqueueError_ArgEnum("attachment", actualAttachSlot);
3625 return;
3628 slot->rb = rb;
3629 slot->tex = tex;
3631 Run<RPROC(FramebufferAttach)>(target, actualAttachSlot, bindImageTarget, id,
3632 mipLevel, zLayerBase, numViewLayers);
3635 if (mIsWebGL2 && attachSlot == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
3636 fnAttachTo(LOCAL_GL_DEPTH_ATTACHMENT);
3637 fnAttachTo(LOCAL_GL_STENCIL_ATTACHMENT);
3638 } else {
3639 fnAttachTo(attachSlot);
3642 if (bindImageTarget) {
3643 if (rb) {
3644 rb->mHasBeenBound = true;
3646 if (tex) {
3647 tex->mTarget = ImageToTexTarget(bindImageTarget);
3652 // -
3654 void ClientWebGLContext::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1,
3655 GLint srcY1, GLint dstX0, GLint dstY0,
3656 GLint dstX1, GLint dstY1,
3657 GLbitfield mask, GLenum filter) {
3658 Run<RPROC(BlitFramebuffer)>(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1,
3659 dstY1, mask, filter);
3661 AfterDrawCall();
3664 void ClientWebGLContext::InvalidateFramebuffer(
3665 GLenum target, const dom::Sequence<GLenum>& attachments,
3666 ErrorResult& unused) {
3667 const auto range = MakeRange(attachments);
3668 const auto& buffer = RawBufferView(range);
3669 Run<RPROC(InvalidateFramebuffer)>(target, buffer);
3671 // Never invalidate the backbuffer, so never needs AfterDrawCall.
3674 void ClientWebGLContext::InvalidateSubFramebuffer(
3675 GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y,
3676 GLsizei width, GLsizei height, ErrorResult& unused) {
3677 const auto range = MakeRange(attachments);
3678 const auto& buffer = RawBufferView(range);
3679 Run<RPROC(InvalidateSubFramebuffer)>(target, buffer, x, y, width, height);
3681 // Never invalidate the backbuffer, so never needs AfterDrawCall.
3684 void ClientWebGLContext::ReadBuffer(GLenum mode) {
3685 Run<RPROC(ReadBuffer)>(mode);
3688 // ----------------------- Renderbuffer objects -----------------------
3690 void ClientWebGLContext::BindRenderbuffer(const GLenum target,
3691 WebGLRenderbufferJS* const rb) {
3692 const FuncScope funcScope(*this, "bindRenderbuffer");
3693 if (IsContextLost()) return;
3694 if (rb && !rb->ValidateUsable(*this, "rb")) return;
3695 auto& state = State();
3697 if (target != LOCAL_GL_RENDERBUFFER) {
3698 EnqueueError_ArgEnum("target", target);
3699 return;
3702 state.mBoundRb = rb;
3703 if (rb) {
3704 rb->mHasBeenBound = true;
3708 void ClientWebGLContext::RenderbufferStorageMultisample(GLenum target,
3709 GLsizei samples,
3710 GLenum internalFormat,
3711 GLsizei width,
3712 GLsizei height) const {
3713 const FuncScope funcScope(*this, "renderbufferStorageMultisample");
3714 if (IsContextLost()) return;
3716 if (target != LOCAL_GL_RENDERBUFFER) {
3717 EnqueueError_ArgEnum("target", target);
3718 return;
3721 const auto& state = State();
3723 const auto& rb = state.mBoundRb;
3724 if (!rb) {
3725 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No renderbuffer bound");
3726 return;
3729 if (!ValidateNonNegative("width", width) ||
3730 !ValidateNonNegative("height", height) ||
3731 !ValidateNonNegative("samples", samples)) {
3732 return;
3735 if (internalFormat == LOCAL_GL_DEPTH_STENCIL && samples > 0) {
3736 // While our backend supports it trivially, the spec forbids it.
3737 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3738 "WebGL 1's DEPTH_STENCIL format may not be multisampled. Use "
3739 "DEPTH24_STENCIL8 when `samples > 0`.");
3740 return;
3743 Run<RPROC(RenderbufferStorageMultisample)>(
3744 rb->mId, static_cast<uint32_t>(samples), internalFormat,
3745 static_cast<uint32_t>(width), static_cast<uint32_t>(height));
3748 // --------------------------- Texture objects ---------------------------
3750 void ClientWebGLContext::ActiveTexture(const GLenum texUnitEnum) {
3751 const FuncScope funcScope(*this, "activeTexture");
3752 if (IsContextLost()) return;
3754 if (texUnitEnum < LOCAL_GL_TEXTURE0) {
3755 EnqueueError(LOCAL_GL_INVALID_VALUE,
3756 "`texture` (0x%04x) must be >= TEXTURE0 (0x%04x).",
3757 texUnitEnum, LOCAL_GL_TEXTURE0);
3758 return;
3761 const auto texUnit = texUnitEnum - LOCAL_GL_TEXTURE0;
3763 auto& state = State();
3764 if (texUnit >= state.mTexUnits.size()) {
3765 EnqueueError(LOCAL_GL_INVALID_VALUE,
3766 "TEXTURE%u must be < MAX_COMBINED_TEXTURE_IMAGE_UNITS (%zu).",
3767 texUnit, state.mTexUnits.size());
3768 return;
3773 state.mActiveTexUnit = texUnit;
3774 Run<RPROC(ActiveTexture)>(texUnit);
3777 static bool IsTexTarget(const GLenum texTarget, const bool webgl2) {
3778 switch (texTarget) {
3779 case LOCAL_GL_TEXTURE_2D:
3780 case LOCAL_GL_TEXTURE_CUBE_MAP:
3781 return true;
3783 case LOCAL_GL_TEXTURE_2D_ARRAY:
3784 case LOCAL_GL_TEXTURE_3D:
3785 return webgl2;
3787 default:
3788 return false;
3792 void ClientWebGLContext::BindTexture(const GLenum texTarget,
3793 WebGLTextureJS* const tex) {
3794 const FuncScope funcScope(*this, "bindTexture");
3795 if (IsContextLost()) return;
3796 if (tex && !tex->ValidateUsable(*this, "tex")) return;
3798 if (!IsTexTarget(texTarget, mIsWebGL2)) {
3799 EnqueueError_ArgEnum("texTarget", texTarget);
3800 return;
3803 if (tex && tex->mTarget) {
3804 if (texTarget != tex->mTarget) {
3805 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3806 "Texture previously bound to %s cannot be bound now to %s.",
3807 EnumString(tex->mTarget).c_str(),
3808 EnumString(texTarget).c_str());
3809 return;
3813 auto& state = State();
3814 auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
3815 texUnit.texByTarget[texTarget] = tex;
3816 if (tex) {
3817 tex->mTarget = texTarget;
3820 Run<RPROC(BindTexture)>(texTarget, tex ? tex->mId : 0);
3823 void ClientWebGLContext::GenerateMipmap(GLenum texTarget) const {
3824 Run<RPROC(GenerateMipmap)>(texTarget);
3827 void ClientWebGLContext::GetTexParameter(
3828 JSContext* cx, GLenum texTarget, GLenum pname,
3829 JS::MutableHandle<JS::Value> retval) const {
3830 retval.set(JS::NullValue());
3831 const FuncScope funcScope(*this, "getTexParameter");
3832 if (IsContextLost()) return;
3833 auto& state = State();
3835 auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
3837 const auto& tex = Find(texUnit.texByTarget, texTarget, nullptr);
3838 if (!tex) {
3839 if (!IsTexTarget(texTarget, mIsWebGL2)) {
3840 EnqueueError_ArgEnum("texTarget", texTarget);
3841 } else {
3842 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No texture bound to %s[%u].",
3843 EnumString(texTarget).c_str(), state.mActiveTexUnit);
3845 return;
3848 const auto maybe = [&]() {
3849 const auto& inProcess = mNotLost->inProcess;
3850 if (inProcess) {
3851 return inProcess->GetTexParameter(tex->mId, pname);
3853 const auto& child = mNotLost->outOfProcess;
3854 child->FlushPendingCmds();
3855 Maybe<double> ret;
3856 if (!child->SendGetTexParameter(tex->mId, pname, &ret)) {
3857 ret.reset();
3859 return ret;
3860 }();
3862 if (maybe) {
3863 switch (pname) {
3864 case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
3865 retval.set(JS::BooleanValue(*maybe));
3866 break;
3868 default:
3869 retval.set(JS::NumberValue(*maybe));
3870 break;
3875 void ClientWebGLContext::TexParameterf(GLenum texTarget, GLenum pname,
3876 GLfloat param) {
3877 Run<RPROC(TexParameter_base)>(texTarget, pname, FloatOrInt(param));
3880 void ClientWebGLContext::TexParameteri(GLenum texTarget, GLenum pname,
3881 GLint param) {
3882 Run<RPROC(TexParameter_base)>(texTarget, pname, FloatOrInt(param));
3885 ////////////////////////////////////
3887 static GLenum JSTypeMatchUnpackTypeError(GLenum unpackType,
3888 js::Scalar::Type jsType) {
3889 bool matches = false;
3890 switch (unpackType) {
3891 case LOCAL_GL_BYTE:
3892 matches = (jsType == js::Scalar::Type::Int8);
3893 break;
3895 case LOCAL_GL_UNSIGNED_BYTE:
3896 matches = (jsType == js::Scalar::Type::Uint8 ||
3897 jsType == js::Scalar::Type::Uint8Clamped);
3898 break;
3900 case LOCAL_GL_SHORT:
3901 matches = (jsType == js::Scalar::Type::Int16);
3902 break;
3904 case LOCAL_GL_UNSIGNED_SHORT:
3905 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
3906 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
3907 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
3908 case LOCAL_GL_HALF_FLOAT:
3909 case LOCAL_GL_HALF_FLOAT_OES:
3910 matches = (jsType == js::Scalar::Type::Uint16);
3911 break;
3913 case LOCAL_GL_INT:
3914 matches = (jsType == js::Scalar::Type::Int32);
3915 break;
3917 case LOCAL_GL_UNSIGNED_INT:
3918 case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
3919 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
3920 case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
3921 case LOCAL_GL_UNSIGNED_INT_24_8:
3922 matches = (jsType == js::Scalar::Type::Uint32);
3923 break;
3925 case LOCAL_GL_FLOAT:
3926 matches = (jsType == js::Scalar::Type::Float32);
3927 break;
3929 case LOCAL_GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
3930 matches = false; // No valid jsType, but we allow uploads with null.
3931 break;
3933 default:
3934 return LOCAL_GL_INVALID_ENUM;
3936 if (!matches) return LOCAL_GL_INVALID_OPERATION;
3937 return 0;
3940 /////////////////////////////////////////////////
3942 static inline uvec2 CastUvec2(const ivec2& val) {
3943 return {static_cast<uint32_t>(val.x), static_cast<uint32_t>(val.y)};
3946 static inline uvec3 CastUvec3(const ivec3& val) {
3947 return {static_cast<uint32_t>(val.x), static_cast<uint32_t>(val.y),
3948 static_cast<uint32_t>(val.z)};
3951 template <typename T>
3952 Range<T> SubRange(const Range<T>& full, const size_t offset,
3953 const size_t length) {
3954 const auto newBegin = full.begin() + offset;
3955 return Range<T>{newBegin, newBegin + length};
3958 static inline size_t SizeOfViewElem(const dom::ArrayBufferView& view) {
3959 const auto& elemType = view.Type();
3960 if (elemType == js::Scalar::MaxTypedArrayViewType) // DataViews.
3961 return 1;
3963 return js::Scalar::byteSize(elemType);
3966 Maybe<Range<const uint8_t>> GetRangeFromView(const dom::ArrayBufferView& view,
3967 GLuint elemOffset,
3968 GLuint elemCountOverride) {
3969 const auto byteRange = MakeRangeAbv(view); // In bytes.
3970 const auto bytesPerElem = SizeOfViewElem(view);
3972 auto elemCount = byteRange.length() / bytesPerElem;
3973 if (elemOffset > elemCount) return {};
3974 elemCount -= elemOffset;
3976 if (elemCountOverride) {
3977 if (elemCountOverride > elemCount) return {};
3978 elemCount = elemCountOverride;
3980 const auto subrange =
3981 SubRange(byteRange, elemOffset * bytesPerElem, elemCount * bytesPerElem);
3982 return Some(subrange);
3985 // -
3987 static bool IsTexTargetForDims(const GLenum texTarget, const bool webgl2,
3988 const uint8_t funcDims) {
3989 if (!IsTexTarget(texTarget, webgl2)) return false;
3990 switch (texTarget) {
3991 case LOCAL_GL_TEXTURE_2D:
3992 case LOCAL_GL_TEXTURE_CUBE_MAP:
3993 return funcDims == 2;
3995 default:
3996 return funcDims == 3;
4000 void ClientWebGLContext::TexStorage(uint8_t funcDims, GLenum texTarget,
4001 GLsizei levels, GLenum internalFormat,
4002 const ivec3& size) const {
4003 const FuncScope funcScope(*this, "texStorage[23]D");
4004 if (IsContextLost()) return;
4005 if (!IsTexTargetForDims(texTarget, mIsWebGL2, funcDims)) {
4006 EnqueueError_ArgEnum("texTarget", texTarget);
4007 return;
4009 Run<RPROC(TexStorage)>(texTarget, static_cast<uint32_t>(levels),
4010 internalFormat, CastUvec3(size));
4013 namespace webgl {
4014 // TODO: Move these definitions into statics here.
4015 Maybe<webgl::TexUnpackBlobDesc> FromImageBitmap(
4016 GLenum target, Maybe<uvec3> size, const dom::ImageBitmap& imageBitmap,
4017 ErrorResult* const out_rv);
4019 Maybe<webgl::TexUnpackBlobDesc> FromOffscreenCanvas(
4020 const ClientWebGLContext&, GLenum target, Maybe<uvec3> size,
4021 const dom::OffscreenCanvas& src, ErrorResult* const out_error);
4023 Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext&,
4024 GLenum target, Maybe<uvec3> size,
4025 const dom::Element& src,
4026 ErrorResult* const out_error);
4027 } // namespace webgl
4029 // -
4031 void webgl::TexUnpackBlobDesc::Shrink(const webgl::PackingInfo& pi) {
4032 if (cpuData) {
4033 if (!size.x || !size.y || !size.z) return;
4035 const auto unpackRes = ExplicitUnpacking(pi, {});
4036 if (!unpackRes.isOk()) {
4037 return;
4039 const auto& unpack = unpackRes.inspect();
4041 const auto bytesUpperBound =
4042 CheckedInt<size_t>(unpack.metrics.bytesPerRowStride) *
4043 unpack.metrics.totalRows;
4044 if (bytesUpperBound.isValid()) {
4045 cpuData->Shrink(bytesUpperBound.value());
4050 // -
4052 void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget,
4053 GLint level, GLenum respecFormat,
4054 const ivec3& offset,
4055 const Maybe<ivec3>& isize, GLint border,
4056 const webgl::PackingInfo& pi,
4057 const TexImageSource& src) const {
4058 const FuncScope funcScope(*this, "tex(Sub)Image[23]D");
4059 if (IsContextLost()) return;
4060 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
4061 EnqueueError_ArgEnum("imageTarget", imageTarget);
4062 return;
4064 if (border != 0) {
4065 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
4066 return;
4069 Maybe<uvec3> size;
4070 if (isize) {
4071 size = Some(CastUvec3(isize.value()));
4074 // -
4076 // Demarcate the region within which GC is disallowed. Typed arrays can move
4077 // their data during a GC, so this will allow the rooting hazard analysis to
4078 // report if a GC is possible while any data pointers extracted from the
4079 // typed array are still live.
4080 dom::Uint8ClampedArray scopedArr;
4081 const auto reset = MakeScopeExit([&] {
4082 scopedArr.Reset(); // (For the hazard analysis) Done with the data.
4085 // -
4086 bool isDataUpload = false;
4087 auto desc = [&]() -> Maybe<webgl::TexUnpackBlobDesc> {
4088 if (src.mPboOffset) {
4089 isDataUpload = true;
4090 const auto offset = static_cast<uint64_t>(*src.mPboOffset);
4091 return Some(webgl::TexUnpackBlobDesc{imageTarget,
4092 size.value(),
4093 gfxAlphaType::NonPremult,
4095 Some(offset)});
4098 if (src.mView) {
4099 isDataUpload = true;
4100 const auto& view = *src.mView;
4101 const auto& jsType = view.Type();
4102 const auto err = JSTypeMatchUnpackTypeError(pi.type, jsType);
4103 switch (err) {
4104 case LOCAL_GL_INVALID_ENUM:
4105 EnqueueError_ArgEnum("unpackType", pi.type);
4106 return {};
4107 case LOCAL_GL_INVALID_OPERATION:
4108 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4109 "ArrayBufferView type %s not compatible with `type` %s.",
4110 name(jsType), EnumString(pi.type).c_str());
4111 return {};
4112 default:
4113 break;
4116 const auto range = GetRangeFromView(view, src.mViewElemOffset,
4117 src.mViewElemLengthOverride);
4118 if (!range) {
4119 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`source` too small.");
4120 return {};
4122 return Some(webgl::TexUnpackBlobDesc{imageTarget,
4123 size.value(),
4124 gfxAlphaType::NonPremult,
4125 Some(RawBuffer<>{*range}),
4126 {}});
4129 if (src.mImageBitmap) {
4130 return webgl::FromImageBitmap(imageTarget, size, *(src.mImageBitmap),
4131 src.mOut_error);
4134 if (src.mImageData) {
4135 const auto& imageData = *src.mImageData;
4136 MOZ_RELEASE_ASSERT(scopedArr.Init(imageData.GetDataObject()));
4137 scopedArr.ComputeState();
4138 const auto dataSize = scopedArr.Length();
4139 const auto data = reinterpret_cast<uint8_t*>(scopedArr.Data());
4140 if (!data) {
4141 // Neutered, e.g. via Transfer
4142 EnqueueError(LOCAL_GL_INVALID_VALUE,
4143 "ImageData.data.buffer is Detached. (Maybe you Transfered "
4144 "it to a Worker?");
4145 return {};
4148 // -
4150 const gfx::IntSize imageSize(imageData.Width(), imageData.Height());
4151 const auto sizeFromDims =
4152 CheckedInt<size_t>(imageSize.width) * imageSize.height * 4;
4153 MOZ_RELEASE_ASSERT(sizeFromDims.isValid() &&
4154 sizeFromDims.value() == dataSize);
4156 const RefPtr<gfx::DataSourceSurface> surf =
4157 gfx::Factory::CreateWrappingDataSourceSurface(
4158 data, imageSize.width * 4, imageSize,
4159 gfx::SurfaceFormat::R8G8B8A8);
4160 MOZ_ASSERT(surf);
4162 // -
4164 const auto imageUSize = *uvec2::FromSize(imageSize);
4165 const auto concreteSize =
4166 size.valueOr(uvec3{imageUSize.x, imageUSize.y, 1});
4168 // WhatWG "HTML Living Standard" (30 October 2015):
4169 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned
4170 // as non-premultiplied alpha values."
4171 return Some(webgl::TexUnpackBlobDesc{imageTarget,
4172 concreteSize,
4173 gfxAlphaType::NonPremult,
4176 Some(imageUSize),
4177 nullptr,
4179 surf});
4182 if (src.mOffscreenCanvas) {
4183 return webgl::FromOffscreenCanvas(
4184 *this, imageTarget, size, *(src.mOffscreenCanvas), src.mOut_error);
4187 if (src.mDomElem) {
4188 return webgl::FromDomElem(*this, imageTarget, size, *(src.mDomElem),
4189 src.mOut_error);
4192 return Some(webgl::TexUnpackBlobDesc{
4193 imageTarget, size.value(), gfxAlphaType::NonPremult, {}, {}});
4194 }();
4195 if (!desc) {
4196 return;
4199 // -
4201 const auto& rawUnpacking = State().mPixelUnpackState;
4203 auto defaultSubrectState = webgl::PixelPackingState{};
4204 defaultSubrectState.alignmentInTypeElems =
4205 rawUnpacking.alignmentInTypeElems;
4206 const bool isSubrect = (rawUnpacking != defaultSubrectState);
4207 if (isDataUpload && isSubrect) {
4208 if (rawUnpacking.flipY || rawUnpacking.premultiplyAlpha) {
4209 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4210 "Non-DOM-Element uploads with alpha-premult"
4211 " or y-flip do not support subrect selection.");
4212 return;
4216 desc->unpacking = rawUnpacking;
4218 if (desc->structuredSrcSize) {
4219 // WebGL 2 spec:
4220 // ### 5.35 Pixel store parameters for uploads from TexImageSource
4221 // UNPACK_ALIGNMENT and UNPACK_ROW_LENGTH are ignored.
4222 const auto& elemSize = *desc->structuredSrcSize;
4223 desc->unpacking.alignmentInTypeElems = 1;
4224 desc->unpacking.rowLength = elemSize.x;
4226 if (!desc->unpacking.rowLength) {
4227 desc->unpacking.rowLength = desc->size.x;
4229 if (!desc->unpacking.imageHeight) {
4230 desc->unpacking.imageHeight = desc->size.y;
4233 // -
4235 mozilla::ipc::Shmem* pShmem = nullptr;
4237 if (desc->sd) {
4238 const auto& sd = *(desc->sd);
4239 const auto sdType = sd.type();
4240 const auto& contextInfo = mNotLost->info;
4242 const auto fallbackReason = [&]() -> Maybe<std::string> {
4243 auto fallbackReason = BlitPreventReason(level, offset, pi, *desc);
4244 if (fallbackReason) return fallbackReason;
4246 const bool canUploadViaSd = contextInfo.uploadableSdTypes[sdType];
4247 if (!canUploadViaSd) {
4248 const nsPrintfCString msg(
4249 "Fast uploads for resource type %i not implemented.", int(sdType));
4250 return Some(ToString(msg));
4253 if (sdType == layers::SurfaceDescriptor::TSurfaceDescriptorBuffer) {
4254 const auto& sdb = sd.get_SurfaceDescriptorBuffer();
4255 const auto& data = sdb.data();
4256 if (data.type() == layers::MemoryOrShmem::TShmem) {
4257 pShmem = &data.get_Shmem();
4258 } else {
4259 return Some(
4260 std::string{"SurfaceDescriptorBuffer data is not Shmem."});
4264 if (sdType == layers::SurfaceDescriptor::TSurfaceDescriptorD3D10) {
4265 const auto& sdD3D = sd.get_SurfaceDescriptorD3D10();
4266 const auto& inProcess = mNotLost->inProcess;
4267 if (sdD3D.gpuProcessTextureId().isSome() && inProcess) {
4268 return Some(
4269 std::string{"gpuProcessTextureId works only in GPU process."});
4273 if (StaticPrefs::webgl_disable_DOM_blit_uploads()) {
4274 return Some(std::string{"DOM blit uploads are disabled."});
4276 return {};
4277 }();
4279 if (fallbackReason) {
4280 EnqueuePerfWarning("Missed GPU-copy fast-path: %s",
4281 fallbackReason->c_str());
4283 const auto& image = desc->image;
4284 const RefPtr<gfx::SourceSurface> surf = image->GetAsSourceSurface();
4285 if (surf) {
4286 // WARNING: OSX can lose our MakeCurrent here.
4287 desc->dataSurf = surf->GetDataSurface();
4289 if (!desc->dataSurf) {
4290 EnqueueError(LOCAL_GL_OUT_OF_MEMORY,
4291 "Failed to retrieve source bytes for CPU upload.");
4292 return;
4294 desc->sd = Nothing();
4297 desc->image = nullptr;
4299 desc->Shrink(pi);
4301 // -
4303 const bool doInlineUpload = !desc->sd;
4304 // Why always de-inline SDs here?
4305 // 1. This way we always send SDs down the same handling path, which
4306 // should keep things from breaking if things flip between paths because of
4307 // what we get handed by SurfaceFromElement etc.
4308 // 2. We don't actually always grab strong-refs to the resources in the SDs,
4309 // so we should try to use them sooner rather than later. Yes we should fix
4310 // this, but for now let's give the SDs the best chance of lucking out, eh?
4311 // :)
4312 // 3. It means we don't need to write QueueParamTraits<SurfaceDescriptor>.
4313 if (doInlineUpload) {
4314 // We definitely want e.g. TexImage(PBO) here.
4315 Run<RPROC(TexImage)>(static_cast<uint32_t>(level), respecFormat,
4316 CastUvec3(offset), pi, std::move(*desc));
4317 } else {
4318 // We can't handle shmems like SurfaceDescriptorBuffer inline, so use ipdl.
4319 const auto& inProcess = mNotLost->inProcess;
4320 if (inProcess) {
4321 return inProcess->TexImage(static_cast<uint32_t>(level), respecFormat,
4322 CastUvec3(offset), pi, *desc);
4324 const auto& child = mNotLost->outOfProcess;
4325 child->FlushPendingCmds();
4327 // The shmem we're handling was only shared from RDD to Content, and
4328 // immediately on Content receiving it, it was closed! RIP
4329 // Eventually we'll be able to make shmems that can traverse multiple
4330 // endpoints, but for now we need to make a new Content->WebGLParent shmem
4331 // and memcpy into it. We don't use `desc` elsewhere, so just replace the
4332 // Shmem buried within it with one that's valid for WebGLChild->Parent
4333 // transport.
4334 if (pShmem) {
4335 MOZ_ASSERT(desc->sd);
4336 const auto byteCount = pShmem->Size<uint8_t>();
4337 const auto* const src = pShmem->get<uint8_t>();
4338 mozilla::ipc::Shmem shmemForResend;
4339 if (!child->AllocShmem(byteCount, &shmemForResend)) {
4340 NS_WARNING("AllocShmem failed in TexImage");
4341 return;
4343 auto* const dst = shmemForResend.get<uint8_t>();
4344 memcpy(dst, src, byteCount);
4345 *pShmem = shmemForResend;
4348 (void)child->SendTexImage(static_cast<uint32_t>(level), respecFormat,
4349 CastUvec3(offset), pi, std::move(*desc));
4353 void ClientWebGLContext::RawTexImage(uint32_t level, GLenum respecFormat,
4354 uvec3 offset, const webgl::PackingInfo& pi,
4355 webgl::TexUnpackBlobDesc&& desc) const {
4356 const FuncScope funcScope(*this, "tex(Sub)Image[23]D");
4357 if (IsContextLost()) return;
4358 if (desc.sd) {
4359 // Shmems are stored in Buffer surface descriptors. We need to ensure first
4360 // that all queued commands are flushed and then send the Shmem over IPDL.
4361 const auto& sd = *(desc.sd);
4362 if (sd.type() == layers::SurfaceDescriptor::TSurfaceDescriptorBuffer &&
4363 sd.get_SurfaceDescriptorBuffer().data().type() ==
4364 layers::MemoryOrShmem::TShmem) {
4365 const auto& inProcess = mNotLost->inProcess;
4366 if (inProcess) {
4367 inProcess->TexImage(level, respecFormat, offset, pi, desc);
4368 } else {
4369 const auto& child = mNotLost->outOfProcess;
4370 child->FlushPendingCmds();
4371 (void)child->SendTexImage(level, respecFormat, offset, pi,
4372 std::move(desc));
4374 } else {
4375 NS_WARNING(
4376 "RawTexImage with SurfaceDescriptor only supports "
4377 "SurfaceDescriptorBuffer with Shmem");
4379 return;
4382 Run<RPROC(TexImage)>(level, respecFormat, offset, pi, desc);
4385 // -
4387 void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims,
4388 GLenum imageTarget, GLint level,
4389 GLenum format, const ivec3& offset,
4390 const ivec3& isize, GLint border,
4391 const TexImageSource& src,
4392 GLsizei pboImageSize) const {
4393 const FuncScope funcScope(*this, "compressedTex(Sub)Image[23]D");
4394 if (IsContextLost()) return;
4395 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
4396 EnqueueError_ArgEnum("imageTarget", imageTarget);
4397 return;
4399 if (border != 0) {
4400 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
4401 return;
4404 RawBuffer<> range;
4405 Maybe<uint64_t> pboOffset;
4406 if (src.mView) {
4407 const auto maybe = GetRangeFromView(*src.mView, src.mViewElemOffset,
4408 src.mViewElemLengthOverride);
4409 if (!maybe) {
4410 EnqueueError(LOCAL_GL_INVALID_VALUE, "`source` too small.");
4411 return;
4413 range = RawBuffer<>{*maybe};
4414 } else if (src.mPboOffset) {
4415 if (!ValidateNonNegative("offset", *src.mPboOffset)) return;
4416 pboOffset = Some(*src.mPboOffset);
4417 } else {
4418 MOZ_CRASH("impossible");
4421 // We don't need to shrink `range` because valid calls require `range` to
4422 // match requirements exactly.
4424 Run<RPROC(CompressedTexImage)>(
4425 sub, imageTarget, static_cast<uint32_t>(level), format, CastUvec3(offset),
4426 CastUvec3(isize), range, static_cast<uint32_t>(pboImageSize), pboOffset);
4429 void ClientWebGLContext::CopyTexImage(uint8_t funcDims, GLenum imageTarget,
4430 GLint level, GLenum respecFormat,
4431 const ivec3& dstOffset,
4432 const ivec2& srcOffset, const ivec2& size,
4433 GLint border) const {
4434 const FuncScope funcScope(*this, "copy(Sub)Image[23]D");
4435 if (IsContextLost()) return;
4436 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
4437 EnqueueError_ArgEnum("imageTarget", imageTarget);
4438 return;
4440 if (border != 0) {
4441 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
4442 return;
4444 Run<RPROC(CopyTexImage)>(imageTarget, static_cast<uint32_t>(level),
4445 respecFormat, CastUvec3(dstOffset), srcOffset,
4446 CastUvec2(size));
4449 // ------------------- Programs and shaders --------------------------------
4451 void ClientWebGLContext::UseProgram(WebGLProgramJS* const prog) {
4452 const FuncScope funcScope(*this, "useProgram");
4453 if (IsContextLost()) return;
4454 if (prog && !prog->ValidateUsable(*this, "prog")) return;
4456 auto& state = State();
4458 if (state.mTfActiveAndNotPaused) {
4459 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4460 "Transform feedback is active and not paused.");
4461 return;
4464 if (prog) {
4465 const auto& res = GetLinkResult(*prog);
4466 if (!res.success) {
4467 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4468 "Program must be linked successfully.");
4469 return;
4473 // -
4475 state.mCurrentProgram = prog;
4476 state.mProgramKeepAlive = prog ? prog->mKeepAliveWeak.lock() : nullptr;
4477 state.mActiveLinkResult = prog ? prog->mResult : nullptr;
4479 Run<RPROC(UseProgram)>(prog ? prog->mId : 0);
4482 void ClientWebGLContext::ValidateProgram(WebGLProgramJS& prog) const {
4483 const FuncScope funcScope(*this, "validateProgram");
4484 if (IsContextLost()) return;
4485 if (!prog.ValidateUsable(*this, "prog")) return;
4487 prog.mLastValidate = [&]() {
4488 const auto& inProcess = mNotLost->inProcess;
4489 if (inProcess) {
4490 return inProcess->ValidateProgram(prog.mId);
4492 const auto& child = mNotLost->outOfProcess;
4493 child->FlushPendingCmds();
4494 bool ret = {};
4495 if (!child->SendValidateProgram(prog.mId, &ret)) {
4496 ret = {};
4498 return ret;
4499 }();
4502 // ------------------------ Uniforms and attributes ------------------------
4504 Maybe<double> ClientWebGLContext::GetVertexAttribPriv(const GLuint index,
4505 const GLenum pname) {
4506 const auto& inProcess = mNotLost->inProcess;
4507 if (inProcess) {
4508 return inProcess->GetVertexAttrib(index, pname);
4510 const auto& child = mNotLost->outOfProcess;
4511 child->FlushPendingCmds();
4512 Maybe<double> ret;
4513 if (!child->SendGetVertexAttrib(index, pname, &ret)) {
4514 ret.reset();
4516 return ret;
4519 void ClientWebGLContext::GetVertexAttrib(JSContext* cx, GLuint index,
4520 GLenum pname,
4521 JS::MutableHandle<JS::Value> retval,
4522 ErrorResult& rv) {
4523 retval.set(JS::NullValue());
4524 const FuncScope funcScope(*this, "getVertexAttrib");
4525 if (IsContextLost()) return;
4526 const auto& state = State();
4528 const auto& genericAttribs = state.mGenericVertexAttribs;
4529 if (index >= genericAttribs.size()) {
4530 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` (%u) >= MAX_VERTEX_ATTRIBS",
4531 index);
4532 return;
4535 switch (pname) {
4536 case LOCAL_GL_CURRENT_VERTEX_ATTRIB: {
4537 JS::Rooted<JSObject*> obj(cx);
4539 const auto& attrib = genericAttribs[index];
4540 switch (attrib.type) {
4541 case webgl::AttribBaseType::Float:
4542 obj = dom::Float32Array::Create(
4543 cx, this, 4, reinterpret_cast<const float*>(attrib.data.data()));
4544 break;
4545 case webgl::AttribBaseType::Int:
4546 obj = dom::Int32Array::Create(
4547 cx, this, 4,
4548 reinterpret_cast<const int32_t*>(attrib.data.data()));
4549 break;
4550 case webgl::AttribBaseType::Uint:
4551 obj = dom::Uint32Array::Create(
4552 cx, this, 4,
4553 reinterpret_cast<const uint32_t*>(attrib.data.data()));
4554 break;
4555 case webgl::AttribBaseType::Boolean:
4556 MOZ_CRASH("impossible");
4559 if (!obj) {
4560 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
4561 return;
4563 retval.set(JS::ObjectValue(*obj));
4564 return;
4567 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: {
4568 const auto& buffers = state.mBoundVao->mAttribBuffers;
4569 const auto& buffer = buffers[index];
4570 (void)ToJSValueOrNull(cx, buffer, retval);
4571 return;
4574 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER:
4575 // Disallowed from JS, but allowed in Host.
4576 EnqueueError_ArgEnum("pname", pname);
4577 return;
4579 default:
4580 break;
4583 const auto maybe = GetVertexAttribPriv(index, pname);
4584 if (maybe) {
4585 switch (pname) {
4586 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED:
4587 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
4588 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_INTEGER:
4589 retval.set(JS::BooleanValue(*maybe));
4590 break;
4592 default:
4593 retval.set(JS::NumberValue(*maybe));
4594 break;
4599 void ClientWebGLContext::UniformData(const GLenum funcElemType,
4600 const WebGLUniformLocationJS* const loc,
4601 bool transpose,
4602 const Range<const uint8_t>& bytes,
4603 GLuint elemOffset,
4604 GLuint elemCountOverride) const {
4605 const FuncScope funcScope(*this, "uniform setter");
4606 if (IsContextLost()) return;
4608 const auto& activeLinkResult = GetActiveLinkResult();
4609 if (!activeLinkResult) {
4610 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No active linked Program.");
4611 return;
4614 // -
4616 auto availCount = bytes.length() / sizeof(float);
4617 if (elemOffset > availCount) {
4618 EnqueueError(LOCAL_GL_INVALID_VALUE, "`elemOffset` too large for `data`.");
4619 return;
4621 availCount -= elemOffset;
4622 if (elemCountOverride) {
4623 if (elemCountOverride > availCount) {
4624 EnqueueError(LOCAL_GL_INVALID_VALUE,
4625 "`elemCountOverride` too large for `data`.");
4626 return;
4628 availCount = elemCountOverride;
4631 // -
4633 const auto channels = ElemTypeComponents(funcElemType);
4634 if (!availCount || availCount % channels != 0) {
4635 EnqueueError(LOCAL_GL_INVALID_VALUE,
4636 "`values` length (%u) must be a positive "
4637 "integer multiple of size of %s.",
4638 availCount, EnumString(funcElemType).c_str());
4639 return;
4642 // -
4644 uint32_t locId = -1;
4645 if (MOZ_LIKELY(loc)) {
4646 locId = loc->mLocation;
4647 if (!loc->ValidateUsable(*this, "location")) return;
4649 // -
4651 const auto& reqLinkInfo = loc->mParent.lock();
4652 if (reqLinkInfo.get() != activeLinkResult) {
4653 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4654 "UniformLocation is not from the current active Program.");
4655 return;
4658 // -
4660 bool funcMatchesLocation = false;
4661 for (const auto allowed : loc->mValidUploadElemTypes) {
4662 funcMatchesLocation |= (funcElemType == allowed);
4664 if (MOZ_UNLIKELY(!funcMatchesLocation)) {
4665 std::string validSetters;
4666 for (const auto allowed : loc->mValidUploadElemTypes) {
4667 validSetters += EnumString(allowed);
4668 validSetters += '/';
4670 validSetters.pop_back(); // Cheekily discard the extra trailing '/'.
4672 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4673 "Uniform's `type` requires uniform setter of type %s.",
4674 validSetters.c_str());
4675 return;
4679 // -
4681 const auto ptr = bytes.begin().get() + (elemOffset * sizeof(float));
4682 const auto range = Range<const uint8_t>{ptr, availCount * sizeof(float)};
4683 Run<RPROC(UniformData)>(locId, transpose, RawBuffer<>(range));
4686 // -
4688 void ClientWebGLContext::BindVertexArray(WebGLVertexArrayJS* const vao) {
4689 const FuncScope funcScope(*this, "bindVertexArray");
4690 if (IsContextLost()) return;
4691 if (vao && !vao->ValidateUsable(*this, "vao")) return;
4692 auto& state = State();
4694 if (vao) {
4695 vao->mHasBeenBound = true;
4696 state.mBoundVao = vao;
4697 } else {
4698 state.mBoundVao = state.mDefaultVao;
4701 Run<RPROC(BindVertexArray)>(vao ? vao->mId : 0);
4704 void ClientWebGLContext::EnableVertexAttribArray(GLuint index) {
4705 Run<RPROC(EnableVertexAttribArray)>(index);
4708 void ClientWebGLContext::DisableVertexAttribArray(GLuint index) {
4709 Run<RPROC(DisableVertexAttribArray)>(index);
4712 WebGLsizeiptr ClientWebGLContext::GetVertexAttribOffset(GLuint index,
4713 GLenum pname) {
4714 const FuncScope funcScope(*this, "getVertexAttribOffset");
4715 if (IsContextLost()) return 0;
4717 if (pname != LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER) {
4718 EnqueueError_ArgEnum("pname", pname);
4719 return 0;
4722 const auto maybe = GetVertexAttribPriv(index, pname);
4723 if (!maybe) return 0;
4724 return *maybe;
4727 void ClientWebGLContext::VertexAttrib4Tv(GLuint index, webgl::AttribBaseType t,
4728 const Range<const uint8_t>& src) {
4729 const FuncScope funcScope(*this, "vertexAttrib[1234]u?[fi]{v}");
4730 if (IsContextLost()) return;
4731 auto& state = State();
4733 if (src.length() / sizeof(float) < 4) {
4734 EnqueueError(LOCAL_GL_INVALID_VALUE, "Array must have >=4 elements.");
4735 return;
4738 auto& list = state.mGenericVertexAttribs;
4739 if (index >= list.size()) {
4740 EnqueueError(LOCAL_GL_INVALID_VALUE,
4741 "`index` must be < MAX_VERTEX_ATTRIBS.");
4742 return;
4745 auto& attrib = list[index];
4746 attrib.type = t;
4747 memcpy(attrib.data.data(), src.begin().get(), attrib.data.size());
4749 Run<RPROC(VertexAttrib4T)>(index, attrib);
4752 // -
4754 void ClientWebGLContext::VertexAttribDivisor(GLuint index, GLuint divisor) {
4755 Run<RPROC(VertexAttribDivisor)>(index, divisor);
4758 // -
4760 void ClientWebGLContext::VertexAttribPointerImpl(bool isFuncInt, GLuint index,
4761 GLint rawChannels, GLenum type,
4762 bool normalized,
4763 GLsizei rawByteStrideOrZero,
4764 WebGLintptr rawByteOffset) {
4765 const FuncScope funcScope(*this, "vertexAttribI?Pointer");
4766 if (IsContextLost()) return;
4767 auto& state = State();
4769 const auto channels = MaybeAs<uint8_t>(rawChannels);
4770 if (!channels) {
4771 EnqueueError(LOCAL_GL_INVALID_VALUE,
4772 "Channel count `size` must be within [1,4].");
4773 return;
4776 const auto byteStrideOrZero = MaybeAs<uint8_t>(rawByteStrideOrZero);
4777 if (!byteStrideOrZero) {
4778 EnqueueError(LOCAL_GL_INVALID_VALUE, "`stride` must be within [0,255].");
4779 return;
4782 if (!ValidateNonNegative("byteOffset", rawByteOffset)) return;
4783 const auto byteOffset = static_cast<uint64_t>(rawByteOffset);
4785 // -
4787 const webgl::VertAttribPointerDesc desc{
4788 isFuncInt, *channels, normalized, *byteStrideOrZero, type, byteOffset};
4790 const auto res = CheckVertexAttribPointer(mIsWebGL2, desc);
4791 if (res.isErr()) {
4792 const auto& err = res.inspectErr();
4793 EnqueueError(err.type, "%s", err.info.c_str());
4794 return;
4797 auto& list = state.mBoundVao->mAttribBuffers;
4798 if (index >= list.size()) {
4799 EnqueueError(LOCAL_GL_INVALID_VALUE,
4800 "`index` (%u) must be < MAX_VERTEX_ATTRIBS.", index);
4801 return;
4804 const auto buffer = state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER];
4805 if (!buffer && byteOffset) {
4806 return EnqueueError(LOCAL_GL_INVALID_OPERATION,
4807 "If ARRAY_BUFFER is null, byteOffset must be zero.");
4810 Run<RPROC(VertexAttribPointer)>(index, desc);
4812 list[index] = buffer;
4815 // -------------------------------- Drawing -------------------------------
4817 void ClientWebGLContext::DrawArraysInstanced(GLenum mode, GLint first,
4818 GLsizei count, GLsizei primcount) {
4819 Run<RPROC(DrawArraysInstanced)>(mode, first, count, primcount);
4820 AfterDrawCall();
4823 void ClientWebGLContext::DrawElementsInstanced(GLenum mode, GLsizei count,
4824 GLenum type, WebGLintptr offset,
4825 GLsizei primcount) {
4826 Run<RPROC(DrawElementsInstanced)>(mode, count, type, offset, primcount);
4827 AfterDrawCall();
4830 // ------------------------------ Readback -------------------------------
4831 void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
4832 GLsizei height, GLenum format, GLenum type,
4833 WebGLsizeiptr offset,
4834 dom::CallerType aCallerType,
4835 ErrorResult& out_error) const {
4836 const FuncScope funcScope(*this, "readPixels");
4837 if (!ReadPixels_SharedPrecheck(aCallerType, out_error)) return;
4838 const auto& state = State();
4839 if (!ValidateNonNegative("width", width)) return;
4840 if (!ValidateNonNegative("height", height)) return;
4841 if (!ValidateNonNegative("offset", offset)) return;
4843 const auto desc = webgl::ReadPixelsDesc{{x, y},
4844 *uvec2::From(width, height),
4845 {format, type},
4846 state.mPixelPackState};
4847 Run<RPROC(ReadPixelsPbo)>(desc, static_cast<uint64_t>(offset));
4850 void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
4851 GLsizei height, GLenum format, GLenum type,
4852 const dom::ArrayBufferView& dstData,
4853 GLuint dstElemOffset,
4854 dom::CallerType aCallerType,
4855 ErrorResult& out_error) const {
4856 const FuncScope funcScope(*this, "readPixels");
4857 if (!ReadPixels_SharedPrecheck(aCallerType, out_error)) return;
4858 const auto& state = State();
4859 if (!ValidateNonNegative("width", width)) return;
4860 if (!ValidateNonNegative("height", height)) return;
4862 ////
4864 js::Scalar::Type reqScalarType;
4865 if (!GetJSScalarFromGLType(type, &reqScalarType)) {
4866 nsCString name;
4867 WebGLContext::EnumName(type, &name);
4868 EnqueueError(LOCAL_GL_INVALID_ENUM, "type: invalid enum value %s",
4869 name.BeginReading());
4870 return;
4873 auto viewElemType = dstData.Type();
4874 if (viewElemType == js::Scalar::Uint8Clamped) {
4875 viewElemType = js::Scalar::Uint8;
4877 if (viewElemType != reqScalarType) {
4878 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4879 "`pixels` type does not match `type`.");
4880 return;
4883 uint8_t* bytes;
4884 size_t byteLen;
4885 if (!ValidateArrayBufferView(dstData, dstElemOffset, 0,
4886 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
4887 return;
4890 const auto desc = webgl::ReadPixelsDesc{{x, y},
4891 *uvec2::From(width, height),
4892 {format, type},
4893 state.mPixelPackState};
4894 const auto range = Range<uint8_t>(bytes, byteLen);
4895 if (!DoReadPixels(desc, range)) {
4896 return;
4900 bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
4901 const Range<uint8_t> dest) const {
4902 const auto notLost =
4903 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
4904 if (!notLost) return false;
4905 const auto& inProcess = notLost->inProcess;
4906 if (inProcess) {
4907 inProcess->ReadPixelsInto(desc, dest);
4908 return true;
4910 const auto& child = notLost->outOfProcess;
4911 child->FlushPendingCmds();
4912 webgl::ReadPixelsResultIpc res = {};
4913 if (!child->SendReadPixels(desc, dest.length(), &res)) {
4914 res = {};
4916 if (!res.byteStride || !res.shmem) return false;
4917 const auto& byteStride = res.byteStride;
4918 const auto& subrect = res.subrect;
4919 const webgl::RaiiShmem shmem{child, res.shmem.ref()};
4920 if (!shmem) {
4921 EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "Failed to map in back buffer.");
4922 return false;
4925 const auto& shmemBytes = shmem.ByteRange();
4927 const auto pii = webgl::PackingInfoInfo::For(desc.pi);
4928 if (!pii) {
4929 gfxCriticalError() << "ReadPixels: Bad " << desc.pi;
4930 return false;
4932 const auto bpp = pii->BytesPerPixel();
4934 const auto& packing = desc.packState;
4935 auto packRect = *uvec2::From(subrect.x, subrect.y);
4936 packRect.x += packing.skipPixels;
4937 packRect.y += packing.skipRows;
4939 const auto xByteSize = bpp * static_cast<uint32_t>(subrect.width);
4940 const ptrdiff_t byteOffset = packRect.y * byteStride + packRect.x * bpp;
4942 auto srcItr = shmemBytes.begin() + byteOffset;
4943 auto destItr = dest.begin() + byteOffset;
4945 for (const auto i : IntegerRange(subrect.height)) {
4946 if (i) {
4947 // Don't trigger an assert on the last loop by pushing a RangedPtr past
4948 // its bounds.
4949 srcItr += byteStride;
4950 destItr += byteStride;
4951 MOZ_RELEASE_ASSERT(srcItr + xByteSize <= shmemBytes.end());
4952 MOZ_RELEASE_ASSERT(destItr + xByteSize <= dest.end());
4954 Memcpy(destItr, srcItr, xByteSize);
4957 return true;
4960 bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
4961 const mozilla::ipc::Shmem& shmem) const {
4962 const auto notLost =
4963 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
4964 if (!notLost) return false;
4965 const auto& inProcess = notLost->inProcess;
4966 if (inProcess) {
4967 const auto& shmemBytes = shmem.Range<uint8_t>();
4968 inProcess->ReadPixelsInto(desc, shmemBytes);
4969 return true;
4971 const auto& child = notLost->outOfProcess;
4972 child->FlushPendingCmds();
4973 webgl::ReadPixelsResultIpc res = {};
4974 // We assume the input is an unsafe shmem which won't be consumed by this
4975 // request. Since SendReadPixels expects a Shmem rvalue, we must create a copy
4976 // to provide it that can be consumed instead of the original descriptor.
4977 mozilla::ipc::Shmem dest = shmem;
4978 if (!child->SendReadPixels(desc, dest, &res)) {
4979 res = {};
4981 return res.byteStride > 0;
4984 bool ClientWebGLContext::ReadPixels_SharedPrecheck(
4985 dom::CallerType aCallerType, ErrorResult& out_error) const {
4986 if (IsContextLost()) return false;
4988 if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
4989 aCallerType != dom::CallerType::System) {
4990 JsWarning("readPixels: Not allowed");
4991 out_error.Throw(NS_ERROR_DOM_SECURITY_ERR);
4992 return false;
4995 return true;
4998 // --------------------------------- GL Query ---------------------------------
5000 static inline GLenum QuerySlotTarget(const GLenum specificTarget) {
5001 if (specificTarget == LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
5002 return LOCAL_GL_ANY_SAMPLES_PASSED;
5004 return specificTarget;
5007 void ClientWebGLContext::GetQuery(JSContext* cx, GLenum specificTarget,
5008 GLenum pname,
5009 JS::MutableHandle<JS::Value> retval) const {
5010 retval.set(JS::NullValue());
5011 const FuncScope funcScope(*this, "getQuery");
5012 if (IsContextLost()) return;
5013 const auto& limits = Limits();
5014 auto& state = State();
5016 if (IsExtensionEnabled(WebGLExtensionID::EXT_disjoint_timer_query)) {
5017 if (pname == LOCAL_GL_QUERY_COUNTER_BITS) {
5018 switch (specificTarget) {
5019 case LOCAL_GL_TIME_ELAPSED_EXT:
5020 retval.set(JS::NumberValue(limits.queryCounterBitsTimeElapsed));
5021 return;
5023 case LOCAL_GL_TIMESTAMP_EXT:
5024 retval.set(JS::NumberValue(limits.queryCounterBitsTimestamp));
5025 return;
5027 default:
5028 EnqueueError_ArgEnum("target", specificTarget);
5029 return;
5034 if (pname != LOCAL_GL_CURRENT_QUERY) {
5035 EnqueueError_ArgEnum("pname", pname);
5036 return;
5039 const auto slotTarget = QuerySlotTarget(specificTarget);
5040 const auto& slot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
5041 if (!slot) {
5042 EnqueueError_ArgEnum("target", specificTarget);
5043 return;
5046 auto query = *slot;
5047 if (query && query->mTarget != specificTarget) {
5048 query = nullptr;
5051 (void)ToJSValueOrNull(cx, query, retval);
5054 void ClientWebGLContext::GetQueryParameter(
5055 JSContext*, WebGLQueryJS& query, const GLenum pname,
5056 JS::MutableHandle<JS::Value> retval) const {
5057 retval.set(JS::NullValue());
5058 const FuncScope funcScope(*this, "getQueryParameter");
5059 if (IsContextLost()) return;
5060 if (!query.ValidateUsable(*this, "query")) return;
5062 auto maybe = [&]() {
5063 const auto& inProcess = mNotLost->inProcess;
5064 if (inProcess) {
5065 return inProcess->GetQueryParameter(query.mId, pname);
5067 const auto& child = mNotLost->outOfProcess;
5068 child->FlushPendingCmds();
5069 Maybe<double> ret;
5070 if (!child->SendGetQueryParameter(query.mId, pname, &ret)) {
5071 ret.reset();
5073 return ret;
5074 }();
5075 if (!maybe) return;
5077 // We must usually wait for an event loop before the query can be available.
5078 const bool canBeAvailable =
5079 (query.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
5080 if (!canBeAvailable) {
5081 if (pname != LOCAL_GL_QUERY_RESULT_AVAILABLE) {
5082 return;
5084 maybe = Some(0.0);
5087 switch (pname) {
5088 case LOCAL_GL_QUERY_RESULT_AVAILABLE:
5089 retval.set(JS::BooleanValue(*maybe));
5090 break;
5092 default:
5093 retval.set(JS::NumberValue(*maybe));
5094 break;
5098 void ClientWebGLContext::BeginQuery(const GLenum specificTarget,
5099 WebGLQueryJS& query) {
5100 const FuncScope funcScope(*this, "beginQuery");
5101 if (IsContextLost()) return;
5102 if (!query.ValidateUsable(*this, "query")) return;
5103 auto& state = State();
5105 const auto slotTarget = QuerySlotTarget(specificTarget);
5106 const auto& slot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
5107 if (!slot) {
5108 EnqueueError_ArgEnum("target", specificTarget);
5109 return;
5112 if (*slot) {
5113 auto enumStr = EnumString(slotTarget);
5114 if (slotTarget == LOCAL_GL_ANY_SAMPLES_PASSED) {
5115 enumStr += "/ANY_SAMPLES_PASSED_CONSERVATIVE";
5117 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5118 "A Query is already active for %s.", enumStr.c_str());
5119 return;
5122 if (query.mTarget && query.mTarget != specificTarget) {
5123 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5124 "`query` cannot be changed to a different target.");
5125 return;
5128 *slot = &query;
5129 query.mTarget = specificTarget;
5131 Run<RPROC(BeginQuery)>(specificTarget, query.mId);
5134 void ClientWebGLContext::EndQuery(const GLenum specificTarget) {
5135 const FuncScope funcScope(*this, "endQuery");
5136 if (IsContextLost()) return;
5137 auto& state = State();
5139 const auto slotTarget = QuerySlotTarget(specificTarget);
5140 const auto& maybeSlot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
5141 if (!maybeSlot) {
5142 EnqueueError_ArgEnum("target", specificTarget);
5143 return;
5145 auto& slot = *maybeSlot;
5146 if (!slot || slot->mTarget != specificTarget) {
5147 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No Query is active for %s.",
5148 EnumString(specificTarget).c_str());
5149 return;
5151 const auto query = slot;
5152 slot = nullptr;
5154 Run<RPROC(EndQuery)>(specificTarget);
5156 auto& availRunnable = EnsureAvailabilityRunnable();
5157 availRunnable.mQueries.push_back(query.get());
5158 query->mCanBeAvailable = false;
5161 void ClientWebGLContext::QueryCounter(WebGLQueryJS& query,
5162 const GLenum target) const {
5163 const FuncScope funcScope(*this, "queryCounter");
5164 if (IsContextLost()) return;
5165 if (!query.ValidateUsable(*this, "query")) return;
5167 if (target != LOCAL_GL_TIMESTAMP) {
5168 EnqueueError(LOCAL_GL_INVALID_ENUM, "`target` must be TIMESTAMP.");
5169 return;
5172 if (query.mTarget && query.mTarget != target) {
5173 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5174 "`query` cannot be changed to a different target.");
5175 return;
5177 query.mTarget = target;
5179 Run<RPROC(QueryCounter)>(query.mId);
5181 auto& availRunnable = EnsureAvailabilityRunnable();
5182 availRunnable.mQueries.push_back(&query);
5183 query.mCanBeAvailable = false;
5186 // -------------------------------- Sampler -------------------------------
5187 void ClientWebGLContext::GetSamplerParameter(
5188 JSContext* cx, const WebGLSamplerJS& sampler, const GLenum pname,
5189 JS::MutableHandle<JS::Value> retval) const {
5190 retval.set(JS::NullValue());
5191 const FuncScope funcScope(*this, "getSamplerParameter");
5192 if (IsContextLost()) return;
5193 if (!sampler.ValidateUsable(*this, "sampler")) return;
5195 const auto maybe = [&]() {
5196 const auto& inProcess = mNotLost->inProcess;
5197 if (inProcess) {
5198 return inProcess->GetSamplerParameter(sampler.mId, pname);
5200 const auto& child = mNotLost->outOfProcess;
5201 child->FlushPendingCmds();
5202 Maybe<double> ret;
5203 if (!child->SendGetSamplerParameter(sampler.mId, pname, &ret)) {
5204 ret.reset();
5206 return ret;
5207 }();
5208 if (maybe) {
5209 retval.set(JS::NumberValue(*maybe));
5213 void ClientWebGLContext::BindSampler(const GLuint unit,
5214 WebGLSamplerJS* const sampler) {
5215 const FuncScope funcScope(*this, "bindSampler");
5216 if (IsContextLost()) return;
5217 if (sampler && !sampler->ValidateUsable(*this, "sampler")) return;
5218 auto& state = State();
5220 auto& texUnits = state.mTexUnits;
5221 if (unit >= texUnits.size()) {
5222 EnqueueError(LOCAL_GL_INVALID_VALUE, "`unit` (%u) larger than %zu.", unit,
5223 texUnits.size());
5224 return;
5227 // -
5229 texUnits[unit].sampler = sampler;
5231 Run<RPROC(BindSampler)>(unit, sampler ? sampler->mId : 0);
5234 void ClientWebGLContext::SamplerParameteri(WebGLSamplerJS& sampler,
5235 const GLenum pname,
5236 const GLint param) const {
5237 const FuncScope funcScope(*this, "samplerParameteri");
5238 if (IsContextLost()) return;
5239 if (!sampler.ValidateUsable(*this, "sampler")) return;
5241 Run<RPROC(SamplerParameteri)>(sampler.mId, pname, param);
5244 void ClientWebGLContext::SamplerParameterf(WebGLSamplerJS& sampler,
5245 const GLenum pname,
5246 const GLfloat param) const {
5247 const FuncScope funcScope(*this, "samplerParameterf");
5248 if (IsContextLost()) return;
5249 if (!sampler.ValidateUsable(*this, "sampler")) return;
5251 Run<RPROC(SamplerParameterf)>(sampler.mId, pname, param);
5254 // ------------------------------- GL Sync ---------------------------------
5256 void ClientWebGLContext::GetSyncParameter(
5257 JSContext* const cx, WebGLSyncJS& sync, const GLenum pname,
5258 JS::MutableHandle<JS::Value> retval) const {
5259 retval.set(JS::NullValue());
5260 const FuncScope funcScope(*this, "getSyncParameter");
5261 if (IsContextLost()) return;
5262 if (!sync.ValidateUsable(*this, "sync")) return;
5264 retval.set([&]() -> JS::Value {
5265 switch (pname) {
5266 case LOCAL_GL_OBJECT_TYPE:
5267 return JS::NumberValue(LOCAL_GL_SYNC_FENCE);
5268 case LOCAL_GL_SYNC_CONDITION:
5269 return JS::NumberValue(LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE);
5270 case LOCAL_GL_SYNC_FLAGS:
5271 return JS::NumberValue(0);
5272 case LOCAL_GL_SYNC_STATUS: {
5273 if (!sync.mSignaled) {
5274 const auto res = ClientWaitSync(sync, 0, 0);
5275 sync.mSignaled = (res == LOCAL_GL_ALREADY_SIGNALED ||
5276 res == LOCAL_GL_CONDITION_SATISFIED);
5278 return JS::NumberValue(sync.mSignaled ? LOCAL_GL_SIGNALED
5279 : LOCAL_GL_UNSIGNALED);
5281 default:
5282 EnqueueError_ArgEnum("pname", pname);
5283 return JS::NullValue();
5285 }());
5288 GLenum ClientWebGLContext::ClientWaitSync(WebGLSyncJS& sync,
5289 const GLbitfield flags,
5290 const GLuint64 timeout) const {
5291 const FuncScope funcScope(*this, "clientWaitSync");
5292 if (IsContextLost()) return LOCAL_GL_WAIT_FAILED;
5293 if (!sync.ValidateUsable(*this, "sync")) return LOCAL_GL_WAIT_FAILED;
5295 if (flags != 0 && flags != LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT) {
5296 EnqueueError(LOCAL_GL_INVALID_VALUE,
5297 "`flags` must be SYNC_FLUSH_COMMANDS_BIT or 0.");
5298 return LOCAL_GL_WAIT_FAILED;
5301 const auto ret = [&]() {
5302 const auto& inProcess = mNotLost->inProcess;
5303 if (inProcess) {
5304 return inProcess->ClientWaitSync(sync.mId, flags, timeout);
5306 const auto& child = mNotLost->outOfProcess;
5307 child->FlushPendingCmds();
5308 GLenum ret = {};
5309 if (!child->SendClientWaitSync(sync.mId, flags, timeout, &ret)) {
5310 ret = {};
5312 return ret;
5313 }();
5315 switch (ret) {
5316 case LOCAL_GL_CONDITION_SATISFIED:
5317 case LOCAL_GL_ALREADY_SIGNALED:
5318 sync.mSignaled = true;
5319 break;
5322 // -
5324 const bool canBeAvailable =
5325 (sync.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
5326 if (!canBeAvailable) {
5327 if (!sync.mHasWarnedNotAvailable) {
5328 EnqueueWarning(
5329 "ClientWaitSync must return TIMEOUT_EXPIRED until control has"
5330 " returned to the user agent's main loop. (only warns once)");
5331 sync.mHasWarnedNotAvailable = true;
5333 return LOCAL_GL_TIMEOUT_EXPIRED;
5336 return ret;
5339 void ClientWebGLContext::WaitSync(const WebGLSyncJS& sync,
5340 const GLbitfield flags,
5341 const GLint64 timeout) const {
5342 const FuncScope funcScope(*this, "waitSync");
5343 if (IsContextLost()) return;
5344 if (!sync.ValidateUsable(*this, "sync")) return;
5346 if (flags != 0) {
5347 EnqueueError(LOCAL_GL_INVALID_VALUE, "`flags` must be 0.");
5348 return;
5350 if (timeout != -1) {
5351 EnqueueError(LOCAL_GL_INVALID_VALUE, "`timeout` must be TIMEOUT_IGNORED.");
5352 return;
5355 JsWarning("waitSync is a no-op.");
5358 // -------------------------- Transform Feedback ---------------------------
5360 void ClientWebGLContext::BindTransformFeedback(
5361 const GLenum target, WebGLTransformFeedbackJS* const tf) {
5362 const FuncScope funcScope(*this, "bindTransformFeedback");
5363 if (IsContextLost()) return;
5364 if (tf && !tf->ValidateUsable(*this, "tf")) return;
5365 auto& state = State();
5367 if (target != LOCAL_GL_TRANSFORM_FEEDBACK) {
5368 EnqueueError(LOCAL_GL_INVALID_ENUM, "`target` must be TRANSFORM_FEEDBACK.");
5369 return;
5371 if (state.mTfActiveAndNotPaused) {
5372 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5373 "Current Transform Feedback object is active and not paused.");
5374 return;
5377 if (tf) {
5378 tf->mHasBeenBound = true;
5379 state.mBoundTfo = tf;
5380 } else {
5381 state.mBoundTfo = state.mDefaultTfo;
5384 Run<RPROC(BindTransformFeedback)>(tf ? tf->mId : 0);
5387 void ClientWebGLContext::BeginTransformFeedback(const GLenum primMode) {
5388 const FuncScope funcScope(*this, "beginTransformFeedback");
5389 if (IsContextLost()) return;
5390 auto& state = State();
5391 auto& tfo = *(state.mBoundTfo);
5393 if (tfo.mActiveOrPaused) {
5394 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5395 "Transform Feedback is already active or paused.");
5396 return;
5398 MOZ_ASSERT(!state.mTfActiveAndNotPaused);
5400 auto& prog = state.mCurrentProgram;
5401 if (!prog) {
5402 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No program in use.");
5403 return;
5405 const auto& linkResult = GetLinkResult(*prog);
5406 if (!linkResult.success) {
5407 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5408 "Program is not successfully linked.");
5409 return;
5412 auto tfBufferCount = linkResult.active.activeTfVaryings.size();
5413 if (tfBufferCount &&
5414 linkResult.tfBufferMode == LOCAL_GL_INTERLEAVED_ATTRIBS) {
5415 tfBufferCount = 1;
5417 if (!tfBufferCount) {
5418 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5419 "Program does not use Transform Feedback.");
5420 return;
5423 const auto& buffers = tfo.mAttribBuffers;
5424 for (const auto i : IntegerRange(tfBufferCount)) {
5425 if (!buffers[i]) {
5426 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5427 "Transform Feedback buffer %u is null.", i);
5428 return;
5432 switch (primMode) {
5433 case LOCAL_GL_POINTS:
5434 case LOCAL_GL_LINES:
5435 case LOCAL_GL_TRIANGLES:
5436 break;
5437 default:
5438 EnqueueError(LOCAL_GL_INVALID_ENUM,
5439 "`primitiveMode` must be POINTS, LINES< or TRIANGLES.");
5440 return;
5443 // -
5445 tfo.mActiveOrPaused = true;
5446 tfo.mActiveProgram = prog;
5447 tfo.mActiveProgramKeepAlive = prog->mKeepAliveWeak.lock();
5448 prog->mActiveTfos.insert(&tfo);
5449 state.mTfActiveAndNotPaused = true;
5451 Run<RPROC(BeginTransformFeedback)>(primMode);
5454 void ClientWebGLContext::EndTransformFeedback() {
5455 const FuncScope funcScope(*this, "endTransformFeedback");
5456 if (IsContextLost()) return;
5457 auto& state = State();
5458 auto& tfo = *(state.mBoundTfo);
5460 if (!tfo.mActiveOrPaused) {
5461 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5462 "Transform Feedback is not active or paused.");
5463 return;
5466 tfo.mActiveOrPaused = false;
5467 tfo.mActiveProgram->mActiveTfos.erase(&tfo);
5468 tfo.mActiveProgram = nullptr;
5469 tfo.mActiveProgramKeepAlive = nullptr;
5470 state.mTfActiveAndNotPaused = false;
5471 Run<RPROC(EndTransformFeedback)>();
5474 void ClientWebGLContext::PauseTransformFeedback() {
5475 const FuncScope funcScope(*this, "pauseTransformFeedback");
5476 if (IsContextLost()) return;
5477 auto& state = State();
5478 auto& tfo = *(state.mBoundTfo);
5480 if (!tfo.mActiveOrPaused) {
5481 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5482 "Transform Feedback is not active.");
5483 return;
5485 if (!state.mTfActiveAndNotPaused) {
5486 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5487 "Transform Feedback is already paused.");
5488 return;
5491 state.mTfActiveAndNotPaused = false;
5492 Run<RPROC(PauseTransformFeedback)>();
5495 void ClientWebGLContext::ResumeTransformFeedback() {
5496 const FuncScope funcScope(*this, "resumeTransformFeedback");
5497 if (IsContextLost()) return;
5498 auto& state = State();
5499 auto& tfo = *(state.mBoundTfo);
5501 if (!tfo.mActiveOrPaused) {
5502 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5503 "Transform Feedback is not active and paused.");
5504 return;
5506 if (state.mTfActiveAndNotPaused) {
5507 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5508 "Transform Feedback is not paused.");
5509 return;
5511 if (state.mCurrentProgram != tfo.mActiveProgram) {
5512 EnqueueError(
5513 LOCAL_GL_INVALID_OPERATION,
5514 "Cannot Resume Transform Feedback with a program link result different"
5515 " from when Begin was called.");
5516 return;
5519 state.mTfActiveAndNotPaused = true;
5520 Run<RPROC(ResumeTransformFeedback)>();
5523 void ClientWebGLContext::SetFramebufferIsInOpaqueRAF(WebGLFramebufferJS* fb,
5524 bool value) {
5525 fb->mInOpaqueRAF = value;
5526 Run<RPROC(SetFramebufferIsInOpaqueRAF)>(fb->mId, value);
5529 // ---------------------------- Misc Extensions ----------------------------
5530 void ClientWebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers) {
5531 const auto range = MakeRange(buffers);
5532 const auto vec = std::vector<GLenum>(range.begin().get(), range.end().get());
5533 Run<RPROC(DrawBuffers)>(vec);
5536 void ClientWebGLContext::EnqueueErrorImpl(const GLenum error,
5537 const nsACString& text) const {
5538 if (!mNotLost) return; // Ignored if context is lost.
5539 Run<RPROC(GenerateError)>(error, ToString(text));
5542 void ClientWebGLContext::RequestExtension(const WebGLExtensionID ext) const {
5543 Run<RPROC(RequestExtension)>(ext);
5546 // -
5548 static bool IsExtensionForbiddenForCaller(const WebGLExtensionID ext,
5549 const dom::CallerType callerType) {
5550 if (callerType == dom::CallerType::System) return false;
5552 if (StaticPrefs::webgl_enable_privileged_extensions()) return false;
5554 const bool resistFingerprinting =
5555 nsContentUtils::ShouldResistFingerprinting();
5556 switch (ext) {
5557 case WebGLExtensionID::MOZ_debug:
5558 return true;
5560 case WebGLExtensionID::WEBGL_debug_renderer_info:
5561 return resistFingerprinting ||
5562 !StaticPrefs::webgl_enable_debug_renderer_info();
5564 case WebGLExtensionID::WEBGL_debug_shaders:
5565 return resistFingerprinting;
5567 default:
5568 return false;
5572 bool ClientWebGLContext::IsSupported(const WebGLExtensionID ext,
5573 const dom::CallerType callerType) const {
5574 if (IsExtensionForbiddenForCaller(ext, callerType)) return false;
5575 const auto& limits = Limits();
5576 return limits.supportedExtensions[ext];
5579 void ClientWebGLContext::GetSupportedExtensions(
5580 dom::Nullable<nsTArray<nsString>>& retval,
5581 const dom::CallerType callerType) const {
5582 retval.SetNull();
5583 if (!mNotLost) return;
5585 auto& retarr = retval.SetValue();
5586 for (const auto i : MakeEnumeratedRange(WebGLExtensionID::Max)) {
5587 if (!IsSupported(i, callerType)) continue;
5589 const auto& extStr = GetExtensionName(i);
5590 retarr.AppendElement(NS_ConvertUTF8toUTF16(extStr));
5594 // -
5596 void ClientWebGLContext::GetSupportedProfilesASTC(
5597 dom::Nullable<nsTArray<nsString>>& retval) const {
5598 retval.SetNull();
5599 if (!mNotLost) return;
5600 const auto& limits = Limits();
5602 auto& retarr = retval.SetValue();
5603 retarr.AppendElement(u"ldr"_ns);
5604 if (limits.astcHdr) {
5605 retarr.AppendElement(u"hdr"_ns);
5609 // -
5611 bool ClientWebGLContext::ShouldResistFingerprinting() const {
5612 if (mCanvasElement) {
5613 // If we're constructed from a canvas element
5614 return nsContentUtils::ShouldResistFingerprinting(GetOwnerDoc());
5616 if (mOffscreenCanvas) {
5617 // If we're constructed from an offscreen canvas
5618 return mOffscreenCanvas->ShouldResistFingerprinting();
5620 // Last resort, just check the global preference
5621 return nsContentUtils::ShouldResistFingerprinting();
5624 uint32_t ClientWebGLContext::GetPrincipalHashValue() const {
5625 if (mCanvasElement) {
5626 return mCanvasElement->NodePrincipal()->GetHashValue();
5628 if (mOffscreenCanvas) {
5629 nsIGlobalObject* global = mOffscreenCanvas->GetOwnerGlobal();
5630 if (global) {
5631 return global->GetPrincipalHashValue();
5634 return 0;
5637 // ---------------------------
5639 void ClientWebGLContext::EnqueueError_ArgEnum(const char* const argName,
5640 const GLenum val) const {
5641 EnqueueError(LOCAL_GL_INVALID_ENUM, "Bad `%s`: 0x%04x", argName, val);
5644 // -
5645 // WebGLProgramJS
5647 void ClientWebGLContext::AttachShader(WebGLProgramJS& prog,
5648 WebGLShaderJS& shader) const {
5649 const FuncScope funcScope(*this, "attachShader");
5650 if (IsContextLost()) return;
5651 if (!prog.ValidateUsable(*this, "program")) return;
5652 if (!shader.ValidateUsable(*this, "shader")) return;
5654 auto& slot = *MaybeFind(prog.mNextLink_Shaders, shader.mType);
5655 if (slot.shader) {
5656 if (&shader == slot.shader) {
5657 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`shader` is already attached.");
5658 } else {
5659 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5660 "Only one of each type of"
5661 " shader may be attached to a program.");
5663 return;
5665 slot = {&shader, shader.mKeepAliveWeak.lock()};
5667 Run<RPROC(AttachShader)>(prog.mId, shader.mId);
5670 void ClientWebGLContext::BindAttribLocation(WebGLProgramJS& prog,
5671 const GLuint location,
5672 const nsAString& name) const {
5673 const FuncScope funcScope(*this, "detachShader");
5674 if (IsContextLost()) return;
5675 if (!prog.ValidateUsable(*this, "program")) return;
5677 const auto& nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
5678 Run<RPROC(BindAttribLocation)>(prog.mId, location, nameU8);
5681 void ClientWebGLContext::DetachShader(WebGLProgramJS& prog,
5682 const WebGLShaderJS& shader) const {
5683 const FuncScope funcScope(*this, "detachShader");
5684 if (IsContextLost()) return;
5685 if (!prog.ValidateUsable(*this, "program")) return;
5686 if (!shader.ValidateUsable(*this, "shader")) return;
5688 auto& slot = *MaybeFind(prog.mNextLink_Shaders, shader.mType);
5690 if (slot.shader != &shader) {
5691 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`shader` is not attached.");
5692 return;
5694 slot = {};
5696 Run<RPROC(DetachShader)>(prog.mId, shader.mId);
5699 void ClientWebGLContext::GetAttachedShaders(
5700 const WebGLProgramJS& prog,
5701 dom::Nullable<nsTArray<RefPtr<WebGLShaderJS>>>& retval) const {
5702 const FuncScope funcScope(*this, "getAttachedShaders");
5703 if (IsContextLost()) return;
5704 if (!prog.ValidateUsable(*this, "program")) return;
5706 auto& arr = retval.SetValue();
5707 for (const auto& pair : prog.mNextLink_Shaders) {
5708 const auto& attachment = pair.second;
5709 if (!attachment.shader) continue;
5710 arr.AppendElement(attachment.shader);
5714 void ClientWebGLContext::LinkProgram(WebGLProgramJS& prog) const {
5715 const FuncScope funcScope(*this, "linkProgram");
5716 if (IsContextLost()) return;
5717 if (!prog.ValidateUsable(*this, "program")) return;
5719 if (!prog.mActiveTfos.empty()) {
5720 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5721 "Program still in use by active or paused"
5722 " Transform Feedback objects.");
5723 return;
5726 prog.mResult = std::make_shared<webgl::LinkResult>();
5727 prog.mUniformLocByName = Nothing();
5728 prog.mUniformBlockBindings = {};
5729 Run<RPROC(LinkProgram)>(prog.mId);
5732 void ClientWebGLContext::TransformFeedbackVaryings(
5733 WebGLProgramJS& prog, const dom::Sequence<nsString>& varyings,
5734 const GLenum bufferMode) const {
5735 const FuncScope funcScope(*this, "transformFeedbackVaryings");
5736 if (IsContextLost()) return;
5737 if (!prog.ValidateUsable(*this, "program")) return;
5739 std::vector<std::string> varyingsU8;
5740 varyingsU8.reserve(varyings.Length());
5741 for (const auto& cur : varyings) {
5742 const auto curU8 = ToString(NS_ConvertUTF16toUTF8(cur));
5743 varyingsU8.push_back(curU8);
5746 Run<RPROC(TransformFeedbackVaryings)>(prog.mId, varyingsU8, bufferMode);
5749 void ClientWebGLContext::UniformBlockBinding(WebGLProgramJS& prog,
5750 const GLuint blockIndex,
5751 const GLuint blockBinding) const {
5752 const FuncScope funcScope(*this, "uniformBlockBinding");
5753 if (IsContextLost()) return;
5754 if (!prog.ValidateUsable(*this, "program")) return;
5755 const auto& state = State();
5757 (void)GetLinkResult(prog);
5758 auto& list = prog.mUniformBlockBindings;
5759 if (blockIndex >= list.size()) {
5760 EnqueueError(
5761 LOCAL_GL_INVALID_VALUE,
5762 "`blockIndex` (%u) must be less than ACTIVE_UNIFORM_BLOCKS (%zu).",
5763 blockIndex, list.size());
5764 return;
5766 if (blockBinding >= state.mBoundUbos.size()) {
5767 EnqueueError(LOCAL_GL_INVALID_VALUE,
5768 "`blockBinding` (%u) must be less than "
5769 "MAX_UNIFORM_BUFFER_BINDINGS (%zu).",
5770 blockBinding, state.mBoundUbos.size());
5771 return;
5774 list[blockIndex] = blockBinding;
5775 Run<RPROC(UniformBlockBinding)>(prog.mId, blockIndex, blockBinding);
5778 // WebGLProgramJS link result reflection
5780 already_AddRefed<WebGLActiveInfoJS> ClientWebGLContext::GetActiveAttrib(
5781 const WebGLProgramJS& prog, const GLuint index) {
5782 const FuncScope funcScope(*this, "getActiveAttrib");
5783 if (IsContextLost()) return nullptr;
5784 if (!prog.ValidateUsable(*this, "program")) return nullptr;
5786 const auto& res = GetLinkResult(prog);
5787 const auto& list = res.active.activeAttribs;
5788 if (index >= list.size()) {
5789 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5790 return nullptr;
5793 const auto& info = list[index];
5794 return AsAddRefed(new WebGLActiveInfoJS(info));
5797 already_AddRefed<WebGLActiveInfoJS> ClientWebGLContext::GetActiveUniform(
5798 const WebGLProgramJS& prog, const GLuint index) {
5799 const FuncScope funcScope(*this, "getActiveUniform");
5800 if (IsContextLost()) return nullptr;
5801 if (!prog.ValidateUsable(*this, "program")) return nullptr;
5803 const auto& res = GetLinkResult(prog);
5804 const auto& list = res.active.activeUniforms;
5805 if (index >= list.size()) {
5806 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5807 return nullptr;
5810 const auto& info = list[index];
5811 return AsAddRefed(new WebGLActiveInfoJS(info));
5814 void ClientWebGLContext::GetActiveUniformBlockName(const WebGLProgramJS& prog,
5815 const GLuint index,
5816 nsAString& retval) const {
5817 retval.SetIsVoid(true);
5818 const FuncScope funcScope(*this, "getActiveUniformBlockName");
5819 if (IsContextLost()) return;
5820 if (!prog.ValidateUsable(*this, "program")) return;
5822 const auto& res = GetLinkResult(prog);
5823 if (!res.success) {
5824 EnqueueError(LOCAL_GL_INVALID_OPERATION, "Program has not been linked.");
5825 return;
5828 const auto& list = res.active.activeUniformBlocks;
5829 if (index >= list.size()) {
5830 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5831 return;
5834 const auto& block = list[index];
5835 CopyUTF8toUTF16(block.name, retval);
5838 void ClientWebGLContext::GetActiveUniformBlockParameter(
5839 JSContext* const cx, const WebGLProgramJS& prog, const GLuint index,
5840 const GLenum pname, JS::MutableHandle<JS::Value> retval, ErrorResult& rv) {
5841 retval.set(JS::NullValue());
5842 const FuncScope funcScope(*this, "getActiveUniformBlockParameter");
5843 if (IsContextLost()) return;
5844 if (!prog.ValidateUsable(*this, "program")) return;
5846 const auto& res = GetLinkResult(prog);
5847 const auto& list = res.active.activeUniformBlocks;
5848 if (index >= list.size()) {
5849 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5850 return;
5852 const auto& block = list[index];
5854 retval.set([&]() -> JS::Value {
5855 switch (pname) {
5856 case LOCAL_GL_UNIFORM_BLOCK_BINDING:
5857 return JS::NumberValue(prog.mUniformBlockBindings[index]);
5859 case LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE:
5860 return JS::NumberValue(block.dataSize);
5862 case LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS:
5863 return JS::NumberValue(block.activeUniformIndices.size());
5865 case LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: {
5866 const auto& indices = block.activeUniformIndices;
5867 JS::Rooted<JSObject*> obj(
5869 dom::Uint32Array::Create(cx, this, indices.size(), indices.data()));
5870 if (!obj) {
5871 rv = NS_ERROR_OUT_OF_MEMORY;
5873 return JS::ObjectOrNullValue(obj);
5876 case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER:
5877 return JS::BooleanValue(block.referencedByVertexShader);
5879 case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER:
5880 return JS::BooleanValue(block.referencedByFragmentShader);
5882 default:
5883 EnqueueError_ArgEnum("pname", pname);
5884 return JS::NullValue();
5886 }());
5889 void ClientWebGLContext::GetActiveUniforms(
5890 JSContext* const cx, const WebGLProgramJS& prog,
5891 const dom::Sequence<GLuint>& uniformIndices, const GLenum pname,
5892 JS::MutableHandle<JS::Value> retval) const {
5893 retval.set(JS::NullValue());
5894 const FuncScope funcScope(*this, "getActiveUniforms");
5895 if (IsContextLost()) return;
5896 if (!prog.ValidateUsable(*this, "program")) return;
5898 const auto& res = GetLinkResult(prog);
5899 const auto& list = res.active.activeUniforms;
5901 const auto count = uniformIndices.Length();
5902 JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, count));
5903 if (!array) return; // Just bail.
5905 for (const auto i : IntegerRange(count)) {
5906 const auto index = uniformIndices[i];
5907 if (index >= list.size()) {
5908 EnqueueError(LOCAL_GL_INVALID_VALUE,
5909 "`uniformIndices[%u]`: `%u` too large.", i, index);
5910 return;
5912 const auto& uniform = list[index];
5914 JS::Rooted<JS::Value> value(cx);
5915 switch (pname) {
5916 case LOCAL_GL_UNIFORM_TYPE:
5917 value = JS::NumberValue(uniform.elemType);
5918 break;
5920 case LOCAL_GL_UNIFORM_SIZE:
5921 value = JS::NumberValue(uniform.elemCount);
5922 break;
5924 case LOCAL_GL_UNIFORM_BLOCK_INDEX:
5925 value = JS::NumberValue(uniform.block_index);
5926 break;
5928 case LOCAL_GL_UNIFORM_OFFSET:
5929 value = JS::NumberValue(uniform.block_offset);
5930 break;
5932 case LOCAL_GL_UNIFORM_ARRAY_STRIDE:
5933 value = JS::NumberValue(uniform.block_arrayStride);
5934 break;
5936 case LOCAL_GL_UNIFORM_MATRIX_STRIDE:
5937 value = JS::NumberValue(uniform.block_matrixStride);
5938 break;
5940 case LOCAL_GL_UNIFORM_IS_ROW_MAJOR:
5941 value = JS::BooleanValue(uniform.block_isRowMajor);
5942 break;
5944 default:
5945 EnqueueError_ArgEnum("pname", pname);
5946 return;
5948 if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE)) return;
5951 retval.setObject(*array);
5954 already_AddRefed<WebGLActiveInfoJS>
5955 ClientWebGLContext::GetTransformFeedbackVarying(const WebGLProgramJS& prog,
5956 const GLuint index) {
5957 const FuncScope funcScope(*this, "getTransformFeedbackVarying");
5958 if (IsContextLost()) return nullptr;
5959 if (!prog.ValidateUsable(*this, "program")) return nullptr;
5961 const auto& res = GetLinkResult(prog);
5962 const auto& list = res.active.activeTfVaryings;
5963 if (index >= list.size()) {
5964 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5965 return nullptr;
5968 const auto& info = list[index];
5969 return AsAddRefed(new WebGLActiveInfoJS(info));
5972 GLint ClientWebGLContext::GetAttribLocation(const WebGLProgramJS& prog,
5973 const nsAString& name) const {
5974 const FuncScope funcScope(*this, "getAttribLocation");
5975 if (IsContextLost()) return -1;
5976 if (!prog.ValidateUsable(*this, "program")) return -1;
5978 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
5979 const auto& res = GetLinkResult(prog);
5980 for (const auto& cur : res.active.activeAttribs) {
5981 if (cur.name == nameU8) return cur.location;
5984 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
5985 if (err) {
5986 EnqueueError(err->type, "%s", err->info.c_str());
5988 return -1;
5991 GLint ClientWebGLContext::GetFragDataLocation(const WebGLProgramJS& prog,
5992 const nsAString& name) const {
5993 const FuncScope funcScope(*this, "getFragDataLocation");
5994 if (IsContextLost()) return -1;
5995 if (!prog.ValidateUsable(*this, "program")) return -1;
5997 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
5999 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
6000 if (err) {
6001 EnqueueError(*err);
6002 return -1;
6005 return [&]() {
6006 const auto& inProcess = mNotLost->inProcess;
6007 if (inProcess) {
6008 return inProcess->GetFragDataLocation(prog.mId, nameU8);
6010 const auto& child = mNotLost->outOfProcess;
6011 child->FlushPendingCmds();
6012 GLint ret = {};
6013 if (!child->SendGetFragDataLocation(prog.mId, nameU8, &ret)) {
6014 ret = {};
6016 return ret;
6017 }();
6020 GLuint ClientWebGLContext::GetUniformBlockIndex(
6021 const WebGLProgramJS& prog, const nsAString& blockName) const {
6022 const FuncScope funcScope(*this, "getUniformBlockIndex");
6023 if (IsContextLost()) return LOCAL_GL_INVALID_INDEX;
6024 if (!prog.ValidateUsable(*this, "program")) return LOCAL_GL_INVALID_INDEX;
6026 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(blockName));
6028 const auto& res = GetLinkResult(prog);
6029 const auto& list = res.active.activeUniformBlocks;
6030 for (const auto i : IntegerRange(list.size())) {
6031 const auto& cur = list[i];
6032 if (cur.name == nameU8) {
6033 return i;
6036 return LOCAL_GL_INVALID_INDEX;
6039 void ClientWebGLContext::GetUniformIndices(
6040 const WebGLProgramJS& prog, const dom::Sequence<nsString>& uniformNames,
6041 dom::Nullable<nsTArray<GLuint>>& retval) const {
6042 const FuncScope funcScope(*this, "getUniformIndices");
6043 if (IsContextLost()) return;
6044 if (!prog.ValidateUsable(*this, "program")) return;
6046 const auto& res = GetLinkResult(prog);
6047 auto ret = nsTArray<GLuint>(uniformNames.Length());
6049 for (const auto& queriedNameU16 : uniformNames) {
6050 const auto queriedName = ToString(NS_ConvertUTF16toUTF8(queriedNameU16));
6051 const auto impliedProperArrayQueriedName = queriedName + "[0]";
6053 GLuint activeId = LOCAL_GL_INVALID_INDEX;
6054 for (const auto i : IntegerRange(res.active.activeUniforms.size())) {
6055 // O(N^2) ok for small N.
6056 const auto& activeInfoForI = res.active.activeUniforms[i];
6057 if (queriedName == activeInfoForI.name ||
6058 impliedProperArrayQueriedName == activeInfoForI.name) {
6059 activeId = i;
6060 break;
6063 ret.AppendElement(activeId);
6066 retval.SetValue(std::move(ret));
6069 already_AddRefed<WebGLUniformLocationJS> ClientWebGLContext::GetUniformLocation(
6070 const WebGLProgramJS& prog, const nsAString& name) const {
6071 const FuncScope funcScope(*this, "getUniformLocation");
6072 if (IsContextLost()) return nullptr;
6073 if (!prog.ValidateUsable(*this, "program")) return nullptr;
6075 const auto& res = GetLinkResult(prog);
6077 if (!prog.mUniformLocByName) {
6078 // Cache a map from name->location.
6079 // Since the only way to set uniforms requires calling GetUniformLocation,
6080 // we expect apps to query most active uniforms once for each scalar or
6081 // array. NB: Uniform array setters do have overflow semantics, even though
6082 // uniform locations aren't guaranteed contiguous, but GetUniformLocation
6083 // must still be called once per array.
6084 prog.mUniformLocByName.emplace();
6086 for (const auto& activeUniform : res.active.activeUniforms) {
6087 if (activeUniform.block_index != -1) continue;
6089 auto locName = activeUniform.name;
6090 const auto indexed = webgl::ParseIndexed(locName);
6091 if (indexed) {
6092 locName = indexed->name;
6095 const auto err = CheckGLSLVariableName(mIsWebGL2, locName);
6096 if (err) continue;
6098 const auto baseLength = locName.size();
6099 for (const auto& pair : activeUniform.locByIndex) {
6100 if (indexed) {
6101 locName.erase(baseLength); // Erase previous "[N]".
6102 locName += '[';
6103 locName += std::to_string(pair.first);
6104 locName += ']';
6106 const auto locInfo =
6107 WebGLProgramJS::UniformLocInfo{pair.second, activeUniform.elemType};
6108 prog.mUniformLocByName->insert({locName, locInfo});
6112 const auto& locByName = *(prog.mUniformLocByName);
6114 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
6115 auto loc = MaybeFind(locByName, nameU8);
6116 if (!loc) {
6117 loc = MaybeFind(locByName, nameU8 + "[0]");
6119 if (!loc) {
6120 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
6121 if (err) {
6122 EnqueueError(err->type, "%s", err->info.c_str());
6124 return nullptr;
6127 return AsAddRefed(new WebGLUniformLocationJS(*this, prog.mResult,
6128 loc->location, loc->elemType));
6131 std::array<uint16_t, 3> ValidUploadElemTypes(const GLenum elemType) {
6132 std::vector<GLenum> ret;
6133 switch (elemType) {
6134 case LOCAL_GL_BOOL:
6135 ret = {LOCAL_GL_FLOAT, LOCAL_GL_INT, LOCAL_GL_UNSIGNED_INT};
6136 break;
6137 case LOCAL_GL_BOOL_VEC2:
6138 ret = {LOCAL_GL_FLOAT_VEC2, LOCAL_GL_INT_VEC2,
6139 LOCAL_GL_UNSIGNED_INT_VEC2};
6140 break;
6141 case LOCAL_GL_BOOL_VEC3:
6142 ret = {LOCAL_GL_FLOAT_VEC3, LOCAL_GL_INT_VEC3,
6143 LOCAL_GL_UNSIGNED_INT_VEC3};
6144 break;
6145 case LOCAL_GL_BOOL_VEC4:
6146 ret = {LOCAL_GL_FLOAT_VEC4, LOCAL_GL_INT_VEC4,
6147 LOCAL_GL_UNSIGNED_INT_VEC4};
6148 break;
6150 case LOCAL_GL_SAMPLER_2D:
6151 case LOCAL_GL_SAMPLER_3D:
6152 case LOCAL_GL_SAMPLER_CUBE:
6153 case LOCAL_GL_SAMPLER_2D_SHADOW:
6154 case LOCAL_GL_SAMPLER_2D_ARRAY:
6155 case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
6156 case LOCAL_GL_SAMPLER_CUBE_SHADOW:
6157 case LOCAL_GL_INT_SAMPLER_2D:
6158 case LOCAL_GL_INT_SAMPLER_3D:
6159 case LOCAL_GL_INT_SAMPLER_CUBE:
6160 case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
6161 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
6162 case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
6163 case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
6164 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
6165 ret = {LOCAL_GL_INT};
6166 break;
6168 default:
6169 ret = {elemType};
6170 break;
6173 std::array<uint16_t, 3> arr = {};
6174 MOZ_ASSERT(arr[2] == 0);
6175 for (const auto i : IntegerRange(ret.size())) {
6176 arr[i] = AssertedCast<uint16_t>(ret[i]);
6178 return arr;
6181 void ClientWebGLContext::GetProgramInfoLog(const WebGLProgramJS& prog,
6182 nsAString& retval) const {
6183 retval.SetIsVoid(true);
6184 const FuncScope funcScope(*this, "getProgramInfoLog");
6185 if (IsContextLost()) return;
6186 if (!prog.ValidateUsable(*this, "program")) return;
6188 const auto& res = GetLinkResult(prog);
6189 CopyUTF8toUTF16(res.log, retval);
6192 void ClientWebGLContext::GetProgramParameter(
6193 JSContext* const js, const WebGLProgramJS& prog, const GLenum pname,
6194 JS::MutableHandle<JS::Value> retval) const {
6195 retval.set(JS::NullValue());
6196 const FuncScope funcScope(*this, "getProgramParameter");
6197 if (IsContextLost()) return;
6198 if (!prog.ValidateUsable(*this, "program")) return;
6200 retval.set([&]() -> JS::Value {
6201 switch (pname) {
6202 case LOCAL_GL_DELETE_STATUS:
6203 // "Is flagged for deletion?"
6204 return JS::BooleanValue(!prog.mKeepAlive);
6205 case LOCAL_GL_VALIDATE_STATUS:
6206 return JS::BooleanValue(prog.mLastValidate);
6207 case LOCAL_GL_ATTACHED_SHADERS: {
6208 size_t shaders = 0;
6209 for (const auto& pair : prog.mNextLink_Shaders) {
6210 const auto& slot = pair.second;
6211 if (slot.shader) {
6212 shaders += 1;
6215 return JS::NumberValue(shaders);
6217 default:
6218 break;
6221 const auto& res = GetLinkResult(prog);
6223 switch (pname) {
6224 case LOCAL_GL_LINK_STATUS:
6225 return JS::BooleanValue(res.success);
6227 case LOCAL_GL_ACTIVE_ATTRIBUTES:
6228 return JS::NumberValue(res.active.activeAttribs.size());
6230 case LOCAL_GL_ACTIVE_UNIFORMS:
6231 return JS::NumberValue(res.active.activeUniforms.size());
6233 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
6234 if (!mIsWebGL2) break;
6235 return JS::NumberValue(res.tfBufferMode);
6237 case LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS:
6238 if (!mIsWebGL2) break;
6239 return JS::NumberValue(res.active.activeTfVaryings.size());
6241 case LOCAL_GL_ACTIVE_UNIFORM_BLOCKS:
6242 if (!mIsWebGL2) break;
6243 return JS::NumberValue(res.active.activeUniformBlocks.size());
6245 default:
6246 break;
6248 EnqueueError_ArgEnum("pname", pname);
6249 return JS::NullValue();
6250 }());
6253 // -
6254 // WebGLShaderJS
6256 void ClientWebGLContext::CompileShader(WebGLShaderJS& shader) const {
6257 const FuncScope funcScope(*this, "compileShader");
6258 if (IsContextLost()) return;
6259 if (!shader.ValidateUsable(*this, "shader")) return;
6261 shader.mResult = {};
6262 Run<RPROC(CompileShader)>(shader.mId);
6265 void ClientWebGLContext::GetShaderInfoLog(const WebGLShaderJS& shader,
6266 nsAString& retval) const {
6267 retval.SetIsVoid(true);
6268 const FuncScope funcScope(*this, "getShaderInfoLog");
6269 if (IsContextLost()) return;
6270 if (!shader.ValidateUsable(*this, "shader")) return;
6272 const auto& result = GetCompileResult(shader);
6273 CopyUTF8toUTF16(result.log, retval);
6276 void ClientWebGLContext::GetShaderParameter(
6277 JSContext* const cx, const WebGLShaderJS& shader, const GLenum pname,
6278 JS::MutableHandle<JS::Value> retval) const {
6279 retval.set(JS::NullValue());
6280 const FuncScope funcScope(*this, "getShaderParameter");
6281 if (IsContextLost()) return;
6282 if (!shader.ValidateUsable(*this, "shader")) return;
6284 retval.set([&]() -> JS::Value {
6285 switch (pname) {
6286 case LOCAL_GL_SHADER_TYPE:
6287 return JS::NumberValue(shader.mType);
6289 case LOCAL_GL_DELETE_STATUS: // "Is flagged for deletion?"
6290 return JS::BooleanValue(!shader.mKeepAlive);
6292 case LOCAL_GL_COMPILE_STATUS: {
6293 const auto& result = GetCompileResult(shader);
6294 return JS::BooleanValue(result.success);
6297 default:
6298 EnqueueError_ArgEnum("pname", pname);
6299 return JS::NullValue();
6301 }());
6304 void ClientWebGLContext::GetShaderSource(const WebGLShaderJS& shader,
6305 nsAString& retval) const {
6306 retval.SetIsVoid(true);
6307 const FuncScope funcScope(*this, "getShaderSource");
6308 if (IsContextLost()) return;
6309 if (!shader.ValidateUsable(*this, "shader")) return;
6311 CopyUTF8toUTF16(shader.mSource, retval);
6314 void ClientWebGLContext::GetTranslatedShaderSource(const WebGLShaderJS& shader,
6315 nsAString& retval) const {
6316 retval.SetIsVoid(true);
6317 const FuncScope funcScope(*this, "getTranslatedShaderSource");
6318 if (IsContextLost()) return;
6319 if (!shader.ValidateUsable(*this, "shader")) return;
6321 const auto& result = GetCompileResult(shader);
6322 CopyUTF8toUTF16(result.translatedSource, retval);
6325 void ClientWebGLContext::ShaderSource(WebGLShaderJS& shader,
6326 const nsAString& sourceU16) const {
6327 const FuncScope funcScope(*this, "shaderSource");
6328 if (IsContextLost()) return;
6329 if (!shader.ValidateUsable(*this, "shader")) return;
6331 shader.mSource = ToString(NS_ConvertUTF16toUTF8(sourceU16));
6332 Run<RPROC(ShaderSource)>(shader.mId, shader.mSource);
6335 // -
6337 const webgl::CompileResult& ClientWebGLContext::GetCompileResult(
6338 const WebGLShaderJS& shader) const {
6339 if (shader.mResult.pending) {
6340 shader.mResult = [&]() {
6341 const auto& inProcess = mNotLost->inProcess;
6342 if (inProcess) {
6343 return inProcess->GetCompileResult(shader.mId);
6345 const auto& child = mNotLost->outOfProcess;
6346 child->FlushPendingCmds();
6347 webgl::CompileResult ret = {};
6348 if (!child->SendGetCompileResult(shader.mId, &ret)) {
6349 ret = {};
6351 return ret;
6352 }();
6354 return shader.mResult;
6357 const webgl::LinkResult& ClientWebGLContext::GetLinkResult(
6358 const WebGLProgramJS& prog) const {
6359 if (prog.mResult->pending) {
6360 const auto notLost =
6361 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
6362 if (!notLost) return *(prog.mResult);
6364 *(prog.mResult) = [&]() {
6365 const auto& inProcess = mNotLost->inProcess;
6366 if (inProcess) {
6367 return inProcess->GetLinkResult(prog.mId);
6369 const auto& child = mNotLost->outOfProcess;
6370 child->FlushPendingCmds();
6371 webgl::LinkResult ret;
6372 if (!child->SendGetLinkResult(prog.mId, &ret)) {
6373 ret = {};
6375 return ret;
6376 }();
6378 prog.mUniformBlockBindings.resize(
6379 prog.mResult->active.activeUniformBlocks.size());
6381 auto& state = State();
6382 if (state.mCurrentProgram == &prog && prog.mResult->success) {
6383 state.mActiveLinkResult = prog.mResult;
6386 return *(prog.mResult);
6389 #undef RPROC
6391 // ---------------------------
6393 bool ClientWebGLContext::ValidateArrayBufferView(
6394 const dom::ArrayBufferView& view, GLuint elemOffset,
6395 GLuint elemCountOverride, const GLenum errorEnum, uint8_t** const out_bytes,
6396 size_t* const out_byteLen) const {
6397 view.ComputeState();
6398 uint8_t* const bytes = view.Data();
6399 const size_t byteLen = view.Length();
6401 const auto& elemSize = SizeOfViewElem(view);
6403 size_t elemCount = byteLen / elemSize;
6404 if (elemOffset > elemCount) {
6405 EnqueueError(errorEnum, "Invalid offset into ArrayBufferView.");
6406 return false;
6408 elemCount -= elemOffset;
6410 if (elemCountOverride) {
6411 if (elemCountOverride > elemCount) {
6412 EnqueueError(errorEnum, "Invalid sub-length for ArrayBufferView.");
6413 return false;
6415 elemCount = elemCountOverride;
6418 *out_bytes = bytes + (elemOffset * elemSize);
6419 *out_byteLen = elemCount * elemSize;
6420 return true;
6423 // ---------------------------
6425 webgl::ObjectJS::ObjectJS(const ClientWebGLContext& webgl)
6426 : mGeneration(webgl.mNotLost), mId(webgl.mNotLost->state.NextId()) {}
6428 // -
6430 WebGLFramebufferJS::WebGLFramebufferJS(const ClientWebGLContext& webgl,
6431 bool opaque)
6432 : webgl::ObjectJS(webgl), mOpaque(opaque) {
6433 (void)mAttachments[LOCAL_GL_DEPTH_ATTACHMENT];
6434 (void)mAttachments[LOCAL_GL_STENCIL_ATTACHMENT];
6435 if (!webgl.mIsWebGL2) {
6436 (void)mAttachments[LOCAL_GL_DEPTH_STENCIL_ATTACHMENT];
6439 EnsureColorAttachments();
6442 void WebGLFramebufferJS::EnsureColorAttachments() {
6443 const auto& webgl = Context();
6444 const auto& limits = webgl->Limits();
6445 auto maxColorDrawBuffers = limits.maxColorDrawBuffers;
6446 if (!webgl->mIsWebGL2 &&
6447 !webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers)) {
6448 maxColorDrawBuffers = 1;
6450 for (const auto i : IntegerRange(maxColorDrawBuffers)) {
6451 (void)mAttachments[LOCAL_GL_COLOR_ATTACHMENT0 + i];
6455 WebGLProgramJS::WebGLProgramJS(const ClientWebGLContext& webgl)
6456 : webgl::ObjectJS(webgl),
6457 mKeepAlive(std::make_shared<webgl::ProgramKeepAlive>(*this)),
6458 mKeepAliveWeak(mKeepAlive) {
6459 (void)mNextLink_Shaders[LOCAL_GL_VERTEX_SHADER];
6460 (void)mNextLink_Shaders[LOCAL_GL_FRAGMENT_SHADER];
6462 mResult = std::make_shared<webgl::LinkResult>();
6465 WebGLShaderJS::WebGLShaderJS(const ClientWebGLContext& webgl, const GLenum type)
6466 : webgl::ObjectJS(webgl),
6467 mType(type),
6468 mKeepAlive(std::make_shared<webgl::ShaderKeepAlive>(*this)),
6469 mKeepAliveWeak(mKeepAlive) {}
6471 WebGLTransformFeedbackJS::WebGLTransformFeedbackJS(
6472 const ClientWebGLContext& webgl)
6473 : webgl::ObjectJS(webgl),
6474 mAttribBuffers(webgl::kMaxTransformFeedbackSeparateAttribs) {}
6476 WebGLVertexArrayJS::WebGLVertexArrayJS(const ClientWebGLContext& webgl)
6477 : webgl::ObjectJS(webgl), mAttribBuffers(webgl.Limits().maxVertexAttribs) {}
6479 // -
6481 #define _(WebGLType) \
6482 JSObject* WebGLType##JS::WrapObject(JSContext* const cx, \
6483 JS::Handle<JSObject*> givenProto) { \
6484 return dom::WebGLType##_Binding::Wrap(cx, this, givenProto); \
6487 _(WebGLBuffer)
6488 _(WebGLFramebuffer)
6489 _(WebGLProgram)
6490 _(WebGLQuery)
6491 _(WebGLRenderbuffer)
6492 _(WebGLSampler)
6493 _(WebGLShader)
6494 _(WebGLSync)
6495 _(WebGLTexture)
6496 _(WebGLTransformFeedback)
6497 _(WebGLUniformLocation)
6498 //_(WebGLVertexArray) // The webidl is `WebGLVertexArrayObject` :(
6500 #undef _
6502 JSObject* WebGLVertexArrayJS::WrapObject(JSContext* const cx,
6503 JS::Handle<JSObject*> givenProto) {
6504 return dom::WebGLVertexArrayObject_Binding::Wrap(cx, this, givenProto);
6507 bool WebGLActiveInfoJS::WrapObject(JSContext* const cx,
6508 JS::Handle<JSObject*> givenProto,
6509 JS::MutableHandle<JSObject*> reflector) {
6510 return dom::WebGLActiveInfo_Binding::Wrap(cx, this, givenProto, reflector);
6513 bool WebGLShaderPrecisionFormatJS::WrapObject(
6514 JSContext* const cx, JS::Handle<JSObject*> givenProto,
6515 JS::MutableHandle<JSObject*> reflector) {
6516 return dom::WebGLShaderPrecisionFormat_Binding::Wrap(cx, this, givenProto,
6517 reflector);
6520 // ---------------------
6522 // Todo: Move this to RefPtr.h.
6523 template <typename T>
6524 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
6525 const RefPtr<T>& field, const char* name,
6526 uint32_t flags) {
6527 ImplCycleCollectionTraverse(callback, const_cast<RefPtr<T>&>(field), name,
6528 flags);
6531 // -
6533 template <typename T>
6534 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
6535 const std::vector<RefPtr<T>>& field,
6536 const char* name, uint32_t flags) {
6537 for (const auto& cur : field) {
6538 ImplCycleCollectionTraverse(callback, cur, name, flags);
6542 template <typename T>
6543 void ImplCycleCollectionUnlink(std::vector<RefPtr<T>>& field) {
6544 field = {};
6547 // -
6549 template <typename T, size_t N>
6550 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
6551 const std::array<RefPtr<T>, N>& field,
6552 const char* name, uint32_t flags) {
6553 for (const auto& cur : field) {
6554 ImplCycleCollectionTraverse(callback, cur, name, flags);
6558 template <typename T, size_t N>
6559 void ImplCycleCollectionUnlink(std::array<RefPtr<T>, N>& field) {
6560 field = {};
6563 // -
6565 template <typename T>
6566 void ImplCycleCollectionTraverse(
6567 nsCycleCollectionTraversalCallback& callback,
6568 const std::unordered_map<GLenum, RefPtr<T>>& field, const char* name,
6569 uint32_t flags) {
6570 for (const auto& pair : field) {
6571 ImplCycleCollectionTraverse(callback, pair.second, name, flags);
6575 template <typename T>
6576 void ImplCycleCollectionUnlink(std::unordered_map<GLenum, RefPtr<T>>& field) {
6577 field = {};
6580 // -
6582 void ImplCycleCollectionTraverse(
6583 nsCycleCollectionTraversalCallback& callback,
6584 const std::unordered_map<GLenum, WebGLFramebufferJS::Attachment>& field,
6585 const char* name, uint32_t flags) {
6586 for (const auto& pair : field) {
6587 const auto& attach = pair.second;
6588 ImplCycleCollectionTraverse(callback, attach.rb, name, flags);
6589 ImplCycleCollectionTraverse(callback, attach.tex, name, flags);
6593 void ImplCycleCollectionUnlink(
6594 std::unordered_map<GLenum, WebGLFramebufferJS::Attachment>& field) {
6595 field = {};
6598 // -
6600 void ImplCycleCollectionTraverse(
6601 nsCycleCollectionTraversalCallback& callback,
6602 const std::unordered_map<GLenum, WebGLProgramJS::Attachment>& field,
6603 const char* name, uint32_t flags) {
6604 for (const auto& pair : field) {
6605 const auto& attach = pair.second;
6606 ImplCycleCollectionTraverse(callback, attach.shader, name, flags);
6610 void ImplCycleCollectionUnlink(
6611 std::unordered_map<GLenum, WebGLProgramJS::Attachment>& field) {
6612 field = {};
6615 // -
6617 void ImplCycleCollectionUnlink(
6618 const RefPtr<ClientWebGLExtensionLoseContext>& field) {
6619 const_cast<RefPtr<ClientWebGLExtensionLoseContext>&>(field) = nullptr;
6621 void ImplCycleCollectionUnlink(const RefPtr<WebGLProgramJS>& field) {
6622 const_cast<RefPtr<WebGLProgramJS>&>(field) = nullptr;
6624 void ImplCycleCollectionUnlink(const RefPtr<WebGLShaderJS>& field) {
6625 const_cast<RefPtr<WebGLShaderJS>&>(field) = nullptr;
6628 // ----------------------
6630 void ImplCycleCollectionTraverse(
6631 nsCycleCollectionTraversalCallback& callback,
6632 const std::shared_ptr<webgl::NotLostData>& field, const char* name,
6633 uint32_t flags) {
6634 if (!field) return;
6636 ImplCycleCollectionTraverse(callback, field->extensions,
6637 "NotLostData.extensions", flags);
6639 const auto& state = field->state;
6641 ImplCycleCollectionTraverse(callback, state.mDefaultTfo, "state.mDefaultTfo",
6642 flags);
6643 ImplCycleCollectionTraverse(callback, state.mDefaultVao, "state.mDefaultVao",
6644 flags);
6646 ImplCycleCollectionTraverse(callback, state.mCurrentProgram,
6647 "state.mCurrentProgram", flags);
6649 ImplCycleCollectionTraverse(callback, state.mBoundBufferByTarget,
6650 "state.mBoundBufferByTarget", flags);
6651 ImplCycleCollectionTraverse(callback, state.mBoundUbos, "state.mBoundUbos",
6652 flags);
6653 ImplCycleCollectionTraverse(callback, state.mBoundDrawFb,
6654 "state.mBoundDrawFb", flags);
6655 ImplCycleCollectionTraverse(callback, state.mBoundReadFb,
6656 "state.mBoundReadFb", flags);
6657 ImplCycleCollectionTraverse(callback, state.mBoundRb, "state.mBoundRb",
6658 flags);
6659 ImplCycleCollectionTraverse(callback, state.mBoundTfo, "state.mBoundTfo",
6660 flags);
6661 ImplCycleCollectionTraverse(callback, state.mBoundVao, "state.mBoundVao",
6662 flags);
6663 ImplCycleCollectionTraverse(callback, state.mCurrentQueryByTarget,
6664 "state.state.mCurrentQueryByTarget", flags);
6666 for (const auto& texUnit : state.mTexUnits) {
6667 ImplCycleCollectionTraverse(callback, texUnit.sampler,
6668 "state.mTexUnits[].sampler", flags);
6669 ImplCycleCollectionTraverse(callback, texUnit.texByTarget,
6670 "state.mTexUnits[].texByTarget", flags);
6674 void ImplCycleCollectionUnlink(std::shared_ptr<webgl::NotLostData>& field) {
6675 if (!field) return;
6676 const auto keepAlive = field;
6677 keepAlive->extensions = {};
6678 keepAlive->state = {};
6679 field = nullptr;
6682 // -----------------------------------------------------
6684 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLBufferJS)
6685 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLFramebufferJS, mAttachments)
6686 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLProgramJS, mNextLink_Shaders)
6687 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLQueryJS)
6688 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLRenderbufferJS)
6689 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLSamplerJS)
6690 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLShaderJS)
6691 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLSyncJS)
6692 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLTextureJS)
6693 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLTransformFeedbackJS, mAttribBuffers,
6694 mActiveProgram)
6695 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLUniformLocationJS)
6696 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLVertexArrayJS, mIndexBuffer,
6697 mAttribBuffers)
6699 // -
6701 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ClientWebGLContext)
6702 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
6703 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
6704 NS_INTERFACE_MAP_ENTRY(nsISupports)
6705 NS_INTERFACE_MAP_END
6707 NS_IMPL_CYCLE_COLLECTING_ADDREF(ClientWebGLContext)
6708 NS_IMPL_CYCLE_COLLECTING_RELEASE(ClientWebGLContext)
6710 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(
6711 ClientWebGLContext, mExtLoseContext, mNotLost,
6712 // Don't forget nsICanvasRenderingContextInternal:
6713 mCanvasElement, mOffscreenCanvas)
6715 // -----------------------------
6717 #define _(X) \
6718 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGL##X##JS, AddRef) \
6719 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGL##X##JS, Release)
6721 _(Buffer)
6722 _(Framebuffer)
6723 _(Program)
6724 _(Query)
6725 _(Renderbuffer)
6726 _(Sampler)
6727 _(Shader)
6728 _(Sync)
6729 _(Texture)
6730 _(TransformFeedback)
6731 _(UniformLocation)
6732 _(VertexArray)
6734 #undef _
6736 } // namespace mozilla