Bug 1941128 - Turn off network.dns.native_https_query on Mac again
[gecko.git] / dom / canvas / ClientWebGLContext.cpp
blob7a60caab578dd45a714657da8962dd861c22097f
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 "gfxCrashReporterUtils.h"
12 #include "HostWebGLContext.h"
13 #include "js/PropertyAndElement.h" // JS_DefineElement
14 #include "js/ScalarType.h" // js::Scalar::Type
15 #include "mozilla/dom/Document.h"
16 #include "mozilla/dom/ToJSValue.h"
17 #include "mozilla/dom/TypedArray.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/CompositableForwarder.h"
26 #include "mozilla/layers/CompositorBridgeChild.h"
27 #include "mozilla/layers/ImageBridgeChild.h"
28 #include "mozilla/layers/OOPCanvasRenderer.h"
29 #include "mozilla/layers/TextureClientSharedSurface.h"
30 #include "mozilla/layers/WebRenderUserData.h"
31 #include "mozilla/layers/WebRenderCanvasRenderer.h"
32 #include "mozilla/ResultVariant.h"
33 #include "mozilla/ScopeExit.h"
34 #include "mozilla/StaticPrefs_webgl.h"
35 #include "nsContentUtils.h"
36 #include "nsDisplayList.h"
37 #include "TexUnpackBlob.h"
38 #include "WebGLFormats.h"
39 #include "WebGLMethodDispatcher.h"
40 #include "WebGLChild.h"
41 #include "WebGLTextureUpload.h"
42 #include "WebGLValidateStrings.h"
44 namespace mozilla {
46 namespace webgl {
47 std::string SanitizeRenderer(const std::string&);
48 } // namespace webgl
50 // -
52 webgl::NotLostData::NotLostData(ClientWebGLContext& _context)
53 : context(_context) {}
55 webgl::NotLostData::~NotLostData() {
56 if (outOfProcess) {
57 outOfProcess->Destroy();
61 // -
63 bool webgl::ObjectJS::ValidateForContext(
64 const ClientWebGLContext& targetContext, const char* const argName) const {
65 if (!IsForContext(targetContext)) {
66 targetContext.EnqueueError(
67 LOCAL_GL_INVALID_OPERATION,
68 "`%s` is from a different (or lost) WebGL context.", argName);
69 return false;
71 return true;
74 void webgl::ObjectJS::WarnInvalidUse(const ClientWebGLContext& targetContext,
75 const char* const argName) const {
76 if (!ValidateForContext(targetContext, argName)) return;
78 const auto errEnum = ErrorOnDeleted();
79 targetContext.EnqueueError(errEnum, "Object `%s` is already deleted.",
80 argName);
83 // -
85 WebGLBufferJS::~WebGLBufferJS() {
86 const auto webgl = Context();
87 if (webgl) {
88 webgl->DeleteBuffer(this);
92 WebGLFramebufferJS::~WebGLFramebufferJS() {
93 const auto webgl = Context();
94 if (webgl) {
95 webgl->DeleteFramebuffer(this);
99 WebGLQueryJS::~WebGLQueryJS() {
100 const auto webgl = Context();
101 if (webgl) {
102 webgl->DeleteQuery(this);
106 WebGLRenderbufferJS::~WebGLRenderbufferJS() {
107 const auto webgl = Context();
108 if (webgl) {
109 webgl->DeleteRenderbuffer(this);
113 WebGLSamplerJS::~WebGLSamplerJS() {
114 const auto webgl = Context();
115 if (webgl) {
116 webgl->DeleteSampler(this);
120 WebGLSyncJS::~WebGLSyncJS() {
121 const auto webgl = Context();
122 if (webgl) {
123 webgl->DeleteSync(this);
127 WebGLTextureJS::~WebGLTextureJS() {
128 const auto webgl = Context();
129 if (webgl) {
130 webgl->DeleteTexture(this);
134 WebGLTransformFeedbackJS::~WebGLTransformFeedbackJS() {
135 const auto webgl = Context();
136 if (webgl) {
137 webgl->DeleteTransformFeedback(this);
141 WebGLVertexArrayJS::~WebGLVertexArrayJS() {
142 const auto webgl = Context();
143 if (webgl) {
144 webgl->DeleteVertexArray(this);
148 // -
150 static bool GetJSScalarFromGLType(GLenum type,
151 js::Scalar::Type* const out_scalarType) {
152 switch (type) {
153 case LOCAL_GL_BYTE:
154 *out_scalarType = js::Scalar::Int8;
155 return true;
157 case LOCAL_GL_UNSIGNED_BYTE:
158 *out_scalarType = js::Scalar::Uint8;
159 return true;
161 case LOCAL_GL_SHORT:
162 *out_scalarType = js::Scalar::Int16;
163 return true;
165 case LOCAL_GL_HALF_FLOAT:
166 case LOCAL_GL_HALF_FLOAT_OES:
167 case LOCAL_GL_UNSIGNED_SHORT:
168 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
169 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
170 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
171 *out_scalarType = js::Scalar::Uint16;
172 return true;
174 case LOCAL_GL_UNSIGNED_INT:
175 case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
176 case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
177 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
178 case LOCAL_GL_UNSIGNED_INT_24_8:
179 *out_scalarType = js::Scalar::Uint32;
180 return true;
181 case LOCAL_GL_INT:
182 *out_scalarType = js::Scalar::Int32;
183 return true;
185 case LOCAL_GL_FLOAT:
186 *out_scalarType = js::Scalar::Float32;
187 return true;
189 default:
190 return false;
194 ClientWebGLContext::ClientWebGLContext(const bool webgl2)
195 : mIsWebGL2(webgl2),
196 mExtLoseContext(new ClientWebGLExtensionLoseContext(*this)) {}
198 ClientWebGLContext::~ClientWebGLContext() { RemovePostRefreshObserver(); }
200 void ClientWebGLContext::JsWarning(const std::string& utf8) const {
201 nsIGlobalObject* global = nullptr;
202 if (mCanvasElement) {
203 mozilla::dom::Document* doc = mCanvasElement->OwnerDoc();
204 if (doc) {
205 global = doc->GetScopeObject();
207 } else if (mOffscreenCanvas) {
208 global = mOffscreenCanvas->GetOwnerGlobal();
211 dom::AutoJSAPI api;
212 if (!api.Init(global)) {
213 return;
215 const auto& cx = api.cx();
216 JS::WarnUTF8(cx, "%s", utf8.c_str());
219 void AutoJsWarning(const std::string& utf8) {
220 if (NS_IsMainThread()) {
221 const AutoJSContext cx;
222 JS::WarnUTF8(cx, "%s", utf8.c_str());
223 return;
226 JSContext* cx = dom::GetCurrentWorkerThreadJSContext();
227 if (NS_WARN_IF(!cx)) {
228 return;
231 JS::WarnUTF8(cx, "%s", utf8.c_str());
234 // ---------
236 bool ClientWebGLContext::DispatchEvent(const nsAString& eventName) const {
237 const auto kCanBubble = CanBubble::eYes;
238 const auto kIsCancelable = Cancelable::eYes;
239 bool useDefaultHandler = true;
241 if (mCanvasElement) {
242 nsContentUtils::DispatchTrustedEvent(mCanvasElement->OwnerDoc(),
243 mCanvasElement, eventName, kCanBubble,
244 kIsCancelable, &useDefaultHandler);
245 } else if (mOffscreenCanvas) {
246 // OffscreenCanvas case
247 RefPtr<dom::Event> event =
248 new dom::Event(mOffscreenCanvas, nullptr, nullptr);
249 event->InitEvent(eventName, kCanBubble, kIsCancelable);
250 event->SetTrusted(true);
251 useDefaultHandler = mOffscreenCanvas->DispatchEvent(
252 *event, dom::CallerType::System, IgnoreErrors());
254 return useDefaultHandler;
257 // -
259 void ClientWebGLContext::EmulateLoseContext() const {
260 const FuncScope funcScope(*this, "loseContext");
261 if (mLossStatus != webgl::LossStatus::Ready) {
262 JsWarning("loseContext: Already lost.");
263 if (!mNextError) {
264 mNextError = LOCAL_GL_INVALID_OPERATION;
266 return;
268 OnContextLoss(webgl::ContextLossReason::Manual);
271 void ClientWebGLContext::OnContextLoss(
272 const webgl::ContextLossReason reason) const {
273 JsWarning("WebGL context was lost.");
275 if (mNotLost) {
276 for (const auto& ext : mNotLost->extensions) {
277 if (!ext) continue;
278 ext->mContext = nullptr; // Detach.
280 mNotLost = {}; // Lost now!
281 mNextError = LOCAL_GL_CONTEXT_LOST_WEBGL;
284 switch (reason) {
285 case webgl::ContextLossReason::Guilty:
286 mLossStatus = webgl::LossStatus::LostForever;
287 break;
289 case webgl::ContextLossReason::None:
290 mLossStatus = webgl::LossStatus::Lost;
291 break;
293 case webgl::ContextLossReason::Manual:
294 mLossStatus = webgl::LossStatus::LostManually;
295 break;
298 const auto weak = WeakPtr<const ClientWebGLContext>(this);
299 const auto fnRun = [weak]() {
300 const auto strong = RefPtr<const ClientWebGLContext>(weak);
301 if (!strong) return;
302 strong->Event_webglcontextlost();
304 already_AddRefed<mozilla::CancelableRunnable> runnable =
305 NS_NewCancelableRunnableFunction("enqueue Event_webglcontextlost", fnRun);
306 NS_DispatchToCurrentThread(std::move(runnable));
309 void ClientWebGLContext::Event_webglcontextlost() const {
310 const bool useDefaultHandler = DispatchEvent(u"webglcontextlost"_ns);
311 if (useDefaultHandler) {
312 mLossStatus = webgl::LossStatus::LostForever;
315 if (mLossStatus == webgl::LossStatus::Lost) {
316 RestoreContext(webgl::LossStatus::Lost);
320 void ClientWebGLContext::RestoreContext(
321 const webgl::LossStatus requiredStatus) const {
322 if (requiredStatus != mLossStatus) {
323 JsWarning(
324 "restoreContext: Only valid iff context lost with loseContext().");
325 if (!mNextError) {
326 mNextError = LOCAL_GL_INVALID_OPERATION;
328 return;
330 MOZ_RELEASE_ASSERT(mLossStatus == webgl::LossStatus::Lost ||
331 mLossStatus == webgl::LossStatus::LostManually);
333 if (mAwaitingRestore) return;
334 mAwaitingRestore = true;
336 const auto weak = WeakPtr<const ClientWebGLContext>(this);
337 const auto fnRun = [weak]() {
338 const auto strong = RefPtr<const ClientWebGLContext>(weak);
339 if (!strong) return;
340 strong->Event_webglcontextrestored();
342 already_AddRefed<mozilla::CancelableRunnable> runnable =
343 NS_NewCancelableRunnableFunction("enqueue Event_webglcontextrestored",
344 fnRun);
345 NS_DispatchToCurrentThread(std::move(runnable));
348 void ClientWebGLContext::Event_webglcontextrestored() const {
349 mAwaitingRestore = false;
350 mLossStatus = webgl::LossStatus::Ready;
351 mNextError = 0;
353 uvec2 requestSize;
354 if (mCanvasElement) {
355 requestSize = {mCanvasElement->Width(), mCanvasElement->Height()};
356 } else if (mOffscreenCanvas) {
357 requestSize = {mOffscreenCanvas->Width(), mOffscreenCanvas->Height()};
358 } else {
359 MOZ_ASSERT_UNREACHABLE("no HTMLCanvasElement or OffscreenCanvas!");
360 return;
363 if (!requestSize.x) {
364 requestSize.x = 1;
366 if (!requestSize.y) {
367 requestSize.y = 1;
370 const auto mutThis = const_cast<ClientWebGLContext*>(
371 this); // TODO: Make context loss non-mutable.
372 if (!mutThis->CreateHostContext(requestSize)) {
373 mLossStatus = webgl::LossStatus::LostForever;
374 return;
377 mResetLayer = true;
379 (void)DispatchEvent(u"webglcontextrestored"_ns);
382 // ---------
384 void ClientWebGLContext::ThrowEvent_WebGLContextCreationError(
385 const std::string& text) const {
386 nsCString msg;
387 msg.AppendPrintf("Failed to create WebGL context: %s", text.c_str());
388 JsWarning(msg.BeginReading());
390 RefPtr<dom::EventTarget> target = mCanvasElement;
391 if (!target && mOffscreenCanvas) {
392 target = mOffscreenCanvas;
393 } else if (!target) {
394 return;
397 const auto kEventName = u"webglcontextcreationerror"_ns;
399 dom::WebGLContextEventInit eventInit;
400 // eventInit.mCancelable = true; // The spec says this, but it's silly.
401 eventInit.mStatusMessage = NS_ConvertASCIItoUTF16(text.c_str());
403 const RefPtr<dom::WebGLContextEvent> event =
404 dom::WebGLContextEvent::Constructor(target, kEventName, eventInit);
405 event->SetTrusted(true);
407 target->DispatchEvent(*event);
410 // -------------------------------------------------------------------------
411 // Client-side helper methods. Dispatch to a Host method.
412 // -------------------------------------------------------------------------
414 // If we are running WebGL in this process then call the HostWebGLContext
415 // method directly. Otherwise, dispatch over IPC.
416 template <typename MethodT, typename... Args>
417 void ClientWebGLContext::Run_WithDestArgTypes(
418 std::optional<JS::AutoCheckCannotGC>&& noGc, const MethodT method,
419 const size_t id, const Args&... args) const {
420 const auto notLost =
421 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
423 // `AutoCheckCannotGC` must be reset after the GC data is done being used but
424 // *before* the `notLost` destructor runs, since the latter can GC.
425 const auto cleanup = MakeScopeExit([&]() { noGc.reset(); });
427 if (IsContextLost()) {
428 return;
431 const auto& inProcess = notLost->inProcess;
432 if (inProcess) {
433 (inProcess.get()->*method)(args...);
434 return;
437 const auto& child = notLost->outOfProcess;
439 const auto info = webgl::SerializationInfo(id, args...);
440 const auto maybeDest = child->AllocPendingCmdBytes(info.requiredByteCount,
441 info.alignmentOverhead);
442 if (!maybeDest) {
443 noGc.reset(); // Reset early, as GC data will not be used, but JsWarning
444 // can GC.
445 JsWarning("Failed to allocate internal command buffer.");
446 OnContextLoss(webgl::ContextLossReason::None);
447 return;
449 const auto& destBytes = *maybeDest;
450 webgl::Serialize(destBytes, id, args...);
453 // -
455 #define RPROC(_METHOD) \
456 decltype(&HostWebGLContext::_METHOD), &HostWebGLContext::_METHOD
458 // ------------------------- Composition, etc -------------------------
460 void ClientWebGLContext::OnBeforePaintTransaction() { Present(nullptr); }
462 void ClientWebGLContext::EndComposition() {
463 // Mark ourselves as no longer invalidated.
464 MarkContextClean();
467 // -
469 layers::TextureType ClientWebGLContext::GetTexTypeForSwapChain() const {
470 const RefPtr<layers::ImageBridgeChild> imageBridge =
471 layers::ImageBridgeChild::GetSingleton();
472 const bool isOutOfProcess = mNotLost && mNotLost->outOfProcess != nullptr;
473 return layers::TexTypeForWebgl(imageBridge, isOutOfProcess);
476 void ClientWebGLContext::Present(WebGLFramebufferJS* const xrFb,
477 const bool webvr,
478 const webgl::SwapChainOptions& options) {
479 const auto texType = GetTexTypeForSwapChain();
480 Present(xrFb, texType, webvr, options);
483 // Fill in remote texture ids to SwapChainOptions if async present is enabled.
484 webgl::SwapChainOptions ClientWebGLContext::PrepareAsyncSwapChainOptions(
485 WebGLFramebufferJS* fb, bool webvr,
486 const webgl::SwapChainOptions& options) {
487 // Currently remote texture ids should only be set internally.
488 MOZ_ASSERT(!options.remoteTextureOwnerId.IsValid() &&
489 !options.remoteTextureId.IsValid());
490 // Async present only works when out-of-process. It is not supported in WebVR.
491 // Allow it if it is either forced or if the pref is set.
492 if (fb || webvr) {
493 return options;
495 if (!IsContextLost() && !mNotLost->inProcess &&
496 (options.forceAsyncPresent ||
497 StaticPrefs::webgl_out_of_process_async_present())) {
498 if (!mRemoteTextureOwnerId) {
499 mRemoteTextureOwnerId = Some(layers::RemoteTextureOwnerId::GetNext());
501 mLastRemoteTextureId = Some(layers::RemoteTextureId::GetNext());
502 webgl::SwapChainOptions asyncOptions = options;
503 asyncOptions.remoteTextureOwnerId = *mRemoteTextureOwnerId;
504 asyncOptions.remoteTextureId = *mLastRemoteTextureId;
505 return asyncOptions;
507 // Clear the current remote texture id so that we disable async.
508 mRemoteTextureOwnerId = Nothing();
509 return options;
512 void ClientWebGLContext::Present(WebGLFramebufferJS* const xrFb,
513 const layers::TextureType type,
514 const bool webvr,
515 const webgl::SwapChainOptions& options) {
516 if (!mIsCanvasDirty && !xrFb) return;
517 if (!xrFb) {
518 mIsCanvasDirty = false;
520 CancelAutoFlush();
521 webgl::SwapChainOptions asyncOptions =
522 PrepareAsyncSwapChainOptions(xrFb, webvr, options);
523 Run<RPROC(Present)>(xrFb ? xrFb->mId : 0, type, webvr, asyncOptions);
526 void ClientWebGLContext::CopyToSwapChain(
527 WebGLFramebufferJS* const fb, const webgl::SwapChainOptions& options) {
528 CancelAutoFlush();
529 const auto texType = GetTexTypeForSwapChain();
530 webgl::SwapChainOptions asyncOptions =
531 PrepareAsyncSwapChainOptions(fb, false, options);
532 Run<RPROC(CopyToSwapChain)>(fb ? fb->mId : 0, texType, asyncOptions);
535 void ClientWebGLContext::EndOfFrame() {
536 CancelAutoFlush();
537 Run<RPROC(EndOfFrame)>();
540 Maybe<layers::SurfaceDescriptor> ClientWebGLContext::GetFrontBuffer(
541 WebGLFramebufferJS* const fb, bool vr) {
542 const FuncScope funcScope(*this, "<GetFrontBuffer>");
543 if (IsContextLost()) return {};
545 const auto& inProcess = mNotLost->inProcess;
546 if (inProcess) {
547 return inProcess->GetFrontBuffer(fb ? fb->mId : 0, vr);
550 const auto& child = mNotLost->outOfProcess;
551 child->FlushPendingCmds();
553 // Always synchronously get the front buffer if not using a remote texture.
554 bool needsSync = true;
555 Maybe<layers::SurfaceDescriptor> syncDesc;
556 Maybe<layers::SurfaceDescriptor> remoteDesc;
557 auto& info = child->GetFlushedCmdInfo();
559 // If valid remote texture data was set for async present, then use it.
560 if (!fb && !vr && mRemoteTextureOwnerId && mLastRemoteTextureId) {
561 const auto tooManyFlushes = 10;
562 // If there are many flushed cmds, force synchronous IPC to avoid too many
563 // pending ipc messages. Otherwise don't sync for other cases to avoid any
564 // performance penalty.
565 needsSync = XRE_IsParentProcess() ||
566 gfx::gfxVars::WebglOopAsyncPresentForceSync() ||
567 info.flushesSinceLastCongestionCheck > tooManyFlushes;
569 // Only send over a remote texture descriptor if the WebGLChild actor is
570 // alive to ensure the remote texture id is valid.
571 if (child->CanSend()) {
572 remoteDesc = Some(layers::SurfaceDescriptorRemoteTexture(
573 *mLastRemoteTextureId, *mRemoteTextureOwnerId));
577 if (needsSync &&
578 !child->SendGetFrontBuffer(fb ? fb->mId : 0, vr, &syncDesc)) {
579 return {};
582 // Reset flushesSinceLastCongestionCheck
583 info.flushesSinceLastCongestionCheck = 0;
584 info.congestionCheckGeneration++;
586 // If there is a remote texture descriptor, use that preferentially, as the
587 // sync front buffer descriptor was only created to force a sync first.
588 return remoteDesc ? remoteDesc : syncDesc;
591 Maybe<layers::SurfaceDescriptor> ClientWebGLContext::PresentFrontBuffer(
592 WebGLFramebufferJS* const fb, bool webvr) {
593 const auto texType = GetTexTypeForSwapChain();
594 Present(fb, texType, webvr);
595 return GetFrontBuffer(fb, webvr);
598 already_AddRefed<layers::FwdTransactionTracker>
599 ClientWebGLContext::UseCompositableForwarder(
600 layers::CompositableForwarder* aForwarder) {
601 if (mRemoteTextureOwnerId) {
602 return layers::FwdTransactionTracker::GetOrCreate(mFwdTransactionTracker);
604 return nullptr;
607 void ClientWebGLContext::OnDestroyChild(dom::WebGLChild* aChild) {
608 // Since NotLostData may be destructing at this point, the RefPtr to
609 // WebGLChild may be unreliable. Instead, it must be explicitly passed in.
610 if (mRemoteTextureOwnerId && mFwdTransactionTracker &&
611 mFwdTransactionTracker->IsUsed()) {
612 (void)aChild->SendWaitForTxn(
613 *mRemoteTextureOwnerId,
614 layers::ToRemoteTextureTxnType(mFwdTransactionTracker),
615 layers::ToRemoteTextureTxnId(mFwdTransactionTracker));
619 void ClientWebGLContext::ClearVRSwapChain() { Run<RPROC(ClearVRSwapChain)>(); }
621 // -
623 bool ClientWebGLContext::UpdateWebRenderCanvasData(
624 nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
625 CanvasRenderer* renderer = aCanvasData->GetCanvasRenderer();
627 if (!IsContextLost() && !mResetLayer && renderer) {
628 return true;
631 const auto& size = DrawingBufferSize();
633 if (!IsContextLost() && !renderer && mNotLost->mCanvasRenderer &&
634 mNotLost->mCanvasRenderer->GetSize() == gfx::IntSize(size.x, size.y) &&
635 aCanvasData->SetCanvasRenderer(mNotLost->mCanvasRenderer)) {
636 mNotLost->mCanvasRenderer->SetDirty();
637 mResetLayer = false;
638 return true;
641 renderer = aCanvasData->CreateCanvasRenderer();
642 if (!InitializeCanvasRenderer(aBuilder, renderer)) {
643 // Clear CanvasRenderer of WebRenderCanvasData
644 aCanvasData->ClearCanvasRenderer();
645 return false;
648 mNotLost->mCanvasRenderer = renderer;
650 MOZ_ASSERT(renderer);
651 mResetLayer = false;
653 return true;
656 bool ClientWebGLContext::InitializeCanvasRenderer(
657 nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) {
658 const FuncScope funcScope(*this, "<InitializeCanvasRenderer>");
659 if (IsContextLost()) return false;
661 layers::CanvasRendererData data;
662 data.mContext = this;
663 data.mOriginPos = gl::OriginPos::BottomLeft;
665 const auto& options = *mInitialOptions;
666 const auto& size = DrawingBufferSize();
668 if (IsContextLost()) return false;
670 data.mIsOpaque = !options.alpha;
671 data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
672 data.mSize = {size.x, size.y};
674 if (aBuilder->IsPaintingToWindow() && mCanvasElement) {
675 data.mDoPaintCallbacks = true;
678 aRenderer->Initialize(data);
679 aRenderer->SetDirty();
680 return true;
683 void ClientWebGLContext::UpdateCanvasParameters() {
684 if (!mOffscreenCanvas) {
685 return;
688 const auto& options = *mInitialOptions;
689 const auto& size = DrawingBufferSize();
691 mozilla::dom::OffscreenCanvasDisplayData data;
692 data.mOriginPos = gl::OriginPos::BottomLeft;
693 data.mIsOpaque = !options.alpha;
694 data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
695 data.mSize = {size.x, size.y};
696 data.mDoPaintCallbacks = false;
698 mOffscreenCanvas->UpdateDisplayData(data);
701 layers::LayersBackend ClientWebGLContext::GetCompositorBackendType() const {
702 if (mCanvasElement) {
703 return mCanvasElement->GetCompositorBackendType();
704 } else if (mOffscreenCanvas) {
705 return mOffscreenCanvas->GetCompositorBackendType();
708 return layers::LayersBackend::LAYERS_NONE;
711 mozilla::dom::Document* ClientWebGLContext::GetOwnerDoc() const {
712 MOZ_ASSERT(mCanvasElement);
713 if (!mCanvasElement) {
714 return nullptr;
716 return mCanvasElement->OwnerDoc();
719 void ClientWebGLContext::Commit() {
720 if (mOffscreenCanvas) {
721 mOffscreenCanvas->CommitFrameToCompositor();
725 void ClientWebGLContext::GetCanvas(
726 dom::Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& retval) {
727 if (mCanvasElement) {
728 MOZ_RELEASE_ASSERT(!mOffscreenCanvas, "GFX: Canvas is offscreen.");
730 if (mCanvasElement->IsInNativeAnonymousSubtree()) {
731 retval.SetNull();
732 } else {
733 retval.SetValue().SetAsHTMLCanvasElement() = mCanvasElement;
735 } else if (mOffscreenCanvas) {
736 retval.SetValue().SetAsOffscreenCanvas() = mOffscreenCanvas;
737 } else {
738 retval.SetNull();
742 void ClientWebGLContext::SetDrawingBufferColorSpace(
743 const dom::PredefinedColorSpace val) {
744 mDrawingBufferColorSpace = val;
745 Run<RPROC(SetDrawingBufferColorSpace)>(*mDrawingBufferColorSpace);
748 void ClientWebGLContext::SetUnpackColorSpace(
749 const dom::PredefinedColorSpace val) {
750 mUnpackColorSpace = val;
751 Run<RPROC(SetUnpackColorSpace)>(*mUnpackColorSpace);
754 void ClientWebGLContext::GetContextAttributes(
755 dom::Nullable<dom::WebGLContextAttributes>& retval) {
756 retval.SetNull();
757 const FuncScope funcScope(*this, "getContextAttributes");
758 if (IsContextLost()) return;
760 dom::WebGLContextAttributes& result = retval.SetValue();
762 const auto& options = mNotLost->info.options;
764 result.mAlpha.Construct(options.alpha);
765 result.mDepth = options.depth;
766 result.mStencil = options.stencil;
767 result.mAntialias.Construct(options.antialias);
768 result.mPremultipliedAlpha = options.premultipliedAlpha;
769 result.mPreserveDrawingBuffer = options.preserveDrawingBuffer;
770 result.mFailIfMajorPerformanceCaveat = options.failIfMajorPerformanceCaveat;
771 result.mPowerPreference = options.powerPreference;
772 result.mForceSoftwareRendering = options.forceSoftwareRendering;
775 // -----------------------
777 NS_IMETHODIMP
778 ClientWebGLContext::SetDimensions(const int32_t signedWidth,
779 const int32_t signedHeight) {
780 const FuncScope funcScope(*this, "<SetDimensions>");
781 MOZ_ASSERT(mInitialOptions);
783 if (mLossStatus != webgl::LossStatus::Ready) {
784 // Attempted resize of a lost context.
785 return NS_OK;
788 uvec2 size = {static_cast<uint32_t>(signedWidth),
789 static_cast<uint32_t>(signedHeight)};
790 if (!size.x) {
791 size.x = 1;
793 if (!size.y) {
794 size.y = 1;
796 const auto prevRequestedSize = mRequestedSize;
797 mRequestedSize = size;
799 mResetLayer = true; // Always treat this as resize.
801 if (mNotLost) {
802 auto& state = State();
804 auto curSize = prevRequestedSize;
805 if (state.mDrawingBufferSize) {
806 curSize = *state.mDrawingBufferSize;
808 if (size == curSize) return NS_OK; // MUST skip no-op resize
810 state.mDrawingBufferSize = Nothing();
811 Run<RPROC(Resize)>(size);
813 UpdateCanvasParameters();
814 MarkCanvasDirty();
815 return NS_OK;
818 // -
819 // Context (re-)creation
821 if (!CreateHostContext(size)) {
822 return NS_ERROR_FAILURE;
824 return NS_OK;
827 void ClientWebGLContext::ResetBitmap() {
828 const auto size = DrawingBufferSize();
829 Run<RPROC(Resize)>(size); // No-change resize still clears/resets everything.
832 static bool IsWebglOutOfProcessEnabled() {
833 if (StaticPrefs::webgl_out_of_process_force()) {
834 return true;
836 if (!gfx::gfxVars::AllowWebglOop()) {
837 return false;
839 if (!NS_IsMainThread()) {
840 return StaticPrefs::webgl_out_of_process_worker();
842 return StaticPrefs::webgl_out_of_process();
845 bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
846 const auto pNotLost = std::make_shared<webgl::NotLostData>(*this);
847 auto& notLost = *pNotLost;
849 auto res = [&]() -> Result<Ok, std::string> {
850 auto options = *mInitialOptions;
851 if (StaticPrefs::webgl_disable_fail_if_major_performance_caveat()) {
852 options.failIfMajorPerformanceCaveat = false;
855 if (options.failIfMajorPerformanceCaveat) {
856 const auto backend = GetCompositorBackendType();
857 bool isCompositorSlow = false;
858 isCompositorSlow |= (backend == layers::LayersBackend::LAYERS_WR &&
859 gfx::gfxVars::UseSoftwareWebRender());
861 if (isCompositorSlow) {
862 return Err(
863 "failIfMajorPerformanceCaveat: Compositor is not"
864 " hardware-accelerated.");
868 const bool resistFingerprinting =
869 ShouldResistFingerprinting(RFPTarget::WebGLRenderCapability);
870 const auto principalKey = GetPrincipalHashValue();
871 const auto initDesc = webgl::InitContextDesc{
872 .isWebgl2 = mIsWebGL2,
873 .resistFingerprinting = resistFingerprinting,
874 .principalKey = principalKey,
875 .size = requestedSize,
876 .options = options,
879 // -
881 auto useOop = IsWebglOutOfProcessEnabled();
882 if (XRE_IsParentProcess()) {
883 useOop = false;
886 if (!useOop) {
887 notLost.inProcess =
888 HostWebGLContext::Create({this, nullptr}, initDesc, &notLost.info);
889 return Ok();
892 // -
894 ScopedGfxFeatureReporter reporter("IpcWebGL");
896 auto* const cm = gfx::CanvasManagerChild::Get();
897 if (NS_WARN_IF(!cm)) {
898 return Err("!CanvasManagerChild::Get()");
901 RefPtr<dom::WebGLChild> outOfProcess = new dom::WebGLChild(*this);
902 outOfProcess =
903 static_cast<dom::WebGLChild*>(cm->SendPWebGLConstructor(outOfProcess));
904 if (!outOfProcess) {
905 return Err("SendPWebGLConstructor failed");
908 // Clear RemoteTextureOwnerId. HostWebGLContext is going to be replaced in
909 // WebGLParent.
910 if (mRemoteTextureOwnerId.isSome()) {
911 mRemoteTextureOwnerId = Nothing();
912 mFwdTransactionTracker = nullptr;
915 if (!outOfProcess->SendInitialize(initDesc, &notLost.info)) {
916 return Err("WebGL actor Initialize failed");
919 notLost.outOfProcess = outOfProcess;
920 reporter.SetSuccessful();
921 return Ok();
922 }();
923 if (!res.isOk()) {
924 auto str = res.unwrapErr();
925 if (StartsWith(str, "failIfMajorPerformanceCaveat")) {
926 str +=
927 " (about:config override available:"
928 " webgl.disable-fail-if-major-performance-caveat)";
930 notLost.info.error = str;
932 if (!notLost.info.error->empty()) {
933 ThrowEvent_WebGLContextCreationError(notLost.info.error);
934 return false;
936 mNotLost = pNotLost;
937 UpdateCanvasParameters();
938 MarkCanvasDirty();
940 // Init state
941 const auto& limits = Limits();
942 auto& state = State();
943 state.mIsEnabledMap = webgl::MakeIsEnabledMap(mIsWebGL2);
945 state.mDefaultTfo = new WebGLTransformFeedbackJS(*this);
946 state.mDefaultVao = new WebGLVertexArrayJS(this);
948 state.mBoundTfo = state.mDefaultTfo;
949 state.mBoundVao = state.mDefaultVao;
951 (void)state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER];
953 state.mTexUnits.resize(limits.maxTexUnits);
954 state.mBoundUbos.resize(limits.maxUniformBufferBindings);
957 webgl::TypedQuad initVal;
958 const float fData[4] = {0, 0, 0, 1};
959 memcpy(initVal.data.data(), fData, initVal.data.size());
960 state.mGenericVertexAttribs.resize(limits.maxVertexAttribs, initVal);
963 const auto& size = DrawingBufferSize();
964 state.mViewport = {0, 0, static_cast<int32_t>(size.x),
965 static_cast<int32_t>(size.y)};
966 state.mScissor = state.mViewport;
968 if (mIsWebGL2) {
969 // Insert keys to enable slots:
970 (void)state.mBoundBufferByTarget[LOCAL_GL_COPY_READ_BUFFER];
971 (void)state.mBoundBufferByTarget[LOCAL_GL_COPY_WRITE_BUFFER];
972 (void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_PACK_BUFFER];
973 (void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_UNPACK_BUFFER];
974 (void)state.mBoundBufferByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER];
975 (void)state.mBoundBufferByTarget[LOCAL_GL_UNIFORM_BUFFER];
977 (void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED];
978 //(void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE];
979 //// Same slot as ANY_SAMPLES_PASSED.
980 (void)state
981 .mCurrentQueryByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN];
984 return true;
987 std::unordered_map<GLenum, bool> webgl::MakeIsEnabledMap(const bool webgl2) {
988 auto ret = std::unordered_map<GLenum, bool>{};
990 ret[LOCAL_GL_BLEND] = false;
991 ret[LOCAL_GL_CULL_FACE] = false;
992 ret[LOCAL_GL_DEPTH_TEST] = false;
993 ret[LOCAL_GL_DITHER] = true;
994 ret[LOCAL_GL_POLYGON_OFFSET_FILL] = false;
995 ret[LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE] = false;
996 ret[LOCAL_GL_SAMPLE_COVERAGE] = false;
997 ret[LOCAL_GL_SCISSOR_TEST] = false;
998 ret[LOCAL_GL_STENCIL_TEST] = false;
1000 if (webgl2) {
1001 ret[LOCAL_GL_RASTERIZER_DISCARD] = false;
1004 return ret;
1007 // -------
1009 uvec2 ClientWebGLContext::DrawingBufferSize() {
1010 if (IsContextLost()) return {};
1011 const auto notLost =
1012 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
1013 auto& state = State();
1014 auto& size = state.mDrawingBufferSize;
1016 if (!size) {
1017 const auto& inProcess = mNotLost->inProcess;
1018 if (inProcess) {
1019 size = Some(inProcess->DrawingBufferSize());
1020 } else {
1021 const auto& child = mNotLost->outOfProcess;
1022 child->FlushPendingCmds();
1023 uvec2 actual = {};
1024 if (!child->SendDrawingBufferSize(&actual)) return {};
1025 size = Some(actual);
1029 return *size;
1032 void ClientWebGLContext::OnMemoryPressure() {
1033 if (IsContextLost()) return;
1035 const auto& inProcess = mNotLost->inProcess;
1036 if (inProcess) {
1037 return inProcess->OnMemoryPressure();
1039 const auto& child = mNotLost->outOfProcess;
1040 (void)child->SendOnMemoryPressure();
1043 NS_IMETHODIMP
1044 ClientWebGLContext::SetContextOptions(JSContext* cx,
1045 JS::Handle<JS::Value> options,
1046 ErrorResult& aRvForDictionaryInit) {
1047 if (mInitialOptions && options.isNullOrUndefined()) return NS_OK;
1049 dom::WebGLContextAttributes attributes;
1050 if (!attributes.Init(cx, options)) {
1051 aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED);
1052 return NS_ERROR_UNEXPECTED;
1055 WebGLContextOptions newOpts;
1057 newOpts.stencil = attributes.mStencil;
1058 newOpts.depth = attributes.mDepth;
1059 newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha;
1060 newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer;
1061 newOpts.failIfMajorPerformanceCaveat =
1062 attributes.mFailIfMajorPerformanceCaveat;
1063 newOpts.xrCompatible = attributes.mXrCompatible;
1064 newOpts.powerPreference = attributes.mPowerPreference;
1065 newOpts.forceSoftwareRendering = attributes.mForceSoftwareRendering;
1066 newOpts.enableDebugRendererInfo =
1067 StaticPrefs::webgl_enable_debug_renderer_info();
1068 MOZ_ASSERT(mCanvasElement || mOffscreenCanvas);
1069 newOpts.shouldResistFingerprinting =
1070 ShouldResistFingerprinting(RFPTarget::WebGLRenderCapability);
1072 if (attributes.mAlpha.WasPassed()) {
1073 newOpts.alpha = attributes.mAlpha.Value();
1075 if (attributes.mAntialias.WasPassed()) {
1076 newOpts.antialias = attributes.mAntialias.Value();
1079 // Don't do antialiasing if we've disabled MSAA.
1080 if (!StaticPrefs::webgl_msaa_samples()) {
1081 newOpts.antialias = false;
1084 // -
1086 if (mInitialOptions && *mInitialOptions != newOpts) {
1087 // Err if the options asked for aren't the same as what they were
1088 // originally.
1089 return NS_ERROR_FAILURE;
1092 mXRCompatible = attributes.mXrCompatible;
1094 mInitialOptions.emplace(newOpts);
1095 return NS_OK;
1098 void ClientWebGLContext::DidRefresh() { Run<RPROC(DidRefresh)>(); }
1100 already_AddRefed<gfx::SourceSurface> ClientWebGLContext::GetSurfaceSnapshot(
1101 gfxAlphaType* const out_alphaType) {
1102 const FuncScope funcScope(*this, "<GetSurfaceSnapshot>");
1103 if (IsContextLost()) return nullptr;
1104 const auto notLost =
1105 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
1107 auto ret = BackBufferSnapshot();
1108 if (!ret) return nullptr;
1110 // -
1112 const auto& options = mNotLost->info.options;
1114 auto srcAlphaType = gfxAlphaType::Opaque;
1115 if (options.alpha) {
1116 if (options.premultipliedAlpha) {
1117 srcAlphaType = gfxAlphaType::Premult;
1118 } else {
1119 srcAlphaType = gfxAlphaType::NonPremult;
1123 if (out_alphaType) {
1124 *out_alphaType = srcAlphaType;
1125 } else {
1126 // Expects Opaque or Premult
1127 if (srcAlphaType == gfxAlphaType::NonPremult) {
1128 const gfx::DataSourceSurface::ScopedMap map(
1129 ret, gfx::DataSourceSurface::READ_WRITE);
1130 MOZ_RELEASE_ASSERT(map.IsMapped(), "Failed to map snapshot surface!");
1132 const auto& size = ret->GetSize();
1133 const auto format = ret->GetFormat();
1134 bool rv =
1135 gfx::PremultiplyData(map.GetData(), map.GetStride(), format,
1136 map.GetData(), map.GetStride(), format, size);
1137 MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
1141 return ret.forget();
1144 RefPtr<gfx::SourceSurface> ClientWebGLContext::GetFrontBufferSnapshot(
1145 const bool requireAlphaPremult) {
1146 const FuncScope funcScope(*this, "<GetSurfaceSnapshot>");
1147 if (IsContextLost()) return nullptr;
1148 const auto notLost =
1149 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
1151 const auto& options = mNotLost->info.options;
1153 const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
1154 : gfx::SurfaceFormat::B8G8R8X8;
1156 const auto fnNewSurf = [&](const uvec2 size) {
1157 const auto stride = size.x * 4;
1158 return RefPtr<gfx::DataSourceSurface>(
1159 gfx::Factory::CreateDataSourceSurfaceWithStride({size.x, size.y},
1160 surfFormat, stride,
1161 /*zero=*/true));
1164 const auto& inProcess = mNotLost->inProcess;
1165 if (inProcess) {
1166 const auto maybeSize = inProcess->FrontBufferSnapshotInto({});
1167 if (!maybeSize) return nullptr;
1168 const auto& surfSize = *maybeSize;
1169 const auto stride = surfSize.x * 4;
1170 const auto byteSize = stride * surfSize.y;
1171 const auto surf = fnNewSurf(surfSize);
1172 if (!surf) return nullptr;
1174 const gfx::DataSourceSurface::ScopedMap map(
1175 surf, gfx::DataSourceSurface::READ_WRITE);
1176 if (!map.IsMapped()) {
1177 MOZ_ASSERT(false);
1178 return nullptr;
1180 MOZ_RELEASE_ASSERT(map.GetStride() == static_cast<int64_t>(stride));
1181 auto range = Range<uint8_t>{map.GetData(), byteSize};
1182 if (!inProcess->FrontBufferSnapshotInto(Some(range))) {
1183 gfxCriticalNote << "ClientWebGLContext::GetFrontBufferSnapshot: "
1184 "FrontBufferSnapshotInto(some) failed after "
1185 "FrontBufferSnapshotInto(none)";
1186 return nullptr;
1188 if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
1189 bool rv = gfx::PremultiplyData(
1190 map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
1191 map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1192 surf->GetSize());
1193 MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
1194 } else {
1195 bool rv = gfx::SwizzleData(
1196 map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
1197 map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1198 surf->GetSize());
1199 MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
1202 return surf;
1204 const auto& child = mNotLost->outOfProcess;
1205 child->FlushPendingCmds();
1206 webgl::FrontBufferSnapshotIpc res;
1207 if (!child->SendGetFrontBufferSnapshot(&res)) {
1208 res = {};
1210 if (!res.shmem) return nullptr;
1212 const auto& surfSize = res.surfSize;
1213 const webgl::RaiiShmem shmem{child, res.shmem.ref()};
1214 if (!shmem) return nullptr;
1215 const auto& shmemBytes = shmem.ByteRange();
1216 if (!surfSize.x) return nullptr; // Zero means failure.
1218 const auto stride = surfSize.x * 4;
1219 const auto byteSize = stride * surfSize.y;
1221 const auto surf = fnNewSurf(surfSize);
1222 if (!surf) return nullptr;
1225 const gfx::DataSourceSurface::ScopedMap map(
1226 surf, gfx::DataSourceSurface::READ_WRITE);
1227 if (!map.IsMapped()) {
1228 MOZ_ASSERT(false);
1229 return nullptr;
1231 MOZ_RELEASE_ASSERT(shmemBytes.length() == byteSize);
1232 if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
1233 bool rv = gfx::PremultiplyData(
1234 shmemBytes.begin().get(), stride, gfx::SurfaceFormat::R8G8B8A8,
1235 map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1236 surf->GetSize());
1237 MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
1238 } else {
1239 bool rv = gfx::SwizzleData(shmemBytes.begin().get(), stride,
1240 gfx::SurfaceFormat::R8G8B8A8, map.GetData(),
1241 map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1242 surf->GetSize());
1243 MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
1246 return surf;
1249 RefPtr<gfx::DataSourceSurface> ClientWebGLContext::BackBufferSnapshot() {
1250 if (IsContextLost()) return nullptr;
1251 const auto notLost =
1252 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
1254 const auto& options = mNotLost->info.options;
1255 const auto& state = State();
1257 const auto drawFbWas = state.mBoundDrawFb;
1258 const auto readFbWas = state.mBoundReadFb;
1259 const auto pboWas =
1260 Find(state.mBoundBufferByTarget, LOCAL_GL_PIXEL_PACK_BUFFER);
1262 const auto size = DrawingBufferSize();
1264 // -
1266 BindFramebuffer(LOCAL_GL_FRAMEBUFFER, nullptr);
1267 if (pboWas) {
1268 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
1271 auto reset = MakeScopeExit([&] {
1272 if (drawFbWas == readFbWas) {
1273 BindFramebuffer(LOCAL_GL_FRAMEBUFFER, drawFbWas);
1274 } else {
1275 BindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, drawFbWas);
1276 BindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, readFbWas);
1278 if (pboWas) {
1279 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
1283 const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
1284 : gfx::SurfaceFormat::B8G8R8X8;
1285 const auto stride = size.x * 4;
1286 RefPtr<gfx::DataSourceSurface> surf =
1287 gfx::Factory::CreateDataSourceSurfaceWithStride(
1288 {size.x, size.y}, surfFormat, stride, /*zero=*/true);
1289 if (NS_WARN_IF(!surf)) {
1290 // Was this an OOM or alloc-limit? (500MB is our default resource size
1291 // limit)
1292 surf = gfx::Factory::CreateDataSourceSurfaceWithStride({1, 1}, surfFormat,
1293 4, /*zero=*/true);
1294 if (!surf) {
1295 // Still failed for a 1x1 size.
1296 gfxCriticalError() << "CreateDataSourceSurfaceWithStride(surfFormat="
1297 << surfFormat << ") failed.";
1299 return nullptr;
1303 const gfx::DataSourceSurface::ScopedMap map(
1304 surf, gfx::DataSourceSurface::READ_WRITE);
1305 if (!map.IsMapped()) {
1306 MOZ_ASSERT(false);
1307 return nullptr;
1309 MOZ_ASSERT(static_cast<uint32_t>(map.GetStride()) == stride);
1311 const auto desc = webgl::ReadPixelsDesc{{0, 0}, size};
1312 const auto pixels = Span<uint8_t>(map.GetData(), stride * size.y);
1313 if (!DoReadPixels(desc, pixels)) return nullptr;
1315 // RGBA->BGRA and flip-y.
1316 MOZ_RELEASE_ASSERT(gfx::SwizzleYFlipData(
1317 pixels.data(), stride, gfx::SurfaceFormat::R8G8B8A8, pixels.data(),
1318 stride, gfx::SurfaceFormat::B8G8R8A8, {size.x, size.y}));
1321 return surf;
1324 UniquePtr<uint8_t[]> ClientWebGLContext::GetImageBuffer(
1325 int32_t* out_format, gfx::IntSize* out_imageSize) {
1326 *out_format = 0;
1327 *out_imageSize = {};
1329 // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
1330 gfxAlphaType any;
1331 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
1332 if (!snapshot) return nullptr;
1334 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
1336 const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;
1337 *out_imageSize = dataSurface->GetSize();
1339 if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
1340 return gfxUtils::GetImageBufferWithRandomNoise(
1341 dataSurface, premultAlpha, GetCookieJarSettings(), out_format);
1344 return gfxUtils::GetImageBuffer(dataSurface, premultAlpha, out_format);
1347 NS_IMETHODIMP
1348 ClientWebGLContext::GetInputStream(const char* mimeType,
1349 const nsAString& encoderOptions,
1350 nsIInputStream** out_stream) {
1351 // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
1352 gfxAlphaType any;
1353 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
1354 if (!snapshot) return NS_ERROR_FAILURE;
1356 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
1357 const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;
1359 if (ShouldResistFingerprinting(RFPTarget::CanvasRandomization)) {
1360 return gfxUtils::GetInputStreamWithRandomNoise(
1361 dataSurface, premultAlpha, mimeType, encoderOptions,
1362 GetCookieJarSettings(), out_stream);
1365 return gfxUtils::GetInputStream(dataSurface, premultAlpha, mimeType,
1366 encoderOptions, out_stream);
1369 // ------------------------- Client WebGL Objects -------------------------
1370 // ------------------------- Create/Destroy/Is -------------------------
1372 template <typename T>
1373 static already_AddRefed<T> AsAddRefed(T* ptr) {
1374 RefPtr<T> rp = ptr;
1375 return rp.forget();
1378 template <typename T>
1379 static RefPtr<T> AsRefPtr(T* ptr) {
1380 return {ptr};
1383 already_AddRefed<WebGLBufferJS> ClientWebGLContext::CreateBuffer() const {
1384 const FuncScope funcScope(*this, "createBuffer");
1386 auto ret = AsRefPtr(new WebGLBufferJS(*this));
1387 Run<RPROC(CreateBuffer)>(ret->mId);
1388 return ret.forget();
1391 already_AddRefed<WebGLFramebufferJS> ClientWebGLContext::CreateFramebuffer()
1392 const {
1393 const FuncScope funcScope(*this, "createFramebuffer");
1395 auto ret = AsRefPtr(new WebGLFramebufferJS(*this));
1396 Run<RPROC(CreateFramebuffer)>(ret->mId);
1397 return ret.forget();
1400 already_AddRefed<WebGLFramebufferJS>
1401 ClientWebGLContext::CreateOpaqueFramebuffer(
1402 const webgl::OpaqueFramebufferOptions& options) const {
1403 const FuncScope funcScope(*this, "createOpaqueFramebuffer");
1405 auto ret = AsRefPtr(new WebGLFramebufferJS(*this, true));
1407 if (mNotLost) {
1408 const auto& inProcess = mNotLost->inProcess;
1409 if (inProcess) {
1410 if (!inProcess->CreateOpaqueFramebuffer(ret->mId, options)) {
1411 ret = nullptr;
1413 return ret.forget();
1415 const auto& child = mNotLost->outOfProcess;
1416 child->FlushPendingCmds();
1417 bool ok = false;
1418 if (!child->SendCreateOpaqueFramebuffer(ret->mId, options, &ok))
1419 return nullptr;
1420 if (!ok) return nullptr;
1423 return ret.forget();
1426 already_AddRefed<WebGLProgramJS> ClientWebGLContext::CreateProgram() const {
1427 const FuncScope funcScope(*this, "createProgram");
1429 auto ret = AsRefPtr(new WebGLProgramJS(*this));
1430 Run<RPROC(CreateProgram)>(ret->mId);
1431 return ret.forget();
1434 already_AddRefed<WebGLQueryJS> ClientWebGLContext::CreateQuery() const {
1435 const FuncScope funcScope(*this, "createQuery");
1437 auto ret = AsRefPtr(new WebGLQueryJS(this));
1438 Run<RPROC(CreateQuery)>(ret->mId);
1439 return ret.forget();
1442 already_AddRefed<WebGLRenderbufferJS> ClientWebGLContext::CreateRenderbuffer()
1443 const {
1444 const FuncScope funcScope(*this, "createRenderbuffer");
1446 auto ret = AsRefPtr(new WebGLRenderbufferJS(*this));
1447 Run<RPROC(CreateRenderbuffer)>(ret->mId);
1448 return ret.forget();
1451 already_AddRefed<WebGLSamplerJS> ClientWebGLContext::CreateSampler() const {
1452 const FuncScope funcScope(*this, "createSampler");
1454 auto ret = AsRefPtr(new WebGLSamplerJS(*this));
1455 Run<RPROC(CreateSampler)>(ret->mId);
1456 return ret.forget();
1459 already_AddRefed<WebGLShaderJS> ClientWebGLContext::CreateShader(
1460 const GLenum type) const {
1461 const FuncScope funcScope(*this, "createShader");
1463 switch (type) {
1464 case LOCAL_GL_VERTEX_SHADER:
1465 case LOCAL_GL_FRAGMENT_SHADER:
1466 break;
1467 default:
1468 EnqueueError_ArgEnum("type", type);
1469 return nullptr;
1472 auto ret = AsRefPtr(new WebGLShaderJS(*this, type));
1473 Run<RPROC(CreateShader)>(ret->mId, ret->mType);
1474 return ret.forget();
1477 already_AddRefed<WebGLSyncJS> ClientWebGLContext::FenceSync(
1478 const GLenum condition, const GLbitfield flags) const {
1479 const FuncScope funcScope(*this, "fenceSync");
1481 if (condition != LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE) {
1482 EnqueueError_ArgEnum("condition", condition);
1483 return nullptr;
1486 if (flags) {
1487 EnqueueError(LOCAL_GL_INVALID_VALUE, "`flags` must be 0.");
1488 return nullptr;
1491 auto ret = AsRefPtr(new WebGLSyncJS(*this));
1492 Run<RPROC(CreateSync)>(ret->mId);
1494 auto& availRunnable = EnsureAvailabilityRunnable();
1495 availRunnable.mSyncs.push_back(ret.get());
1496 ret->mCanBeAvailable = false;
1498 AutoEnqueueFlush();
1500 return ret.forget();
1503 already_AddRefed<WebGLTextureJS> ClientWebGLContext::CreateTexture() const {
1504 const FuncScope funcScope(*this, "createTexture");
1506 auto ret = AsRefPtr(new WebGLTextureJS(*this));
1507 Run<RPROC(CreateTexture)>(ret->mId);
1508 return ret.forget();
1511 already_AddRefed<WebGLTransformFeedbackJS>
1512 ClientWebGLContext::CreateTransformFeedback() const {
1513 const FuncScope funcScope(*this, "createTransformFeedback");
1515 auto ret = AsRefPtr(new WebGLTransformFeedbackJS(*this));
1516 Run<RPROC(CreateTransformFeedback)>(ret->mId);
1517 return ret.forget();
1520 already_AddRefed<WebGLVertexArrayJS> ClientWebGLContext::CreateVertexArray()
1521 const {
1522 const FuncScope funcScope(*this, "createVertexArray");
1524 auto ret = AsRefPtr(new WebGLVertexArrayJS(this));
1525 Run<RPROC(CreateVertexArray)>(ret->mId);
1526 return ret.forget();
1529 // -
1531 static bool ValidateOrSkipForDelete(const ClientWebGLContext& context,
1532 const webgl::ObjectJS* const obj) {
1533 if (!obj) return false;
1534 if (!obj->ValidateForContext(context, "obj")) return false;
1535 if (obj->IsDeleted()) return false;
1536 return true;
1539 void ClientWebGLContext::DeleteBuffer(WebGLBufferJS* const obj) {
1540 const FuncScope funcScope(*this, "deleteBuffer");
1541 if (IsContextLost()) return;
1542 if (!ValidateOrSkipForDelete(*this, obj)) return;
1543 auto& state = State();
1545 // Unbind from all bind points and bound containers
1547 // UBOs
1548 for (const auto i : IntegerRange(state.mBoundUbos.size())) {
1549 if (state.mBoundUbos[i] == obj) {
1550 BindBufferBase(LOCAL_GL_UNIFORM_BUFFER, i, nullptr);
1554 // TFO only if not active
1555 if (!state.mBoundTfo->mActiveOrPaused) {
1556 const auto& buffers = state.mBoundTfo->mAttribBuffers;
1557 for (const auto i : IntegerRange(buffers.size())) {
1558 if (buffers[i] == obj) {
1559 BindBufferBase(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, i, nullptr);
1564 // Generic/global bind points
1565 for (const auto& pair : state.mBoundBufferByTarget) {
1566 if (pair.second == obj) {
1567 BindBuffer(pair.first, nullptr);
1571 // VAO attachments
1572 if (state.mBoundVao->mIndexBuffer == obj) {
1573 BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, nullptr);
1576 const auto& vaoBuffers = state.mBoundVao->mAttribBuffers;
1577 Maybe<WebGLBufferJS*> toRestore;
1578 for (const auto i : IntegerRange(vaoBuffers.size())) {
1579 if (vaoBuffers[i] == obj) {
1580 if (!toRestore) {
1581 toRestore =
1582 Some(state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER].get());
1583 if (*toRestore) {
1584 BindBuffer(LOCAL_GL_ARRAY_BUFFER, nullptr);
1587 VertexAttribPointer(i, 4, LOCAL_GL_FLOAT, false, 0, 0);
1590 if (toRestore && *toRestore) {
1591 BindBuffer(LOCAL_GL_ARRAY_BUFFER, *toRestore);
1594 // -
1596 obj->mDeleteRequested = true;
1597 Run<RPROC(DeleteBuffer)>(obj->mId);
1600 void ClientWebGLContext::DeleteFramebuffer(WebGLFramebufferJS* const obj,
1601 bool canDeleteOpaque) {
1602 const FuncScope funcScope(*this, "deleteFramebuffer");
1603 if (IsContextLost()) return;
1604 if (!ValidateOrSkipForDelete(*this, obj)) return;
1605 if (!canDeleteOpaque && obj->mOpaque) {
1606 EnqueueError(
1607 LOCAL_GL_INVALID_OPERATION,
1608 "An opaque framebuffer's attachments cannot be inspected or changed.");
1609 return;
1611 const auto& state = State();
1613 // Unbind
1614 const auto fnDetach = [&](const GLenum target,
1615 const WebGLFramebufferJS* const fb) {
1616 if (obj == fb) {
1617 BindFramebuffer(target, nullptr);
1620 if (state.mBoundDrawFb == state.mBoundReadFb) {
1621 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1622 } else {
1623 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1624 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1627 obj->mDeleteRequested = true;
1628 Run<RPROC(DeleteFramebuffer)>(obj->mId);
1631 void ClientWebGLContext::DeleteProgram(WebGLProgramJS* const obj) const {
1632 const FuncScope funcScope(*this, "deleteProgram");
1633 if (IsContextLost()) return;
1634 if (!ValidateOrSkipForDelete(*this, obj)) return;
1636 // Don't unbind
1638 obj->mKeepAlive = nullptr;
1641 webgl::ProgramKeepAlive::~ProgramKeepAlive() {
1642 if (!mParent) return;
1643 const auto& context = mParent->Context();
1644 if (!context) return;
1645 context->DoDeleteProgram(*mParent);
1648 void ClientWebGLContext::DoDeleteProgram(WebGLProgramJS& obj) const {
1649 obj.mNextLink_Shaders = {};
1650 Run<RPROC(DeleteProgram)>(obj.mId);
1653 static GLenum QuerySlotTarget(const GLenum specificTarget);
1655 void ClientWebGLContext::DeleteQuery(WebGLQueryJS* const obj) {
1656 const FuncScope funcScope(*this, "deleteQuery");
1657 if (IsContextLost()) return;
1658 if (!ValidateOrSkipForDelete(*this, obj)) return;
1659 const auto& state = State();
1661 // Unbind if current
1663 if (obj->mTarget) {
1664 // Despite mTarget being set, we may not have called BeginQuery on this
1665 // object. QueryCounter may also set mTarget.
1666 const auto slotTarget = QuerySlotTarget(obj->mTarget);
1667 const auto curForTarget =
1668 MaybeFind(state.mCurrentQueryByTarget, slotTarget);
1670 if (curForTarget && *curForTarget == obj) {
1671 EndQuery(obj->mTarget);
1675 obj->mDeleteRequested = true;
1676 Run<RPROC(DeleteQuery)>(obj->mId);
1679 void ClientWebGLContext::DeleteRenderbuffer(WebGLRenderbufferJS* const obj) {
1680 const FuncScope funcScope(*this, "deleteRenderbuffer");
1681 if (IsContextLost()) return;
1682 if (!ValidateOrSkipForDelete(*this, obj)) return;
1683 const auto& state = State();
1685 // Unbind
1686 if (state.mBoundRb == obj) {
1687 BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nullptr);
1690 // Unbind from bound FBs
1691 const auto fnDetach = [&](const GLenum target,
1692 const WebGLFramebufferJS* const fb) {
1693 if (!fb) return;
1694 for (const auto& pair : fb->mAttachments) {
1695 if (pair.second.rb == obj) {
1696 FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
1697 nullptr);
1701 if (state.mBoundDrawFb == state.mBoundReadFb) {
1702 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1703 } else {
1704 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1705 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1708 obj->mDeleteRequested = true;
1709 Run<RPROC(DeleteRenderbuffer)>(obj->mId);
1712 void ClientWebGLContext::DeleteSampler(WebGLSamplerJS* const obj) {
1713 const FuncScope funcScope(*this, "deleteSampler");
1714 if (IsContextLost()) return;
1715 if (!ValidateOrSkipForDelete(*this, obj)) return;
1716 const auto& state = State();
1718 // Unbind
1719 for (const auto i : IntegerRange(state.mTexUnits.size())) {
1720 if (state.mTexUnits[i].sampler == obj) {
1721 BindSampler(i, nullptr);
1725 obj->mDeleteRequested = true;
1726 Run<RPROC(DeleteSampler)>(obj->mId);
1729 void ClientWebGLContext::DeleteShader(WebGLShaderJS* const obj) const {
1730 const FuncScope funcScope(*this, "deleteShader");
1731 if (IsContextLost()) return;
1732 if (!ValidateOrSkipForDelete(*this, obj)) return;
1734 // Don't unbind
1736 obj->mKeepAlive = nullptr;
1739 webgl::ShaderKeepAlive::~ShaderKeepAlive() {
1740 if (!mParent) return;
1741 const auto& context = mParent->Context();
1742 if (!context) return;
1743 context->DoDeleteShader(*mParent);
1746 void ClientWebGLContext::DoDeleteShader(const WebGLShaderJS& obj) const {
1747 Run<RPROC(DeleteShader)>(obj.mId);
1750 void ClientWebGLContext::DeleteSync(WebGLSyncJS* const obj) const {
1751 const FuncScope funcScope(*this, "deleteSync");
1752 if (IsContextLost()) return;
1753 if (!ValidateOrSkipForDelete(*this, obj)) return;
1755 // Nothing to unbind
1757 obj->mDeleteRequested = true;
1758 Run<RPROC(DeleteSync)>(obj->mId);
1761 void ClientWebGLContext::DeleteTexture(WebGLTextureJS* const obj) {
1762 const FuncScope funcScope(*this, "deleteTexture");
1763 if (IsContextLost()) return;
1764 if (!ValidateOrSkipForDelete(*this, obj)) return;
1765 auto& state = State();
1767 // Unbind
1768 const auto& target = obj->mTarget;
1769 if (target) {
1770 // Unbind from tex units
1771 Maybe<uint32_t> restoreTexUnit;
1772 for (const auto i : IntegerRange(state.mTexUnits.size())) {
1773 if (state.mTexUnits[i].texByTarget[target] == obj) {
1774 if (!restoreTexUnit) {
1775 restoreTexUnit = Some(state.mActiveTexUnit);
1777 ActiveTexture(LOCAL_GL_TEXTURE0 + i);
1778 BindTexture(target, nullptr);
1781 if (restoreTexUnit) {
1782 ActiveTexture(LOCAL_GL_TEXTURE0 + *restoreTexUnit);
1785 // Unbind from bound FBs
1786 const auto fnDetach = [&](const GLenum target,
1787 const WebGLFramebufferJS* const fb) {
1788 if (!fb) return;
1789 for (const auto& pair : fb->mAttachments) {
1790 if (pair.second.tex == obj) {
1791 FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
1792 nullptr);
1796 if (state.mBoundDrawFb == state.mBoundReadFb) {
1797 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1798 } else {
1799 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1800 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1804 obj->mDeleteRequested = true;
1805 Run<RPROC(DeleteTexture)>(obj->mId);
1808 void ClientWebGLContext::DeleteTransformFeedback(
1809 WebGLTransformFeedbackJS* const obj) {
1810 const FuncScope funcScope(*this, "deleteTransformFeedback");
1811 if (IsContextLost()) return;
1812 if (!ValidateOrSkipForDelete(*this, obj)) return;
1813 const auto& state = State();
1815 if (obj->mActiveOrPaused) {
1816 EnqueueError(LOCAL_GL_INVALID_OPERATION,
1817 "Transform Feedback object still active or paused.");
1818 return;
1821 // Unbind
1822 if (state.mBoundTfo == obj) {
1823 BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
1826 obj->mDeleteRequested = true;
1827 Run<RPROC(DeleteTransformFeedback)>(obj->mId);
1830 void ClientWebGLContext::DeleteVertexArray(WebGLVertexArrayJS* const obj) {
1831 const FuncScope funcScope(*this, "deleteVertexArray");
1832 if (IsContextLost()) return;
1833 if (!ValidateOrSkipForDelete(*this, obj)) return;
1834 const auto& state = State();
1836 // Unbind
1837 if (state.mBoundVao == obj) {
1838 BindVertexArray(nullptr);
1841 obj->mDeleteRequested = true;
1842 Run<RPROC(DeleteVertexArray)>(obj->mId);
1845 // -
1847 bool ClientWebGLContext::IsBuffer(const WebGLBufferJS* const obj) const {
1848 const FuncScope funcScope(*this, "isBuffer");
1849 if (IsContextLost()) return false;
1851 return obj && obj->IsUsable(*this) &&
1852 obj->mKind != webgl::BufferKind::Undefined;
1855 bool ClientWebGLContext::IsFramebuffer(
1856 const WebGLFramebufferJS* const obj) const {
1857 const FuncScope funcScope(*this, "isFramebuffer");
1858 if (IsContextLost()) return false;
1860 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1863 bool ClientWebGLContext::IsProgram(const WebGLProgramJS* const obj) const {
1864 const FuncScope funcScope(*this, "isProgram");
1865 if (IsContextLost()) return false;
1867 return obj && obj->IsUsable(*this);
1870 bool ClientWebGLContext::IsQuery(const WebGLQueryJS* const obj) const {
1871 const FuncScope funcScope(*this, "isQuery");
1872 if (IsContextLost()) return false;
1874 return obj && obj->IsUsable(*this) && obj->mTarget;
1877 bool ClientWebGLContext::IsRenderbuffer(
1878 const WebGLRenderbufferJS* const obj) const {
1879 const FuncScope funcScope(*this, "isRenderbuffer");
1880 if (IsContextLost()) return false;
1882 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1885 bool ClientWebGLContext::IsSampler(const WebGLSamplerJS* const obj) const {
1886 const FuncScope funcScope(*this, "isSampler");
1887 if (IsContextLost()) return false;
1889 return obj && obj->IsUsable(*this);
1892 bool ClientWebGLContext::IsShader(const WebGLShaderJS* const obj) const {
1893 const FuncScope funcScope(*this, "isShader");
1894 if (IsContextLost()) return false;
1896 return obj && obj->IsUsable(*this);
1899 bool ClientWebGLContext::IsSync(const WebGLSyncJS* const obj) const {
1900 const FuncScope funcScope(*this, "isSync");
1901 if (IsContextLost()) return false;
1903 return obj && obj->IsUsable(*this);
1906 bool ClientWebGLContext::IsTexture(const WebGLTextureJS* const obj) const {
1907 const FuncScope funcScope(*this, "isTexture");
1908 if (IsContextLost()) return false;
1910 return obj && obj->IsUsable(*this) && obj->mTarget;
1913 bool ClientWebGLContext::IsTransformFeedback(
1914 const WebGLTransformFeedbackJS* const obj) const {
1915 const FuncScope funcScope(*this, "isTransformFeedback");
1916 if (IsContextLost()) return false;
1918 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1921 bool ClientWebGLContext::IsVertexArray(
1922 const WebGLVertexArrayJS* const obj) const {
1923 const FuncScope funcScope(*this, "isVertexArray");
1924 if (IsContextLost()) return false;
1926 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1929 // ------------------------- GL State -------------------------
1931 void ClientWebGLContext::SetEnabledI(const GLenum cap, const Maybe<GLuint> i,
1932 const bool val) const {
1933 const FuncScope funcScope(*this, "enable/disable");
1934 if (IsContextLost()) return;
1936 auto& map = mNotLost->state.mIsEnabledMap;
1937 auto slot = MaybeFind(map, cap);
1938 if (i && cap != LOCAL_GL_BLEND) {
1939 slot = nullptr;
1941 if (!slot) {
1942 EnqueueError_ArgEnum("cap", cap);
1943 return;
1946 Run<RPROC(SetEnabled)>(cap, i, val);
1948 if (!i || *i == 0) {
1949 *slot = val;
1953 bool ClientWebGLContext::IsEnabled(const GLenum cap) const {
1954 const FuncScope funcScope(*this, "isEnabled");
1955 if (IsContextLost()) return false;
1957 const auto& map = mNotLost->state.mIsEnabledMap;
1958 const auto slot = MaybeFind(map, cap);
1959 if (!slot) {
1960 EnqueueError_ArgEnum("cap", cap);
1961 return false;
1964 return *slot;
1967 template <typename T, typename S>
1968 static JS::Value Create(JSContext* cx, nsWrapperCache* creator, const S& src,
1969 ErrorResult& rv) {
1970 return JS::ObjectOrNullValue(T::Create(cx, creator, src, rv));
1973 void ClientWebGLContext::GetInternalformatParameter(
1974 JSContext* cx, GLenum target, GLenum internalformat, GLenum pname,
1975 JS::MutableHandle<JS::Value> retval, ErrorResult& rv) {
1976 const FuncScope funcScope(*this, "getInternalformatParameter");
1977 retval.set(JS::NullValue());
1978 const auto notLost =
1979 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
1980 if (IsContextLost()) return;
1982 const auto& inProcessContext = notLost->inProcess;
1983 Maybe<std::vector<int32_t>> maybe;
1984 if (inProcessContext) {
1985 maybe = inProcessContext->GetInternalformatParameter(target, internalformat,
1986 pname);
1987 } else {
1988 const auto& child = notLost->outOfProcess;
1989 child->FlushPendingCmds();
1990 if (!child->SendGetInternalformatParameter(target, internalformat, pname,
1991 &maybe)) {
1992 return;
1996 if (!maybe) {
1997 return;
2000 retval.set(Create<dom::Int32Array>(cx, this, *maybe, rv));
2003 static JS::Value StringValue(JSContext* cx, const std::string& str,
2004 ErrorResult& er) {
2005 JSString* jsStr = JS_NewStringCopyN(cx, str.data(), str.size());
2006 if (!jsStr) {
2007 er.Throw(NS_ERROR_OUT_OF_MEMORY);
2008 return JS::NullValue();
2011 return JS::StringValue(jsStr);
2014 template <typename T>
2015 bool ToJSValueOrNull(JSContext* const cx, const RefPtr<T>& ptr,
2016 JS::MutableHandle<JS::Value> retval) {
2017 if (!ptr) {
2018 retval.set(JS::NullValue());
2019 return true;
2021 return dom::ToJSValue(cx, ptr, retval);
2024 Maybe<double> ClientWebGLContext::GetNumber(const GLenum pname) {
2025 MOZ_ASSERT(!IsContextLost());
2027 const auto& inProcess = mNotLost->inProcess;
2028 if (inProcess) {
2029 return inProcess->GetNumber(pname);
2032 const auto& child = mNotLost->outOfProcess;
2033 child->FlushPendingCmds();
2035 Maybe<double> ret;
2036 if (!child->SendGetNumber(pname, &ret)) {
2037 ret.reset();
2039 return ret;
2042 Maybe<std::string> ClientWebGLContext::GetString(const GLenum pname) {
2043 MOZ_ASSERT(!IsContextLost());
2045 const auto& inProcess = mNotLost->inProcess;
2046 if (inProcess) {
2047 return inProcess->GetString(pname);
2050 const auto& child = mNotLost->outOfProcess;
2051 child->FlushPendingCmds();
2053 Maybe<std::string> ret;
2054 if (!child->SendGetString(pname, &ret)) {
2055 ret.reset();
2057 return ret;
2060 void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
2061 JS::MutableHandle<JS::Value> retval,
2062 ErrorResult& rv, const bool debug) {
2063 retval.set(JS::NullValue());
2064 const FuncScope funcScope(*this, "getParameter");
2065 if (IsContextLost()) return;
2066 const auto& limits = Limits();
2067 const auto& state = State();
2069 // -
2071 const auto fnSetRetval_Buffer = [&](const GLenum target) {
2072 const auto buffer = *MaybeFind(state.mBoundBufferByTarget, target);
2073 (void)ToJSValueOrNull(cx, buffer, retval);
2075 const auto fnSetRetval_Tex = [&](const GLenum texTarget) {
2076 const auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
2077 const auto tex = Find(texUnit.texByTarget, texTarget, nullptr);
2078 (void)ToJSValueOrNull(cx, tex, retval);
2081 switch (pname) {
2082 case LOCAL_GL_ARRAY_BUFFER_BINDING:
2083 fnSetRetval_Buffer(LOCAL_GL_ARRAY_BUFFER);
2084 return;
2086 case LOCAL_GL_CURRENT_PROGRAM:
2087 (void)ToJSValueOrNull(cx, state.mCurrentProgram, retval);
2088 return;
2090 case LOCAL_GL_ELEMENT_ARRAY_BUFFER_BINDING:
2091 (void)ToJSValueOrNull(cx, state.mBoundVao->mIndexBuffer, retval);
2092 return;
2094 case LOCAL_GL_FRAMEBUFFER_BINDING:
2095 (void)ToJSValueOrNull(cx, state.mBoundDrawFb, retval);
2096 return;
2098 case LOCAL_GL_RENDERBUFFER_BINDING:
2099 (void)ToJSValueOrNull(cx, state.mBoundRb, retval);
2100 return;
2102 case LOCAL_GL_TEXTURE_BINDING_2D:
2103 fnSetRetval_Tex(LOCAL_GL_TEXTURE_2D);
2104 return;
2106 case LOCAL_GL_TEXTURE_BINDING_CUBE_MAP:
2107 fnSetRetval_Tex(LOCAL_GL_TEXTURE_CUBE_MAP);
2108 return;
2110 case LOCAL_GL_VERTEX_ARRAY_BINDING: {
2111 if (!mIsWebGL2 &&
2112 !IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object))
2113 break;
2115 auto ret = state.mBoundVao;
2116 if (ret == state.mDefaultVao) {
2117 ret = nullptr;
2119 (void)ToJSValueOrNull(cx, ret, retval);
2120 return;
2123 case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
2124 retval.set(JS::NumberValue(limits.maxTexUnits));
2125 return;
2126 case LOCAL_GL_MAX_TEXTURE_SIZE:
2127 retval.set(JS::NumberValue(limits.maxTex2dSize));
2128 return;
2129 case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE:
2130 retval.set(JS::NumberValue(limits.maxTexCubeSize));
2131 return;
2132 case LOCAL_GL_MAX_VERTEX_ATTRIBS:
2133 retval.set(JS::NumberValue(limits.maxVertexAttribs));
2134 return;
2136 case LOCAL_GL_MAX_VIEWS_OVR:
2137 if (IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) {
2138 retval.set(JS::NumberValue(limits.maxMultiviewLayers));
2139 return;
2141 break;
2143 case LOCAL_GL_PACK_ALIGNMENT:
2144 retval.set(JS::NumberValue(state.mPixelPackState.alignmentInTypeElems));
2145 return;
2146 case LOCAL_GL_UNPACK_ALIGNMENT:
2147 retval.set(JS::NumberValue(state.mPixelUnpackState.alignmentInTypeElems));
2148 return;
2150 case dom::WebGLRenderingContext_Binding::UNPACK_FLIP_Y_WEBGL:
2151 retval.set(JS::BooleanValue(state.mPixelUnpackState.flipY));
2152 return;
2153 case dom::WebGLRenderingContext_Binding::UNPACK_PREMULTIPLY_ALPHA_WEBGL:
2154 retval.set(JS::BooleanValue(state.mPixelUnpackState.premultiplyAlpha));
2155 return;
2156 case dom::WebGLRenderingContext_Binding::UNPACK_COLORSPACE_CONVERSION_WEBGL:
2157 retval.set(JS::NumberValue(state.mPixelUnpackState.colorspaceConversion));
2158 return;
2160 case dom::WEBGL_provoking_vertex_Binding::PROVOKING_VERTEX_WEBGL:
2161 if (!IsExtensionEnabled(WebGLExtensionID::WEBGL_provoking_vertex)) break;
2162 retval.set(JS::NumberValue(UnderlyingValue(state.mProvokingVertex)));
2163 return;
2165 case LOCAL_GL_DEPTH_CLAMP:
2166 if (!IsExtensionEnabled(WebGLExtensionID::EXT_depth_clamp)) break;
2167 retval.set(JS::BooleanValue(state.mIsEnabledMap[LOCAL_GL_DEPTH_CLAMP]));
2168 return;
2170 // -
2171 // Array returns
2173 // 2 floats
2174 case LOCAL_GL_DEPTH_RANGE:
2175 retval.set(Create<dom::Float32Array>(cx, this, state.mDepthRange, rv));
2176 return;
2178 case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
2179 retval.set(
2180 Create<dom::Float32Array>(cx, this, limits.pointSizeRange, rv));
2181 return;
2183 case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE:
2184 retval.set(
2185 Create<dom::Float32Array>(cx, this, limits.lineWidthRange, rv));
2186 return;
2188 // 4 floats
2189 case LOCAL_GL_COLOR_CLEAR_VALUE:
2190 retval.set(Create<dom::Float32Array>(cx, this, state.mClearColor, rv));
2191 return;
2193 case LOCAL_GL_BLEND_COLOR:
2194 retval.set(Create<dom::Float32Array>(cx, this, state.mBlendColor, rv));
2195 return;
2197 // 2 ints
2198 case LOCAL_GL_MAX_VIEWPORT_DIMS: {
2199 auto maxViewportDim = BitwiseCast<int32_t>(limits.maxViewportDim);
2200 const auto dims = std::array<int32_t, 2>{maxViewportDim, maxViewportDim};
2201 retval.set(Create<dom::Int32Array>(cx, this, dims, rv));
2202 return;
2205 // 4 ints
2206 case LOCAL_GL_SCISSOR_BOX:
2207 retval.set(Create<dom::Int32Array>(cx, this, state.mScissor, rv));
2208 return;
2210 case LOCAL_GL_VIEWPORT:
2211 retval.set(Create<dom::Int32Array>(cx, this, state.mViewport, rv));
2212 return;
2214 // any
2215 case LOCAL_GL_COMPRESSED_TEXTURE_FORMATS:
2216 retval.set(Create<dom::Uint32Array>(cx, this,
2217 state.mCompressedTextureFormats, rv));
2218 return;
2221 if (mIsWebGL2) {
2222 switch (pname) {
2223 case LOCAL_GL_COPY_READ_BUFFER_BINDING:
2224 fnSetRetval_Buffer(LOCAL_GL_COPY_READ_BUFFER);
2225 return;
2227 case LOCAL_GL_COPY_WRITE_BUFFER_BINDING:
2228 fnSetRetval_Buffer(LOCAL_GL_COPY_WRITE_BUFFER);
2229 return;
2231 case LOCAL_GL_DRAW_FRAMEBUFFER_BINDING:
2232 (void)ToJSValueOrNull(cx, state.mBoundDrawFb, retval);
2233 return;
2235 case LOCAL_GL_MAX_CLIENT_WAIT_TIMEOUT_WEBGL:
2236 retval.set(JS::NumberValue(webgl::kMaxClientWaitSyncTimeoutNS));
2237 return;
2239 case LOCAL_GL_PIXEL_PACK_BUFFER_BINDING:
2240 fnSetRetval_Buffer(LOCAL_GL_PIXEL_PACK_BUFFER);
2241 return;
2243 case LOCAL_GL_PIXEL_UNPACK_BUFFER_BINDING:
2244 fnSetRetval_Buffer(LOCAL_GL_PIXEL_UNPACK_BUFFER);
2245 return;
2247 case LOCAL_GL_READ_FRAMEBUFFER_BINDING:
2248 (void)ToJSValueOrNull(cx, state.mBoundReadFb, retval);
2249 return;
2251 case LOCAL_GL_SAMPLER_BINDING: {
2252 const auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
2253 (void)ToJSValueOrNull(cx, texUnit.sampler, retval);
2254 return;
2257 case LOCAL_GL_TEXTURE_BINDING_2D_ARRAY:
2258 fnSetRetval_Tex(LOCAL_GL_TEXTURE_2D_ARRAY);
2259 return;
2261 case LOCAL_GL_TEXTURE_BINDING_3D:
2262 fnSetRetval_Tex(LOCAL_GL_TEXTURE_3D);
2263 return;
2265 case LOCAL_GL_TRANSFORM_FEEDBACK_BINDING: {
2266 auto ret = state.mBoundTfo;
2267 if (ret == state.mDefaultTfo) {
2268 ret = nullptr;
2270 (void)ToJSValueOrNull(cx, ret, retval);
2271 return;
2274 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
2275 fnSetRetval_Buffer(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER);
2276 return;
2278 case LOCAL_GL_UNIFORM_BUFFER_BINDING:
2279 fnSetRetval_Buffer(LOCAL_GL_UNIFORM_BUFFER);
2280 return;
2282 case LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS:
2283 retval.set(
2284 JS::NumberValue(webgl::kMaxTransformFeedbackSeparateAttribs));
2285 return;
2286 case LOCAL_GL_MAX_UNIFORM_BUFFER_BINDINGS:
2287 retval.set(JS::NumberValue(limits.maxUniformBufferBindings));
2288 return;
2289 case LOCAL_GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT:
2290 retval.set(JS::NumberValue(limits.uniformBufferOffsetAlignment));
2291 return;
2292 case LOCAL_GL_MAX_3D_TEXTURE_SIZE:
2293 retval.set(JS::NumberValue(limits.maxTex3dSize));
2294 return;
2295 case LOCAL_GL_MAX_ARRAY_TEXTURE_LAYERS:
2296 retval.set(JS::NumberValue(limits.maxTexArrayLayers));
2297 return;
2299 case LOCAL_GL_PACK_ROW_LENGTH:
2300 retval.set(JS::NumberValue(state.mPixelPackState.rowLength));
2301 return;
2302 case LOCAL_GL_PACK_SKIP_PIXELS:
2303 retval.set(JS::NumberValue(state.mPixelPackState.skipPixels));
2304 return;
2305 case LOCAL_GL_PACK_SKIP_ROWS:
2306 retval.set(JS::NumberValue(state.mPixelPackState.skipRows));
2307 return;
2309 case LOCAL_GL_UNPACK_IMAGE_HEIGHT:
2310 retval.set(JS::NumberValue(state.mPixelUnpackState.imageHeight));
2311 return;
2312 case LOCAL_GL_UNPACK_ROW_LENGTH:
2313 retval.set(JS::NumberValue(state.mPixelUnpackState.rowLength));
2314 return;
2315 case LOCAL_GL_UNPACK_SKIP_IMAGES:
2316 retval.set(JS::NumberValue(state.mPixelUnpackState.skipImages));
2317 return;
2318 case LOCAL_GL_UNPACK_SKIP_PIXELS:
2319 retval.set(JS::NumberValue(state.mPixelUnpackState.skipPixels));
2320 return;
2321 case LOCAL_GL_UNPACK_SKIP_ROWS:
2322 retval.set(JS::NumberValue(state.mPixelUnpackState.skipRows));
2323 return;
2324 } // switch pname
2325 } // if webgl2
2327 // -
2329 if (!debug) {
2330 const auto GetUnmaskedRenderer = [&]() {
2331 const auto prefLock = StaticPrefs::webgl_override_unmasked_renderer();
2332 if (!prefLock->IsEmpty()) {
2333 return Some(ToString(*prefLock));
2335 return GetString(LOCAL_GL_RENDERER);
2338 const auto GetUnmaskedVendor = [&]() {
2339 const auto prefLock = StaticPrefs::webgl_override_unmasked_vendor();
2340 if (!prefLock->IsEmpty()) {
2341 return Some(ToString(*prefLock));
2343 return GetString(LOCAL_GL_VENDOR);
2346 // -
2348 Maybe<std::string> ret;
2350 switch (pname) {
2351 case LOCAL_GL_VENDOR:
2352 ret = Some(std::string{"Mozilla"});
2353 break;
2355 case LOCAL_GL_RENDERER: {
2356 bool allowRenderer = StaticPrefs::webgl_enable_renderer_query();
2357 if (ShouldResistFingerprinting(RFPTarget::WebGLRenderInfo)) {
2358 allowRenderer = false;
2360 if (allowRenderer) {
2361 ret = GetUnmaskedRenderer();
2362 if (ret) {
2363 ret = Some(webgl::SanitizeRenderer(*ret));
2366 if (!ret) {
2367 ret = Some(std::string{"Mozilla"});
2369 break;
2372 case LOCAL_GL_VERSION:
2373 if (mIsWebGL2) {
2374 ret = Some(std::string{"WebGL 2.0"});
2375 } else {
2376 ret = Some(std::string{"WebGL 1.0"});
2378 break;
2380 case LOCAL_GL_SHADING_LANGUAGE_VERSION:
2381 if (mIsWebGL2) {
2382 ret = Some(std::string{"WebGL GLSL ES 3.00"});
2383 } else {
2384 ret = Some(std::string{"WebGL GLSL ES 1.0"});
2386 break;
2388 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
2389 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL: {
2390 if (!IsExtensionEnabled(WebGLExtensionID::WEBGL_debug_renderer_info)) {
2391 EnqueueError_ArgEnum("pname", pname);
2392 return;
2395 switch (pname) {
2396 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
2397 ret = GetUnmaskedRenderer();
2398 if (ret && StaticPrefs::webgl_sanitize_unmasked_renderer()) {
2399 *ret = webgl::SanitizeRenderer(*ret);
2401 break;
2403 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
2404 ret = GetUnmaskedVendor();
2405 break;
2407 default:
2408 MOZ_CRASH();
2410 break;
2413 default:
2414 break;
2417 if (ret) {
2418 retval.set(StringValue(cx, *ret, rv));
2419 return;
2421 } // if (!debug)
2423 // -
2425 bool debugOnly = false;
2426 bool asString = false;
2428 switch (pname) {
2429 case LOCAL_GL_EXTENSIONS:
2430 case LOCAL_GL_RENDERER:
2431 case LOCAL_GL_VENDOR:
2432 case LOCAL_GL_VERSION:
2433 case dom::MOZ_debug_Binding::CONTEXT_TYPE:
2434 case dom::MOZ_debug_Binding::WSI_INFO:
2435 debugOnly = true;
2436 asString = true;
2437 break;
2439 case dom::MOZ_debug_Binding::DOES_INDEX_VALIDATION:
2440 debugOnly = true;
2441 break;
2443 default:
2444 break;
2447 if (debugOnly && !debug) {
2448 EnqueueError_ArgEnum("pname", pname);
2449 return;
2452 // -
2454 if (asString) {
2455 const auto maybe = GetString(pname);
2456 if (maybe) {
2457 auto str = std::string{};
2458 if (pname == dom::MOZ_debug_Binding::WSI_INFO) {
2459 const auto& outOfProcess = mNotLost->outOfProcess;
2460 const auto& inProcess = mNotLost->inProcess;
2461 str += PrintfStdString("outOfProcess: %s\ninProcess: %s\n",
2462 ToChars(bool(outOfProcess)),
2463 ToChars(bool(inProcess)));
2465 str += *maybe;
2466 retval.set(StringValue(cx, str.c_str(), rv));
2468 } else {
2469 const auto maybe = GetNumber(pname);
2470 if (maybe) {
2471 switch (pname) {
2472 // WebGL 1:
2473 case LOCAL_GL_BLEND:
2474 case LOCAL_GL_CULL_FACE:
2475 case LOCAL_GL_DEPTH_TEST:
2476 case LOCAL_GL_DEPTH_WRITEMASK:
2477 case LOCAL_GL_DITHER:
2478 case LOCAL_GL_POLYGON_OFFSET_FILL:
2479 case LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE:
2480 case LOCAL_GL_SAMPLE_COVERAGE:
2481 case LOCAL_GL_SAMPLE_COVERAGE_INVERT:
2482 case LOCAL_GL_SCISSOR_TEST:
2483 case LOCAL_GL_STENCIL_TEST:
2484 // WebGL 2:
2485 case LOCAL_GL_RASTERIZER_DISCARD:
2486 case LOCAL_GL_TRANSFORM_FEEDBACK_ACTIVE:
2487 case LOCAL_GL_TRANSFORM_FEEDBACK_PAUSED:
2488 retval.set(JS::BooleanValue(*maybe));
2489 break;
2491 // 4 bools
2492 case LOCAL_GL_COLOR_WRITEMASK: {
2493 const auto mask = uint8_t(*maybe);
2494 const auto bs = std::bitset<4>(mask);
2495 const auto src = std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]};
2496 JS::Rooted<JS::Value> arr(cx);
2497 if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
2498 rv = NS_ERROR_OUT_OF_MEMORY;
2500 retval.set(arr);
2501 return;
2504 default:
2505 retval.set(JS::NumberValue(*maybe));
2506 break;
2512 void ClientWebGLContext::GetBufferParameter(
2513 JSContext* cx, GLenum target, GLenum pname,
2514 JS::MutableHandle<JS::Value> retval) const {
2515 retval.set(JS::NullValue());
2516 if (IsContextLost()) return;
2518 const auto maybe = [&]() {
2519 const auto& inProcess = mNotLost->inProcess;
2520 if (inProcess) {
2521 return inProcess->GetBufferParameter(target, pname);
2523 const auto& child = mNotLost->outOfProcess;
2524 child->FlushPendingCmds();
2525 Maybe<double> ret;
2526 if (!child->SendGetBufferParameter(target, pname, &ret)) {
2527 ret.reset();
2529 return ret;
2530 }();
2531 if (maybe) {
2532 retval.set(JS::NumberValue(*maybe));
2536 bool IsFramebufferTarget(const bool isWebgl2, const GLenum target) {
2537 switch (target) {
2538 case LOCAL_GL_FRAMEBUFFER:
2539 return true;
2541 case LOCAL_GL_DRAW_FRAMEBUFFER:
2542 case LOCAL_GL_READ_FRAMEBUFFER:
2543 return isWebgl2;
2545 default:
2546 return false;
2550 void ClientWebGLContext::GetFramebufferAttachmentParameter(
2551 JSContext* const cx, const GLenum target, const GLenum attachment,
2552 const GLenum pname, JS::MutableHandle<JS::Value> retval,
2553 ErrorResult& rv) const {
2554 retval.set(JS::NullValue());
2555 const FuncScope funcScope(*this, "getFramebufferAttachmentParameter");
2556 if (IsContextLost()) return;
2558 const auto& state = State();
2560 if (!IsFramebufferTarget(mIsWebGL2, target)) {
2561 EnqueueError_ArgEnum("target", target);
2562 return;
2564 auto fb = state.mBoundDrawFb;
2565 if (target == LOCAL_GL_READ_FRAMEBUFFER) {
2566 fb = state.mBoundReadFb;
2569 const auto fnGet = [&](const GLenum pname) {
2570 const auto fbId = fb ? fb->mId : 0;
2572 const auto& inProcess = mNotLost->inProcess;
2573 if (inProcess) {
2574 return inProcess->GetFramebufferAttachmentParameter(fbId, attachment,
2575 pname);
2577 const auto& child = mNotLost->outOfProcess;
2578 child->FlushPendingCmds();
2579 Maybe<double> ret;
2580 if (!child->SendGetFramebufferAttachmentParameter(fbId, attachment, pname,
2581 &ret)) {
2582 ret.reset();
2584 return ret;
2587 if (fb) {
2588 if (fb->mOpaque) {
2589 EnqueueError(LOCAL_GL_INVALID_OPERATION,
2590 "An opaque framebuffer's attachments cannot be inspected or "
2591 "changed.");
2592 return;
2594 auto attachmentSlotEnum = attachment;
2595 if (mIsWebGL2 && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
2596 // In webgl2, DEPTH_STENCIL is valid iff the DEPTH and STENCIL images
2597 // match, so check if the server errors.
2598 const auto maybe = fnGet(LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE);
2599 if (!maybe) return;
2600 attachmentSlotEnum = LOCAL_GL_DEPTH_ATTACHMENT;
2603 const auto maybeSlot = fb->GetAttachment(attachmentSlotEnum);
2604 if (!maybeSlot) {
2605 EnqueueError_ArgEnum("attachment", attachment);
2606 return;
2608 const auto& attached = *maybeSlot;
2610 // -
2612 if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) {
2613 if (attached.rb) {
2614 (void)ToJSValueOrNull(cx, attached.rb, retval);
2615 } else {
2616 if (!mIsWebGL2 && !attached.tex) {
2617 EnqueueError_ArgEnum("pname", pname);
2618 return;
2620 (void)ToJSValueOrNull(cx, attached.tex, retval);
2622 return;
2626 const auto maybe = fnGet(pname);
2627 if (maybe) {
2628 retval.set(JS::NumberValue(*maybe));
2632 void ClientWebGLContext::GetRenderbufferParameter(
2633 JSContext* cx, GLenum target, GLenum pname,
2634 JS::MutableHandle<JS::Value> retval) const {
2635 retval.set(JS::NullValue());
2636 const FuncScope funcScope(*this, "getRenderbufferParameter");
2637 if (IsContextLost()) return;
2639 if (target != LOCAL_GL_RENDERBUFFER) {
2640 EnqueueError_ArgEnum("target", target);
2641 return;
2644 const auto& state = State();
2645 const auto& rb = state.mBoundRb;
2646 const auto rbId = rb ? rb->mId : 0;
2647 const auto maybe = [&]() {
2648 const auto& inProcess = mNotLost->inProcess;
2649 if (inProcess) {
2650 return inProcess->GetRenderbufferParameter(rbId, pname);
2652 const auto& child = mNotLost->outOfProcess;
2653 child->FlushPendingCmds();
2654 Maybe<double> ret;
2655 if (!child->SendGetRenderbufferParameter(rbId, pname, &ret)) {
2656 ret.reset();
2658 return ret;
2659 }();
2660 if (maybe) {
2661 retval.set(JS::NumberValue(*maybe));
2665 void ClientWebGLContext::GetIndexedParameter(
2666 JSContext* cx, GLenum target, GLuint index,
2667 JS::MutableHandle<JS::Value> retval, ErrorResult& rv) const {
2668 retval.set(JS::NullValue());
2669 const FuncScope funcScope(*this, "getIndexedParameter");
2670 if (IsContextLost()) return;
2672 const auto& state = State();
2674 switch (target) {
2675 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: {
2676 const auto& list = state.mBoundTfo->mAttribBuffers;
2677 if (index >= list.size()) {
2678 EnqueueError(LOCAL_GL_INVALID_VALUE,
2679 "`index` (%u) >= MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS",
2680 index);
2681 return;
2683 (void)ToJSValueOrNull(cx, list[index], retval);
2684 return;
2687 case LOCAL_GL_UNIFORM_BUFFER_BINDING: {
2688 const auto& list = state.mBoundUbos;
2689 if (index >= list.size()) {
2690 EnqueueError(LOCAL_GL_INVALID_VALUE,
2691 "`index` (%u) >= MAX_UNIFORM_BUFFER_BINDINGS", index);
2692 return;
2694 (void)ToJSValueOrNull(cx, list[index], retval);
2695 return;
2699 const auto maybe = [&]() {
2700 const auto& inProcess = mNotLost->inProcess;
2701 if (inProcess) {
2702 return inProcess->GetIndexedParameter(target, index);
2704 const auto& child = mNotLost->outOfProcess;
2705 child->FlushPendingCmds();
2706 Maybe<double> ret;
2707 if (!child->SendGetIndexedParameter(target, index, &ret)) {
2708 ret.reset();
2710 return ret;
2711 }();
2712 if (maybe) {
2713 switch (target) {
2714 case LOCAL_GL_COLOR_WRITEMASK: {
2715 const auto bs = std::bitset<4>(*maybe);
2716 const auto src = std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]};
2717 JS::Rooted<JS::Value> arr(cx);
2718 if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
2719 rv = NS_ERROR_OUT_OF_MEMORY;
2721 retval.set(arr);
2722 return;
2725 default:
2726 retval.set(JS::NumberValue(*maybe));
2727 return;
2732 void ClientWebGLContext::GetUniform(JSContext* const cx,
2733 const WebGLProgramJS& prog,
2734 const WebGLUniformLocationJS& loc,
2735 JS::MutableHandle<JS::Value> retval) {
2736 retval.set(JS::NullValue());
2737 const FuncScope funcScope(*this, "getUniform");
2738 if (IsContextLost()) return;
2739 if (!prog.ValidateUsable(*this, "prog")) return;
2740 if (!loc.ValidateUsable(*this, "loc")) return;
2742 const auto& progLinkResult = GetLinkResult(prog);
2743 if (!progLinkResult.success) {
2744 EnqueueError(LOCAL_GL_INVALID_OPERATION, "Program is not linked.");
2745 return;
2747 const auto& uniformLinkResult = loc.mParent.lock();
2748 if (uniformLinkResult.get() != &progLinkResult) {
2749 EnqueueError(
2750 LOCAL_GL_INVALID_OPERATION,
2751 "UniformLocation is not from the most recent linking of Program.");
2752 return;
2755 const auto res = [&]() {
2756 const auto& inProcess = mNotLost->inProcess;
2757 if (inProcess) {
2758 return inProcess->GetUniform(prog.mId, loc.mLocation);
2760 const auto& child = mNotLost->outOfProcess;
2761 child->FlushPendingCmds();
2762 webgl::GetUniformData ret;
2763 if (!child->SendGetUniform(prog.mId, loc.mLocation, &ret)) {
2764 ret = {};
2766 return ret;
2767 }();
2768 if (!res.type) return;
2770 const auto elemCount = ElemTypeComponents(res.type);
2771 MOZ_ASSERT(elemCount);
2773 switch (res.type) {
2774 case LOCAL_GL_BOOL:
2775 retval.set(JS::BooleanValue(res.data[0]));
2776 return;
2778 case LOCAL_GL_FLOAT: {
2779 const auto ptr = reinterpret_cast<const float*>(res.data);
2780 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2781 return;
2783 case LOCAL_GL_INT: {
2784 const auto ptr = reinterpret_cast<const int32_t*>(res.data);
2785 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2786 return;
2788 case LOCAL_GL_UNSIGNED_INT:
2789 case LOCAL_GL_SAMPLER_2D:
2790 case LOCAL_GL_SAMPLER_3D:
2791 case LOCAL_GL_SAMPLER_CUBE:
2792 case LOCAL_GL_SAMPLER_2D_SHADOW:
2793 case LOCAL_GL_SAMPLER_2D_ARRAY:
2794 case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
2795 case LOCAL_GL_SAMPLER_CUBE_SHADOW:
2796 case LOCAL_GL_INT_SAMPLER_2D:
2797 case LOCAL_GL_INT_SAMPLER_3D:
2798 case LOCAL_GL_INT_SAMPLER_CUBE:
2799 case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
2800 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
2801 case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
2802 case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
2803 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: {
2804 const auto ptr = reinterpret_cast<const uint32_t*>(res.data);
2805 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2806 return;
2809 // -
2811 case LOCAL_GL_BOOL_VEC2:
2812 case LOCAL_GL_BOOL_VEC3:
2813 case LOCAL_GL_BOOL_VEC4: {
2814 const auto intArr = reinterpret_cast<const int32_t*>(res.data);
2815 bool boolArr[4] = {};
2816 for (const auto i : IntegerRange(elemCount)) {
2817 boolArr[i] = bool(intArr[i]);
2819 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, boolArr, elemCount, retval));
2820 return;
2823 case LOCAL_GL_FLOAT_VEC2:
2824 case LOCAL_GL_FLOAT_VEC3:
2825 case LOCAL_GL_FLOAT_VEC4:
2826 case LOCAL_GL_FLOAT_MAT2:
2827 case LOCAL_GL_FLOAT_MAT3:
2828 case LOCAL_GL_FLOAT_MAT4:
2829 case LOCAL_GL_FLOAT_MAT2x3:
2830 case LOCAL_GL_FLOAT_MAT2x4:
2831 case LOCAL_GL_FLOAT_MAT3x2:
2832 case LOCAL_GL_FLOAT_MAT3x4:
2833 case LOCAL_GL_FLOAT_MAT4x2:
2834 case LOCAL_GL_FLOAT_MAT4x3: {
2835 const auto ptr = reinterpret_cast<const float*>(res.data);
2836 IgnoredErrorResult error;
2837 JSObject* obj =
2838 dom::Float32Array::Create(cx, this, Span(ptr, elemCount), error);
2839 MOZ_ASSERT(obj);
2840 retval.set(JS::ObjectOrNullValue(obj));
2841 return;
2844 case LOCAL_GL_INT_VEC2:
2845 case LOCAL_GL_INT_VEC3:
2846 case LOCAL_GL_INT_VEC4: {
2847 const auto ptr = reinterpret_cast<const int32_t*>(res.data);
2848 IgnoredErrorResult error;
2849 JSObject* obj =
2850 dom::Int32Array::Create(cx, this, Span(ptr, elemCount), error);
2851 MOZ_ASSERT(obj);
2852 retval.set(JS::ObjectOrNullValue(obj));
2853 return;
2856 case LOCAL_GL_UNSIGNED_INT_VEC2:
2857 case LOCAL_GL_UNSIGNED_INT_VEC3:
2858 case LOCAL_GL_UNSIGNED_INT_VEC4: {
2859 const auto ptr = reinterpret_cast<const uint32_t*>(res.data);
2860 IgnoredErrorResult error;
2861 JSObject* obj =
2862 dom::Uint32Array::Create(cx, this, Span(ptr, elemCount), error);
2863 MOZ_ASSERT(obj);
2864 retval.set(JS::ObjectOrNullValue(obj));
2865 return;
2868 default:
2869 MOZ_CRASH("GFX: Invalid elemType.");
2873 already_AddRefed<WebGLShaderPrecisionFormatJS>
2874 ClientWebGLContext::GetShaderPrecisionFormat(const GLenum shadertype,
2875 const GLenum precisiontype) {
2876 if (IsContextLost()) return nullptr;
2877 const auto info = [&]() {
2878 const auto& inProcess = mNotLost->inProcess;
2879 if (inProcess) {
2880 return inProcess->GetShaderPrecisionFormat(shadertype, precisiontype);
2882 const auto& child = mNotLost->outOfProcess;
2883 child->FlushPendingCmds();
2884 Maybe<webgl::ShaderPrecisionFormat> ret;
2885 if (!child->SendGetShaderPrecisionFormat(shadertype, precisiontype, &ret)) {
2886 ret.reset();
2888 return ret;
2889 }();
2891 if (!info) return nullptr;
2892 return AsAddRefed(new WebGLShaderPrecisionFormatJS(*info));
2895 void ClientWebGLContext::BlendColor(GLclampf r, GLclampf g, GLclampf b,
2896 GLclampf a) {
2897 const FuncScope funcScope(*this, "blendColor");
2898 if (IsContextLost()) return;
2899 auto& state = State();
2901 const bool unclamped =
2902 (mIsWebGL2 ||
2903 IsExtensionEnabled(WebGLExtensionID::WEBGL_color_buffer_float) ||
2904 IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float));
2905 if (!unclamped) {
2906 r = std::clamp(r, 0.0f, 1.0f);
2907 g = std::clamp(g, 0.0f, 1.0f);
2908 b = std::clamp(b, 0.0f, 1.0f);
2909 a = std::clamp(a, 0.0f, 1.0f);
2912 auto& cache = state.mBlendColor;
2913 cache[0] = r;
2914 cache[1] = g;
2915 cache[2] = b;
2916 cache[3] = a;
2918 Run<RPROC(BlendColor)>(r, g, b, a);
2921 void ClientWebGLContext::BlendEquationSeparateI(Maybe<GLuint> i, GLenum modeRGB,
2922 GLenum modeAlpha) {
2923 Run<RPROC(BlendEquationSeparate)>(i, modeRGB, modeAlpha);
2926 void ClientWebGLContext::BlendFuncSeparateI(Maybe<GLuint> i, GLenum srcRGB,
2927 GLenum dstRGB, GLenum srcAlpha,
2928 GLenum dstAlpha) {
2929 Run<RPROC(BlendFuncSeparate)>(i, srcRGB, dstRGB, srcAlpha, dstAlpha);
2932 GLenum ClientWebGLContext::CheckFramebufferStatus(GLenum target) {
2933 if (IsContextLost()) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
2935 const auto& inProcess = mNotLost->inProcess;
2936 if (inProcess) {
2937 return inProcess->CheckFramebufferStatus(target);
2939 const auto& child = mNotLost->outOfProcess;
2940 child->FlushPendingCmds();
2941 GLenum ret = 0;
2942 if (!child->SendCheckFramebufferStatus(target, &ret)) {
2943 ret = 0;
2945 return ret;
2948 void ClientWebGLContext::Clear(GLbitfield mask) {
2949 Run<RPROC(Clear)>(mask);
2951 AfterDrawCall();
2954 // -
2956 void ClientWebGLContext::ClearBufferTv(const GLenum buffer,
2957 const GLint drawBuffer,
2958 const webgl::AttribBaseType type,
2959 JS::AutoCheckCannotGC&& nogc,
2960 const Span<const uint8_t>& view,
2961 const GLuint srcElemOffset) {
2962 if (IsContextLost()) return;
2964 const auto byteOffset = CheckedInt<size_t>(srcElemOffset) * sizeof(float);
2965 if (!byteOffset.isValid() || byteOffset.value() > view.Length()) {
2966 nogc.reset();
2967 EnqueueError(LOCAL_GL_INVALID_VALUE, "`srcOffset` too large for `values`.");
2968 return;
2970 webgl::TypedQuad data;
2971 data.type = type;
2973 auto dataSize = data.data.size();
2974 switch (buffer) {
2975 case LOCAL_GL_COLOR:
2976 break;
2978 case LOCAL_GL_DEPTH:
2979 dataSize = sizeof(float);
2980 break;
2982 case LOCAL_GL_STENCIL:
2983 dataSize = sizeof(int32_t);
2984 break;
2986 default:
2987 nogc.reset();
2988 EnqueueError_ArgEnum("buffer", buffer);
2989 return;
2992 const auto requiredBytes = byteOffset + dataSize;
2993 if (!requiredBytes.isValid() || requiredBytes.value() > view.Length()) {
2994 nogc.reset();
2995 EnqueueError(LOCAL_GL_INVALID_VALUE, "`values` too small.");
2996 return;
2999 memcpy(data.data.data(), view.data() + byteOffset.value(), dataSize);
3000 nogc.reset(); // Done with `view`.
3001 Run<RPROC(ClearBufferTv)>(buffer, drawBuffer, data);
3003 AfterDrawCall();
3006 void ClientWebGLContext::ClearBufferfi(GLenum buffer, GLint drawBuffer,
3007 GLfloat depth, GLint stencil) {
3008 Run<RPROC(ClearBufferfi)>(buffer, drawBuffer, depth, stencil);
3010 AfterDrawCall();
3013 // -
3015 void ClientWebGLContext::ClearColor(GLclampf r, GLclampf g, GLclampf b,
3016 GLclampf a) {
3017 const FuncScope funcScope(*this, "clearColor");
3018 if (IsContextLost()) return;
3019 auto& state = State();
3021 auto& cache = state.mClearColor;
3022 cache[0] = r;
3023 cache[1] = g;
3024 cache[2] = b;
3025 cache[3] = a;
3027 Run<RPROC(ClearColor)>(r, g, b, a);
3030 void ClientWebGLContext::ClearDepth(GLclampf v) { Run<RPROC(ClearDepth)>(v); }
3032 void ClientWebGLContext::ClearStencil(GLint v) { Run<RPROC(ClearStencil)>(v); }
3034 void ClientWebGLContext::ColorMaskI(Maybe<GLuint> i, bool r, bool g, bool b,
3035 bool a) const {
3036 const FuncScope funcScope(*this, "colorMask");
3037 if (IsContextLost()) return;
3039 const uint8_t mask =
3040 uint8_t(r << 0) | uint8_t(g << 1) | uint8_t(b << 2) | uint8_t(a << 3);
3041 Run<RPROC(ColorMask)>(i, mask);
3044 void ClientWebGLContext::CullFace(GLenum face) { Run<RPROC(CullFace)>(face); }
3046 void ClientWebGLContext::DepthFunc(GLenum func) { Run<RPROC(DepthFunc)>(func); }
3048 void ClientWebGLContext::DepthMask(WebGLboolean b) { Run<RPROC(DepthMask)>(b); }
3050 void ClientWebGLContext::DepthRange(GLclampf zNear, GLclampf zFar) {
3051 const FuncScope funcScope(*this, "depthRange");
3052 if (IsContextLost()) return;
3053 auto& state = State();
3055 state.mDepthRange = {zNear, zFar};
3057 Run<RPROC(DepthRange)>(zNear, zFar);
3060 void ClientWebGLContext::Flush(const bool flushGl) const {
3061 const FuncScope funcScope(*this, "flush");
3062 if (IsContextLost()) return;
3064 if (flushGl) {
3065 Run<RPROC(Flush)>();
3068 if (mNotLost->inProcess) return;
3069 const auto& child = mNotLost->outOfProcess;
3070 child->FlushPendingCmds();
3073 void ClientWebGLContext::Finish() {
3074 if (IsContextLost()) return;
3076 const auto& inProcess = mNotLost->inProcess;
3077 if (inProcess) {
3078 inProcess->Finish();
3079 return;
3081 const auto& child = mNotLost->outOfProcess;
3082 child->FlushPendingCmds();
3083 (void)child->SendFinish();
3086 void ClientWebGLContext::FrontFace(GLenum mode) { Run<RPROC(FrontFace)>(mode); }
3088 GLenum ClientWebGLContext::GetError() {
3089 const FuncScope funcScope(*this, "getError");
3090 if (mNextError) {
3091 const auto ret = mNextError;
3092 mNextError = 0;
3093 return ret;
3095 if (IsContextLost()) return 0;
3097 const auto& inProcess = mNotLost->inProcess;
3098 if (inProcess) {
3099 return inProcess->GetError();
3101 const auto& child = mNotLost->outOfProcess;
3102 child->FlushPendingCmds();
3103 GLenum ret = 0;
3104 if (!child->SendGetError(&ret)) {
3105 ret = 0;
3107 return ret;
3110 void ClientWebGLContext::Hint(GLenum target, GLenum mode) {
3111 Run<RPROC(Hint)>(target, mode);
3114 void ClientWebGLContext::LineWidth(GLfloat width) {
3115 Run<RPROC(LineWidth)>(width);
3118 Maybe<webgl::ErrorInfo> SetPixelUnpack(
3119 const bool isWebgl2, webgl::PixelUnpackStateWebgl* const unpacking,
3120 const GLenum pname, const GLint param);
3122 void ClientWebGLContext::PixelStorei(const GLenum pname, const GLint iparam) {
3123 const FuncScope funcScope(*this, "pixelStorei");
3124 if (IsContextLost()) return;
3125 if (!ValidateNonNegative("param", iparam)) return;
3126 const auto param = static_cast<uint32_t>(iparam);
3128 auto& state = State();
3129 auto& packState = state.mPixelPackState;
3130 switch (pname) {
3131 case LOCAL_GL_PACK_ALIGNMENT:
3132 switch (param) {
3133 case 1:
3134 case 2:
3135 case 4:
3136 case 8:
3137 break;
3138 default:
3139 EnqueueError(LOCAL_GL_INVALID_VALUE,
3140 "PACK_ALIGNMENT must be one of [1,2,4,8], was %i.",
3141 iparam);
3142 return;
3144 packState.alignmentInTypeElems = param;
3145 return;
3147 case LOCAL_GL_PACK_ROW_LENGTH:
3148 if (!mIsWebGL2) break;
3149 packState.rowLength = param;
3150 return;
3152 case LOCAL_GL_PACK_SKIP_PIXELS:
3153 if (!mIsWebGL2) break;
3154 packState.skipPixels = param;
3155 return;
3157 case LOCAL_GL_PACK_SKIP_ROWS:
3158 if (!mIsWebGL2) break;
3159 packState.skipRows = param;
3160 return;
3162 case dom::MOZ_debug_Binding::UNPACK_REQUIRE_FASTPATH:
3163 if (!IsSupported(WebGLExtensionID::MOZ_debug)) {
3164 EnqueueError_ArgEnum("pname", pname);
3165 return;
3167 break;
3169 default:
3170 break;
3173 const auto err =
3174 SetPixelUnpack(mIsWebGL2, &state.mPixelUnpackState, pname, iparam);
3175 if (err) {
3176 EnqueueError(*err);
3177 return;
3181 void ClientWebGLContext::PolygonOffset(GLfloat factor, GLfloat units) {
3182 Run<RPROC(PolygonOffset)>(factor, units);
3185 void ClientWebGLContext::SampleCoverage(GLclampf value, WebGLboolean invert) {
3186 Run<RPROC(SampleCoverage)>(value, invert);
3189 void ClientWebGLContext::Scissor(GLint x, GLint y, GLsizei width,
3190 GLsizei height) {
3191 const FuncScope funcScope(*this, "scissor");
3192 if (IsContextLost()) return;
3193 auto& state = State();
3195 if (!ValidateNonNegative("width", width) ||
3196 !ValidateNonNegative("height", height)) {
3197 return;
3200 state.mScissor = {x, y, width, height};
3202 Run<RPROC(Scissor)>(x, y, width, height);
3205 void ClientWebGLContext::StencilFuncSeparate(GLenum face, GLenum func,
3206 GLint ref, GLuint mask) {
3207 Run<RPROC(StencilFuncSeparate)>(face, func, ref, mask);
3210 void ClientWebGLContext::StencilMaskSeparate(GLenum face, GLuint mask) {
3211 Run<RPROC(StencilMaskSeparate)>(face, mask);
3214 void ClientWebGLContext::StencilOpSeparate(GLenum face, GLenum sfail,
3215 GLenum dpfail, GLenum dppass) {
3216 Run<RPROC(StencilOpSeparate)>(face, sfail, dpfail, dppass);
3219 void ClientWebGLContext::Viewport(GLint x, GLint y, GLsizei width,
3220 GLsizei height) {
3221 const FuncScope funcScope(*this, "viewport");
3222 if (IsContextLost()) return;
3223 auto& state = State();
3225 if (!ValidateNonNegative("width", width) ||
3226 !ValidateNonNegative("height", height)) {
3227 return;
3230 state.mViewport = {x, y, width, height};
3232 Run<RPROC(Viewport)>(x, y, width, height);
3235 // ------------------------- Buffer Objects -------------------------
3237 Maybe<const webgl::ErrorInfo> ValidateBindBuffer(
3238 const GLenum target, const webgl::BufferKind curKind) {
3239 if (curKind == webgl::BufferKind::Undefined) return {};
3241 auto requiredKind = webgl::BufferKind::NonIndex;
3242 switch (target) {
3243 case LOCAL_GL_COPY_READ_BUFFER:
3244 case LOCAL_GL_COPY_WRITE_BUFFER:
3245 return {}; // Always ok
3247 case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
3248 requiredKind = webgl::BufferKind::Index;
3249 break;
3251 default:
3252 break;
3255 if (curKind != requiredKind) {
3256 const auto fnKindStr = [&](const webgl::BufferKind kind) {
3257 if (kind == webgl::BufferKind::Index) return "ELEMENT_ARRAY_BUFFER";
3258 return "non-ELEMENT_ARRAY_BUFFER";
3260 const auto info = nsPrintfCString(
3261 "Buffer previously bound to %s cannot be now bound to %s.",
3262 fnKindStr(curKind), fnKindStr(requiredKind));
3263 return Some(
3264 webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION, info.BeginReading()});
3267 return {};
3270 Maybe<webgl::ErrorInfo> CheckBindBufferRange(
3271 const GLenum target, const GLuint index, const bool isBuffer,
3272 const uint64_t offset, const uint64_t size, const webgl::Limits& limits) {
3273 const auto fnSome = [&](const GLenum type, const nsACString& info) {
3274 return Some(webgl::ErrorInfo{type, info.BeginReading()});
3277 switch (target) {
3278 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
3279 if (index >= webgl::kMaxTransformFeedbackSeparateAttribs) {
3280 const auto info = nsPrintfCString(
3281 "`index` (%u) must be less than "
3282 "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS (%u).",
3283 index, webgl::kMaxTransformFeedbackSeparateAttribs);
3284 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3287 if (offset % 4 != 0 || size % 4 != 0) {
3288 const auto info =
3289 nsPrintfCString("`offset` (%" PRIu64 ") and `size` (%" PRIu64
3290 ") must both be aligned to 4 for"
3291 " TRANSFORM_FEEDBACK_BUFFER.",
3292 offset, size);
3293 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3295 break;
3297 case LOCAL_GL_UNIFORM_BUFFER:
3298 if (index >= limits.maxUniformBufferBindings) {
3299 const auto info = nsPrintfCString(
3300 "`index` (%u) must be less than MAX_UNIFORM_BUFFER_BINDINGS (%u).",
3301 index, limits.maxUniformBufferBindings);
3302 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3305 if (offset % limits.uniformBufferOffsetAlignment != 0) {
3306 const auto info =
3307 nsPrintfCString("`offset` (%" PRIu64
3308 ") must be aligned to "
3309 "UNIFORM_BUFFER_OFFSET_ALIGNMENT (%u).",
3310 offset, limits.uniformBufferOffsetAlignment);
3311 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3313 break;
3315 default: {
3316 const auto info =
3317 nsPrintfCString("Unrecognized `target`: 0x%04x", target);
3318 return fnSome(LOCAL_GL_INVALID_ENUM, info);
3322 return {};
3325 // -
3327 void ClientWebGLContext::BindBuffer(const GLenum target,
3328 WebGLBufferJS* const buffer) {
3329 const FuncScope funcScope(*this, "bindBuffer");
3330 if (IsContextLost()) return;
3331 if (buffer && !buffer->ValidateUsable(*this, "buffer")) return;
3333 // -
3334 // Check for INVALID_ENUM
3336 auto& state = State();
3337 auto* slot = &(state.mBoundVao->mIndexBuffer);
3338 if (target != LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
3339 const auto itr = state.mBoundBufferByTarget.find(target);
3340 if (itr == state.mBoundBufferByTarget.end()) {
3341 EnqueueError_ArgEnum("target", target);
3342 return;
3344 slot = &(itr->second);
3347 // -
3349 auto kind = webgl::BufferKind::Undefined;
3350 if (buffer) {
3351 kind = buffer->mKind;
3353 const auto err = ValidateBindBuffer(target, kind);
3354 if (err) {
3355 EnqueueError(err->type, "%s", err->info.c_str());
3356 return;
3359 // -
3360 // Validation complete
3362 if (buffer && buffer->mKind == webgl::BufferKind::Undefined) {
3363 if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
3364 buffer->mKind = webgl::BufferKind::Index;
3365 } else {
3366 buffer->mKind = webgl::BufferKind::NonIndex;
3369 *slot = buffer;
3371 // -
3373 Run<RPROC(BindBuffer)>(target, buffer ? buffer->mId : 0);
3376 // -
3378 void ClientWebGLContext::BindBufferRangeImpl(const GLenum target,
3379 const GLuint index,
3380 WebGLBufferJS* const buffer,
3381 const uint64_t offset,
3382 const uint64_t size) {
3383 if (buffer && !buffer->ValidateUsable(*this, "buffer")) return;
3384 auto& state = State();
3386 // -
3388 const auto& limits = Limits();
3389 auto err =
3390 CheckBindBufferRange(target, index, bool(buffer), offset, size, limits);
3391 if (err) {
3392 EnqueueError(err->type, "%s", err->info.c_str());
3393 return;
3396 // -
3398 auto kind = webgl::BufferKind::Undefined;
3399 if (buffer) {
3400 kind = buffer->mKind;
3402 err = ValidateBindBuffer(target, kind);
3403 if (err) {
3404 EnqueueError(err->type, "%s", err->info.c_str());
3405 return;
3408 if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) {
3409 if (state.mTfActiveAndNotPaused) {
3410 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3411 "Cannot change TRANSFORM_FEEDBACK_BUFFER while "
3412 "TransformFeedback is active and not paused.");
3413 return;
3417 // -
3418 // Validation complete
3420 if (buffer && buffer->mKind == webgl::BufferKind::Undefined) {
3421 buffer->mKind = webgl::BufferKind::NonIndex;
3424 // -
3426 switch (target) {
3427 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
3428 state.mBoundTfo->mAttribBuffers[index] = buffer;
3429 break;
3431 case LOCAL_GL_UNIFORM_BUFFER:
3432 state.mBoundUbos[index] = buffer;
3433 break;
3435 default:
3436 MOZ_CRASH("Bad `target`");
3438 state.mBoundBufferByTarget[target] = buffer;
3440 // -
3442 Run<RPROC(BindBufferRange)>(target, index, buffer ? buffer->mId : 0, offset,
3443 size);
3446 static inline size_t SizeOfViewElem(const dom::ArrayBufferView& view) {
3447 const auto& elemType = view.Type();
3448 if (elemType == js::Scalar::MaxTypedArrayViewType) // DataViews.
3449 return 1;
3451 return js::Scalar::byteSize(elemType);
3454 void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset,
3455 const dom::ArrayBufferView& dstData,
3456 GLuint dstElemOffset,
3457 GLuint dstElemCountOverride) {
3458 const FuncScope funcScope(*this, "getBufferSubData");
3459 if (IsContextLost()) return;
3460 const auto notLost =
3461 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
3462 if (!ValidateNonNegative("srcByteOffset", srcByteOffset)) return;
3464 size_t elemSize = SizeOfViewElem(dstData);
3465 dstData.ProcessFixedData([&](const Span<uint8_t>& aData) {
3466 const auto& destView =
3467 ValidateArrayBufferView(aData, elemSize, dstElemOffset,
3468 dstElemCountOverride, LOCAL_GL_INVALID_VALUE);
3469 if (!destView) {
3470 return;
3473 const auto& inProcessContext = notLost->inProcess;
3474 if (inProcessContext) {
3475 inProcessContext->GetBufferSubData(target, srcByteOffset, *destView);
3476 return;
3479 const auto& child = notLost->outOfProcess;
3480 child->FlushPendingCmds();
3481 mozilla::ipc::Shmem rawShmem;
3482 if (!child->SendGetBufferSubData(target, srcByteOffset, destView->size(),
3483 &rawShmem)) {
3484 return;
3486 const webgl::RaiiShmem shmem{child, rawShmem};
3487 if (!shmem) {
3488 EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "Failed to map in sub data buffer.");
3489 return;
3492 const auto shmemView = Span{shmem.ByteRange()};
3493 MOZ_RELEASE_ASSERT(shmemView.size() == 1 + destView->size());
3495 const auto ok = bool(shmemView[0]);
3496 const auto srcView = shmemView.subspan(1);
3497 if (ok) {
3498 Memcpy(&*destView, srcView);
3503 ////
3505 void ClientWebGLContext::BufferData(GLenum target, WebGLsizeiptr rawSize,
3506 GLenum usage) {
3507 const FuncScope funcScope(*this, "bufferData");
3508 if (!ValidateNonNegative("size", rawSize)) return;
3510 const auto size = MaybeAs<size_t>(rawSize);
3511 if (!size) {
3512 EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "`size` too large for platform.");
3513 return;
3515 Run<RPROC(BufferData_SizeOnly)>(target, *size, usage);
3518 void ClientWebGLContext::BufferData(
3519 GLenum target, const dom::Nullable<dom::ArrayBuffer>& maybeSrc,
3520 GLenum usage) {
3521 const FuncScope funcScope(*this, "bufferData");
3522 if (!ValidateNonNull("src", maybeSrc)) return;
3523 const auto& src = maybeSrc.Value();
3525 src.ProcessFixedData([&](const Span<const uint8_t>& aData) {
3526 Run<RPROC(BufferData)>(target, aData, usage);
3530 void ClientWebGLContext::BufferData(GLenum target,
3531 const dom::ArrayBufferView& src,
3532 GLenum usage, GLuint srcElemOffset,
3533 GLuint srcElemCountOverride) {
3534 const FuncScope funcScope(*this, "bufferData");
3535 size_t elemSize = SizeOfViewElem(src);
3536 src.ProcessFixedData([&](const Span<uint8_t>& aData) {
3537 const auto& range =
3538 ValidateArrayBufferView(aData, elemSize, srcElemOffset,
3539 srcElemCountOverride, LOCAL_GL_INVALID_VALUE);
3540 if (!range) {
3541 return;
3543 Run<RPROC(BufferData)>(target, *range, usage);
3547 ////
3549 void ClientWebGLContext::BufferSubData(GLenum target,
3550 WebGLsizeiptr dstByteOffset,
3551 const dom::ArrayBuffer& src) {
3552 const FuncScope funcScope(*this, "bufferSubData");
3553 src.ProcessFixedData([&](const Span<const uint8_t>& aData) {
3554 Run<RPROC(BufferSubData)>(target, dstByteOffset, aData,
3555 /* unsynchronized */ false);
3559 void ClientWebGLContext::BufferSubData(GLenum target,
3560 WebGLsizeiptr dstByteOffset,
3561 const dom::ArrayBufferView& src,
3562 GLuint srcElemOffset,
3563 GLuint srcElemCountOverride) {
3564 const FuncScope funcScope(*this, "bufferSubData");
3565 size_t elemSize = SizeOfViewElem(src);
3566 src.ProcessFixedData([&](const Span<uint8_t>& aData) {
3567 const auto& range =
3568 ValidateArrayBufferView(aData, elemSize, srcElemOffset,
3569 srcElemCountOverride, LOCAL_GL_INVALID_VALUE);
3570 if (!range) {
3571 return;
3573 Run<RPROC(BufferSubData)>(target, dstByteOffset, *range,
3574 /* unsynchronized */ false);
3578 void ClientWebGLContext::CopyBufferSubData(GLenum readTarget,
3579 GLenum writeTarget,
3580 GLintptr readOffset,
3581 GLintptr writeOffset,
3582 GLsizeiptr size) {
3583 const FuncScope funcScope(*this, "copyBufferSubData");
3584 if (!ValidateNonNegative("readOffset", readOffset) ||
3585 !ValidateNonNegative("writeOffset", writeOffset) ||
3586 !ValidateNonNegative("size", size)) {
3587 return;
3589 Run<RPROC(CopyBufferSubData)>(
3590 readTarget, writeTarget, static_cast<uint64_t>(readOffset),
3591 static_cast<uint64_t>(writeOffset), static_cast<uint64_t>(size));
3594 // -------------------------- Framebuffer Objects --------------------------
3596 void ClientWebGLContext::BindFramebuffer(const GLenum target,
3597 WebGLFramebufferJS* const fb) {
3598 const FuncScope funcScope(*this, "bindFramebuffer");
3599 if (IsContextLost()) return;
3600 if (fb && !fb->ValidateUsable(*this, "fb")) return;
3602 if (!IsFramebufferTarget(mIsWebGL2, target)) {
3603 EnqueueError_ArgEnum("target", target);
3604 return;
3607 // -
3609 auto& state = State();
3611 switch (target) {
3612 case LOCAL_GL_FRAMEBUFFER:
3613 state.mBoundDrawFb = fb;
3614 state.mBoundReadFb = fb;
3615 break;
3617 case LOCAL_GL_DRAW_FRAMEBUFFER:
3618 state.mBoundDrawFb = fb;
3619 break;
3620 case LOCAL_GL_READ_FRAMEBUFFER:
3621 state.mBoundReadFb = fb;
3622 break;
3624 default:
3625 MOZ_CRASH();
3628 // -
3630 if (fb) {
3631 fb->mHasBeenBound = true;
3634 Run<RPROC(BindFramebuffer)>(target, fb ? fb->mId : 0);
3637 // -
3639 void ClientWebGLContext::FramebufferTexture2D(GLenum target, GLenum attachSlot,
3640 GLenum bindImageTarget,
3641 WebGLTextureJS* const tex,
3642 GLint mipLevel) const {
3643 const FuncScope funcScope(*this, "framebufferTexture2D");
3644 if (IsContextLost()) return;
3646 const auto bindTexTarget = ImageToTexTarget(bindImageTarget);
3647 uint32_t zLayer = 0;
3648 switch (bindTexTarget) {
3649 case LOCAL_GL_TEXTURE_2D:
3650 break;
3651 case LOCAL_GL_TEXTURE_CUBE_MAP:
3652 zLayer = bindImageTarget - LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X;
3653 break;
3654 default:
3655 EnqueueError_ArgEnum("imageTarget", bindImageTarget);
3656 return;
3659 if (!mIsWebGL2 &&
3660 !IsExtensionEnabled(WebGLExtensionID::OES_fbo_render_mipmap)) {
3661 if (mipLevel != 0) {
3662 EnqueueError(LOCAL_GL_INVALID_VALUE,
3663 "mipLevel != 0 requires OES_fbo_render_mipmap.");
3664 return;
3668 FramebufferAttach(target, attachSlot, bindImageTarget, nullptr, tex,
3669 static_cast<uint32_t>(mipLevel), zLayer, 0);
3672 Maybe<webgl::ErrorInfo> CheckFramebufferAttach(const GLenum bindImageTarget,
3673 const GLenum curTexTarget,
3674 const uint32_t mipLevel,
3675 const uint32_t zLayerBase,
3676 const uint32_t zLayerCount,
3677 const webgl::Limits& limits) {
3678 if (!curTexTarget) {
3679 return Some(
3680 webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3681 "`tex` not yet bound. Call bindTexture first."});
3684 auto texTarget = curTexTarget;
3685 if (bindImageTarget) {
3686 // FramebufferTexture2D
3687 const auto bindTexTarget = ImageToTexTarget(bindImageTarget);
3688 if (curTexTarget != bindTexTarget) {
3689 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3690 "`tex` cannot be rebound to a new target."});
3693 switch (bindTexTarget) {
3694 case LOCAL_GL_TEXTURE_2D:
3695 case LOCAL_GL_TEXTURE_CUBE_MAP:
3696 break;
3697 default:
3698 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_ENUM,
3699 "`tex` must have been bound to target "
3700 "TEXTURE_2D or TEXTURE_CUBE_MAP."});
3702 texTarget = bindTexTarget;
3703 } else {
3704 // FramebufferTextureLayer/Multiview
3705 switch (curTexTarget) {
3706 case LOCAL_GL_TEXTURE_2D_ARRAY:
3707 case LOCAL_GL_TEXTURE_3D:
3708 break;
3709 default:
3710 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3711 "`tex` must have been bound to target "
3712 "TEXTURE_2D_ARRAY or TEXTURE_3D."});
3715 MOZ_ASSERT(texTarget);
3716 uint32_t maxSize;
3717 uint32_t maxZ;
3718 switch (texTarget) {
3719 case LOCAL_GL_TEXTURE_2D:
3720 maxSize = limits.maxTex2dSize;
3721 maxZ = 1;
3722 break;
3723 case LOCAL_GL_TEXTURE_CUBE_MAP:
3724 maxSize = limits.maxTexCubeSize;
3725 maxZ = 6;
3726 break;
3727 case LOCAL_GL_TEXTURE_2D_ARRAY:
3728 maxSize = limits.maxTex2dSize;
3729 maxZ = limits.maxTexArrayLayers;
3730 break;
3731 case LOCAL_GL_TEXTURE_3D:
3732 maxSize = limits.maxTex3dSize;
3733 maxZ = limits.maxTex3dSize;
3734 break;
3735 default:
3736 MOZ_CRASH();
3738 const auto maxMipLevel = FloorLog2(maxSize);
3739 if (mipLevel > maxMipLevel) {
3740 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE,
3741 "`mipLevel` too large for texture target."});
3743 const auto requiredZLayers = CheckedInt<uint32_t>(zLayerBase) + zLayerCount;
3744 if (!requiredZLayers.isValid() || requiredZLayers.value() > maxZ) {
3745 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE,
3746 "`zLayer` too large for texture target."});
3749 return {};
3752 void ClientWebGLContext::FramebufferAttach(
3753 const GLenum target, const GLenum attachSlot, const GLenum bindImageTarget,
3754 WebGLRenderbufferJS* const rb, WebGLTextureJS* const tex,
3755 const uint32_t mipLevel, const uint32_t zLayerBase,
3756 const uint32_t numViewLayers) const {
3757 if (rb && !rb->ValidateUsable(*this, "rb")) return;
3758 if (tex && !tex->ValidateUsable(*this, "tex")) return;
3759 const auto& state = State();
3760 const auto& limits = Limits();
3762 if (!IsFramebufferTarget(mIsWebGL2, target)) {
3763 EnqueueError_ArgEnum("target", target);
3764 return;
3766 auto fb = state.mBoundDrawFb;
3767 if (target == LOCAL_GL_READ_FRAMEBUFFER) {
3768 fb = state.mBoundReadFb;
3770 if (!fb) {
3771 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No framebuffer bound.");
3772 return;
3775 if (fb->mOpaque) {
3776 EnqueueError(
3777 LOCAL_GL_INVALID_OPERATION,
3778 "An opaque framebuffer's attachments cannot be inspected or changed.");
3779 return;
3782 // -
3783 // Multiview-specific validation skipped by Host.
3785 if (tex && numViewLayers) {
3786 if (tex->mTarget != LOCAL_GL_TEXTURE_2D_ARRAY) {
3787 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3788 "`tex` must have been bound to target TEXTURE_2D_ARRAY.");
3789 return;
3791 if (numViewLayers > limits.maxMultiviewLayers) {
3792 EnqueueError(LOCAL_GL_INVALID_VALUE,
3793 "`numViews` (%u) must be <= MAX_VIEWS (%u).", numViewLayers,
3794 limits.maxMultiviewLayers);
3795 return;
3799 // -
3801 webgl::ObjectId id = 0;
3802 if (tex) {
3803 auto zLayerCount = numViewLayers;
3804 if (!zLayerCount) {
3805 zLayerCount = 1;
3807 const auto err =
3808 CheckFramebufferAttach(bindImageTarget, tex->mTarget, mipLevel,
3809 zLayerBase, zLayerCount, limits);
3810 if (err) {
3811 EnqueueError(err->type, "%s", err->info.c_str());
3812 return;
3814 id = tex->mId;
3815 } else if (rb) {
3816 if (!rb->mHasBeenBound) {
3817 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3818 "`rb` has not yet been bound with BindRenderbuffer.");
3819 return;
3821 id = rb->mId;
3824 // Ready!
3825 // But DEPTH_STENCIL in webgl2 is actually two slots!
3827 const auto fnAttachTo = [&](const GLenum actualAttachSlot) {
3828 const auto slot = fb->GetAttachment(actualAttachSlot);
3829 if (!slot) {
3830 EnqueueError_ArgEnum("attachment", actualAttachSlot);
3831 return;
3834 slot->rb = rb;
3835 slot->tex = tex;
3837 Run<RPROC(FramebufferAttach)>(target, actualAttachSlot, bindImageTarget, id,
3838 mipLevel, zLayerBase, numViewLayers);
3841 if (mIsWebGL2 && attachSlot == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
3842 fnAttachTo(LOCAL_GL_DEPTH_ATTACHMENT);
3843 fnAttachTo(LOCAL_GL_STENCIL_ATTACHMENT);
3844 } else {
3845 fnAttachTo(attachSlot);
3848 if (bindImageTarget) {
3849 if (rb) {
3850 rb->mHasBeenBound = true;
3852 if (tex) {
3853 tex->mTarget = ImageToTexTarget(bindImageTarget);
3858 // -
3860 void ClientWebGLContext::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1,
3861 GLint srcY1, GLint dstX0, GLint dstY0,
3862 GLint dstX1, GLint dstY1,
3863 GLbitfield mask, GLenum filter) {
3864 Run<RPROC(BlitFramebuffer)>(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1,
3865 dstY1, mask, filter);
3867 AfterDrawCall();
3870 void ClientWebGLContext::InvalidateFramebuffer(
3871 GLenum target, const dom::Sequence<GLenum>& attachments,
3872 ErrorResult& unused) {
3873 Run<RPROC(InvalidateFramebuffer)>(target, Span{attachments});
3875 // Never invalidate the backbuffer, so never needs AfterDrawCall.
3878 void ClientWebGLContext::InvalidateSubFramebuffer(
3879 GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y,
3880 GLsizei width, GLsizei height, ErrorResult& unused) {
3881 Run<RPROC(InvalidateSubFramebuffer)>(target, Span{attachments}, x, y, width,
3882 height);
3884 // Never invalidate the backbuffer, so never needs AfterDrawCall.
3887 void ClientWebGLContext::ReadBuffer(GLenum mode) {
3888 Run<RPROC(ReadBuffer)>(mode);
3891 // ----------------------- Renderbuffer objects -----------------------
3893 void ClientWebGLContext::BindRenderbuffer(const GLenum target,
3894 WebGLRenderbufferJS* const rb) {
3895 const FuncScope funcScope(*this, "bindRenderbuffer");
3896 if (IsContextLost()) return;
3897 if (rb && !rb->ValidateUsable(*this, "rb")) return;
3898 auto& state = State();
3900 if (target != LOCAL_GL_RENDERBUFFER) {
3901 EnqueueError_ArgEnum("target", target);
3902 return;
3905 state.mBoundRb = rb;
3906 if (rb) {
3907 rb->mHasBeenBound = true;
3911 void ClientWebGLContext::RenderbufferStorageMultisample(GLenum target,
3912 GLsizei samples,
3913 GLenum internalFormat,
3914 GLsizei width,
3915 GLsizei height) const {
3916 const FuncScope funcScope(*this, "renderbufferStorageMultisample");
3917 if (IsContextLost()) return;
3919 if (target != LOCAL_GL_RENDERBUFFER) {
3920 EnqueueError_ArgEnum("target", target);
3921 return;
3924 const auto& state = State();
3926 const auto& rb = state.mBoundRb;
3927 if (!rb) {
3928 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No renderbuffer bound");
3929 return;
3932 if (!ValidateNonNegative("width", width) ||
3933 !ValidateNonNegative("height", height) ||
3934 !ValidateNonNegative("samples", samples)) {
3935 return;
3938 if (internalFormat == LOCAL_GL_DEPTH_STENCIL && samples > 0) {
3939 // While our backend supports it trivially, the spec forbids it.
3940 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3941 "WebGL 1's DEPTH_STENCIL format may not be multisampled. Use "
3942 "DEPTH24_STENCIL8 when `samples > 0`.");
3943 return;
3946 Run<RPROC(RenderbufferStorageMultisample)>(
3947 rb->mId, static_cast<uint32_t>(samples), internalFormat,
3948 static_cast<uint32_t>(width), static_cast<uint32_t>(height));
3951 // --------------------------- Texture objects ---------------------------
3953 void ClientWebGLContext::ActiveTexture(const GLenum texUnitEnum) {
3954 const FuncScope funcScope(*this, "activeTexture");
3955 if (IsContextLost()) return;
3957 if (texUnitEnum < LOCAL_GL_TEXTURE0) {
3958 EnqueueError(LOCAL_GL_INVALID_VALUE,
3959 "`texture` (0x%04x) must be >= TEXTURE0 (0x%04x).",
3960 texUnitEnum, LOCAL_GL_TEXTURE0);
3961 return;
3964 const auto texUnit = texUnitEnum - LOCAL_GL_TEXTURE0;
3966 auto& state = State();
3967 if (texUnit >= state.mTexUnits.size()) {
3968 EnqueueError(LOCAL_GL_INVALID_VALUE,
3969 "TEXTURE%u must be < MAX_COMBINED_TEXTURE_IMAGE_UNITS (%zu).",
3970 texUnit, state.mTexUnits.size());
3971 return;
3976 state.mActiveTexUnit = texUnit;
3977 Run<RPROC(ActiveTexture)>(texUnit);
3980 static bool IsTexTarget(const GLenum texTarget, const bool webgl2) {
3981 switch (texTarget) {
3982 case LOCAL_GL_TEXTURE_2D:
3983 case LOCAL_GL_TEXTURE_CUBE_MAP:
3984 return true;
3986 case LOCAL_GL_TEXTURE_2D_ARRAY:
3987 case LOCAL_GL_TEXTURE_3D:
3988 return webgl2;
3990 default:
3991 return false;
3995 void ClientWebGLContext::BindTexture(const GLenum texTarget,
3996 WebGLTextureJS* const tex) {
3997 const FuncScope funcScope(*this, "bindTexture");
3998 if (IsContextLost()) return;
3999 if (tex && !tex->ValidateUsable(*this, "tex")) return;
4001 if (!IsTexTarget(texTarget, mIsWebGL2)) {
4002 EnqueueError_ArgEnum("texTarget", texTarget);
4003 return;
4006 if (tex && tex->mTarget) {
4007 if (texTarget != tex->mTarget) {
4008 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4009 "Texture previously bound to %s cannot be bound now to %s.",
4010 EnumString(tex->mTarget).c_str(),
4011 EnumString(texTarget).c_str());
4012 return;
4016 auto& state = State();
4017 auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
4018 texUnit.texByTarget[texTarget] = tex;
4019 if (tex) {
4020 tex->mTarget = texTarget;
4023 Run<RPROC(BindTexture)>(texTarget, tex ? tex->mId : 0);
4026 void ClientWebGLContext::GenerateMipmap(GLenum texTarget) const {
4027 Run<RPROC(GenerateMipmap)>(texTarget);
4030 void ClientWebGLContext::GetTexParameter(
4031 JSContext* cx, GLenum texTarget, GLenum pname,
4032 JS::MutableHandle<JS::Value> retval) const {
4033 retval.set(JS::NullValue());
4034 const FuncScope funcScope(*this, "getTexParameter");
4035 if (IsContextLost()) return;
4036 auto& state = State();
4038 auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
4040 const auto& tex = Find(texUnit.texByTarget, texTarget, nullptr);
4041 if (!tex) {
4042 if (!IsTexTarget(texTarget, mIsWebGL2)) {
4043 EnqueueError_ArgEnum("texTarget", texTarget);
4044 } else {
4045 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No texture bound to %s[%u].",
4046 EnumString(texTarget).c_str(), state.mActiveTexUnit);
4048 return;
4051 const auto maybe = [&]() {
4052 const auto& inProcess = mNotLost->inProcess;
4053 if (inProcess) {
4054 return inProcess->GetTexParameter(tex->mId, pname);
4056 const auto& child = mNotLost->outOfProcess;
4057 child->FlushPendingCmds();
4058 Maybe<double> ret;
4059 if (!child->SendGetTexParameter(tex->mId, pname, &ret)) {
4060 ret.reset();
4062 return ret;
4063 }();
4065 if (maybe) {
4066 switch (pname) {
4067 case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
4068 retval.set(JS::BooleanValue(*maybe));
4069 break;
4071 default:
4072 retval.set(JS::NumberValue(*maybe));
4073 break;
4078 void ClientWebGLContext::TexParameterf(GLenum texTarget, GLenum pname,
4079 GLfloat param) {
4080 Run<RPROC(TexParameter_base)>(texTarget, pname, FloatOrInt(param));
4083 void ClientWebGLContext::TexParameteri(GLenum texTarget, GLenum pname,
4084 GLint param) {
4085 Run<RPROC(TexParameter_base)>(texTarget, pname, FloatOrInt(param));
4088 ////////////////////////////////////
4090 static GLenum JSTypeMatchUnpackTypeError(GLenum unpackType,
4091 js::Scalar::Type jsType) {
4092 bool matches = false;
4093 switch (unpackType) {
4094 case LOCAL_GL_BYTE:
4095 matches = (jsType == js::Scalar::Type::Int8);
4096 break;
4098 case LOCAL_GL_UNSIGNED_BYTE:
4099 matches = (jsType == js::Scalar::Type::Uint8 ||
4100 jsType == js::Scalar::Type::Uint8Clamped);
4101 break;
4103 case LOCAL_GL_SHORT:
4104 matches = (jsType == js::Scalar::Type::Int16);
4105 break;
4107 case LOCAL_GL_UNSIGNED_SHORT:
4108 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
4109 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
4110 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
4111 case LOCAL_GL_HALF_FLOAT:
4112 case LOCAL_GL_HALF_FLOAT_OES:
4113 matches = (jsType == js::Scalar::Type::Uint16);
4114 break;
4116 case LOCAL_GL_INT:
4117 matches = (jsType == js::Scalar::Type::Int32);
4118 break;
4120 case LOCAL_GL_UNSIGNED_INT:
4121 case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
4122 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
4123 case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
4124 case LOCAL_GL_UNSIGNED_INT_24_8:
4125 matches = (jsType == js::Scalar::Type::Uint32);
4126 break;
4128 case LOCAL_GL_FLOAT:
4129 matches = (jsType == js::Scalar::Type::Float32);
4130 break;
4132 case LOCAL_GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
4133 matches = false; // No valid jsType, but we allow uploads with null.
4134 break;
4136 default:
4137 return LOCAL_GL_INVALID_ENUM;
4139 if (!matches) return LOCAL_GL_INVALID_OPERATION;
4140 return 0;
4143 /////////////////////////////////////////////////
4145 static inline uvec2 CastUvec2(const ivec2& val) {
4146 return {static_cast<uint32_t>(val.x), static_cast<uint32_t>(val.y)};
4149 static inline uvec3 CastUvec3(const ivec3& val) {
4150 return {static_cast<uint32_t>(val.x), static_cast<uint32_t>(val.y),
4151 static_cast<uint32_t>(val.z)};
4154 template <typename T>
4155 Range<T> SubRange(const Range<T>& full, const size_t offset,
4156 const size_t length) {
4157 const auto newBegin = full.begin() + offset;
4158 return Range<T>{newBegin, newBegin + length};
4161 Maybe<Span<const uint8_t>> GetRangeFromData(const Span<uint8_t>& data,
4162 size_t bytesPerElem,
4163 GLuint elemOffset,
4164 GLuint elemCountOverride) {
4165 auto elemCount = data.size() / bytesPerElem;
4166 if (elemOffset > elemCount) return {};
4167 elemCount -= elemOffset;
4169 if (elemCountOverride) {
4170 if (elemCountOverride > elemCount) return {};
4171 elemCount = elemCountOverride;
4173 return Some(
4174 data.subspan(elemOffset * bytesPerElem, elemCount * bytesPerElem));
4177 // -
4179 static bool IsTexTargetForDims(const GLenum texTarget, const bool webgl2,
4180 const uint8_t funcDims) {
4181 if (!IsTexTarget(texTarget, webgl2)) return false;
4182 switch (texTarget) {
4183 case LOCAL_GL_TEXTURE_2D:
4184 case LOCAL_GL_TEXTURE_CUBE_MAP:
4185 return funcDims == 2;
4187 default:
4188 return funcDims == 3;
4192 void ClientWebGLContext::TexStorage(uint8_t funcDims, GLenum texTarget,
4193 GLsizei levels, GLenum internalFormat,
4194 const ivec3& size) const {
4195 const FuncScope funcScope(*this, "texStorage[23]D");
4196 if (IsContextLost()) return;
4197 if (!IsTexTargetForDims(texTarget, mIsWebGL2, funcDims)) {
4198 EnqueueError_ArgEnum("texTarget", texTarget);
4199 return;
4201 Run<RPROC(TexStorage)>(texTarget, static_cast<uint32_t>(levels),
4202 internalFormat, CastUvec3(size));
4205 // -
4207 void webgl::TexUnpackBlobDesc::Shrink(const webgl::PackingInfo& pi) {
4208 if (cpuData) {
4209 if (!size.x || !size.y || !size.z) return;
4211 const auto unpackRes = ExplicitUnpacking(pi, {});
4212 if (!unpackRes.isOk()) {
4213 return;
4215 const auto& unpack = unpackRes.inspect();
4217 const auto bytesUpperBound =
4218 CheckedInt<size_t>(unpack.metrics.bytesPerRowStride) *
4219 unpack.metrics.totalRows;
4220 if (bytesUpperBound.isValid()) {
4221 auto& span = *cpuData;
4222 span = span.subspan(0, std::min(span.size(), bytesUpperBound.value()));
4227 // -
4229 void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget,
4230 GLint level, GLenum respecFormat,
4231 const ivec3& offset,
4232 const Maybe<ivec3>& isize, GLint border,
4233 const webgl::PackingInfo& pi,
4234 const TexImageSource& src) const {
4235 const FuncScope funcScope(*this, "tex(Sub)Image[23]D");
4236 if (IsContextLost()) return;
4237 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
4238 EnqueueError_ArgEnum("imageTarget", imageTarget);
4239 return;
4241 if (border != 0) {
4242 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
4243 return;
4246 Maybe<uvec3> size;
4247 if (isize) {
4248 size = Some(CastUvec3(isize.value()));
4251 // -
4253 // -
4254 bool isDataUpload = false;
4255 auto desc = [&]() -> Maybe<webgl::TexUnpackBlobDesc> {
4256 if (src.mPboOffset) {
4257 isDataUpload = true;
4258 const auto offset = static_cast<uint64_t>(*src.mPboOffset);
4259 return Some(webgl::TexUnpackBlobDesc{imageTarget,
4260 size.value(),
4261 gfxAlphaType::NonPremult,
4263 Some(offset)});
4266 if (src.mView) {
4267 isDataUpload = true;
4268 const auto& view = *src.mView;
4269 const auto& jsType = view.Type();
4270 const auto err = JSTypeMatchUnpackTypeError(pi.type, jsType);
4271 switch (err) {
4272 case LOCAL_GL_INVALID_ENUM:
4273 EnqueueError_ArgEnum("unpackType", pi.type);
4274 return {};
4275 case LOCAL_GL_INVALID_OPERATION:
4276 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4277 "ArrayBufferView type %s not compatible with `type` %s.",
4278 name(jsType), EnumString(pi.type).c_str());
4279 return {};
4280 default:
4281 break;
4284 return view.ProcessData(
4285 [&](const Span<uint8_t>& aData,
4286 JS::AutoCheckCannotGC&& nogc) -> Maybe<webgl::TexUnpackBlobDesc> {
4287 const auto range = GetRangeFromData(aData, SizeOfViewElem(view),
4288 src.mViewElemOffset,
4289 src.mViewElemLengthOverride);
4290 if (!range) {
4291 nogc.reset();
4292 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`source` too small.");
4293 return {};
4295 return Some(webgl::TexUnpackBlobDesc{imageTarget,
4296 size.value(),
4297 gfxAlphaType::NonPremult,
4298 Some(*range),
4299 {}});
4303 if (src.mImageBitmap) {
4304 return webgl::FromImageBitmap(imageTarget, size, *(src.mImageBitmap),
4305 src.mOut_error);
4308 if (src.mImageData) {
4309 const auto& imageData = *src.mImageData;
4310 dom::Uint8ClampedArray scopedArr;
4311 MOZ_RELEASE_ASSERT(scopedArr.Init(imageData.GetDataObject()));
4313 return scopedArr.ProcessData(
4314 [&](const Span<uint8_t>& aData,
4315 JS::AutoCheckCannotGC&& nogc) -> Maybe<webgl::TexUnpackBlobDesc> {
4316 const auto dataSize = aData.Length();
4317 const auto data = aData.Elements();
4318 if (dataSize == 0) {
4319 nogc.reset(); // aData will not be used.
4320 EnqueueError(
4321 LOCAL_GL_INVALID_VALUE,
4322 "ImageData.data.buffer is Detached. (Maybe you Transfered "
4323 "it to a Worker?");
4324 return {};
4327 // -
4329 const gfx::IntSize imageSize(imageData.Width(), imageData.Height());
4330 const auto sizeFromDims =
4331 CheckedInt<size_t>(imageSize.width) * imageSize.height * 4;
4332 MOZ_RELEASE_ASSERT(sizeFromDims.isValid() &&
4333 sizeFromDims.value() == dataSize);
4335 const RefPtr<gfx::DataSourceSurface> surf =
4336 gfx::Factory::CreateWrappingDataSourceSurface(
4337 data, imageSize.width * 4, imageSize,
4338 gfx::SurfaceFormat::R8G8B8A8);
4339 MOZ_ASSERT(surf);
4341 // -
4343 const auto imageUSize = *uvec2::FromSize(imageSize);
4344 const auto concreteSize =
4345 size.valueOr(uvec3{imageUSize.x, imageUSize.y, 1});
4347 // WhatWG "HTML Living Standard" (30 October 2015):
4348 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be
4349 // returned as non-premultiplied alpha values."
4350 auto result =
4351 Some(webgl::TexUnpackBlobDesc{imageTarget,
4352 concreteSize,
4353 gfxAlphaType::NonPremult,
4356 Some(imageUSize),
4357 nullptr,
4359 surf});
4360 nogc.reset(); // Done with aData
4361 return result;
4365 if (src.mOffscreenCanvas) {
4366 return webgl::FromOffscreenCanvas(
4367 *this, imageTarget, size, *(src.mOffscreenCanvas), src.mOut_error);
4370 if (src.mVideoFrame) {
4371 return webgl::FromVideoFrame(*this, imageTarget, size, *(src.mVideoFrame),
4372 src.mOut_error);
4375 if (src.mDomElem) {
4376 return webgl::FromDomElem(*this, imageTarget, size, *(src.mDomElem),
4377 src.mOut_error);
4380 return Some(webgl::TexUnpackBlobDesc{
4381 imageTarget, size.value(), gfxAlphaType::NonPremult, {}, {}});
4382 }();
4383 if (!desc) {
4384 return;
4387 // -
4389 const auto& rawUnpacking = State().mPixelUnpackState;
4391 auto defaultSubrectState = webgl::PixelPackingState{};
4392 defaultSubrectState.alignmentInTypeElems =
4393 rawUnpacking.alignmentInTypeElems;
4394 const bool isSubrect = (rawUnpacking != defaultSubrectState);
4395 if (isDataUpload && isSubrect) {
4396 if (rawUnpacking.flipY || rawUnpacking.premultiplyAlpha) {
4397 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4398 "Non-DOM-Element uploads with alpha-premult"
4399 " or y-flip do not support subrect selection.");
4400 return;
4404 desc->unpacking = rawUnpacking;
4406 if (desc->structuredSrcSize) {
4407 // WebGL 2 spec:
4408 // ### 5.35 Pixel store parameters for uploads from TexImageSource
4409 // UNPACK_ALIGNMENT and UNPACK_ROW_LENGTH are ignored.
4410 const auto& elemSize = *desc->structuredSrcSize;
4411 desc->unpacking.alignmentInTypeElems = 1;
4412 desc->unpacking.rowLength = elemSize.x;
4414 if (!desc->unpacking.rowLength) {
4415 desc->unpacking.rowLength = desc->size.x;
4417 if (!desc->unpacking.imageHeight) {
4418 desc->unpacking.imageHeight = desc->size.y;
4421 // -
4423 // -
4425 mozilla::ipc::Shmem* pShmem = nullptr;
4426 // Image to release after WebGLContext::TexImage().
4427 RefPtr<layers::Image> keepAliveImage;
4428 RefPtr<gfx::DataSourceSurface> keepAliveSurf;
4430 if (desc->sd) {
4431 const auto& sd = *(desc->sd);
4432 const auto sdType = sd.type();
4433 const auto& contextInfo = mNotLost->info;
4435 // TODO (Bug 754256): Figure out the source colorSpace.
4436 const auto& webgl = this;
4437 dom::PredefinedColorSpace srcColorSpace = dom::PredefinedColorSpace::Srgb;
4438 dom::PredefinedColorSpace dstColorSpace =
4439 webgl->mUnpackColorSpace ? *webgl->mUnpackColorSpace
4440 : dom::PredefinedColorSpace::Srgb;
4441 bool sameColorSpace = (srcColorSpace == dstColorSpace);
4443 const auto fallbackReason = [&]() -> Maybe<std::string> {
4444 auto fallbackReason = BlitPreventReason(
4445 level, offset, respecFormat, pi, *desc,
4446 contextInfo.optionalRenderableFormatBits, sameColorSpace);
4447 if (fallbackReason) return fallbackReason;
4449 const bool canUploadViaSd = contextInfo.uploadableSdTypes[sdType];
4450 if (!canUploadViaSd) {
4451 const nsPrintfCString msg(
4452 "Fast uploads for resource type %i not implemented.", int(sdType));
4453 return Some(ToString(msg));
4456 switch (sdType) {
4457 default:
4458 break;
4459 case layers::SurfaceDescriptor::TSurfaceDescriptorBuffer: {
4460 const auto& sdb = sd.get_SurfaceDescriptorBuffer();
4461 const auto& data = sdb.data();
4462 if (data.type() == layers::MemoryOrShmem::TShmem) {
4463 pShmem = &data.get_Shmem();
4464 } else {
4465 return Some(
4466 std::string{"SurfaceDescriptorBuffer data is not Shmem."});
4468 } break;
4469 case layers::SurfaceDescriptor::TSurfaceDescriptorD3D10: {
4470 const auto& sdD3D = sd.get_SurfaceDescriptorD3D10();
4471 const auto& inProcess = mNotLost->inProcess;
4472 MOZ_ASSERT(desc->image);
4473 keepAliveImage = desc->image;
4475 if (sdD3D.gpuProcessTextureId().isSome() && inProcess) {
4476 return Some(
4477 std::string{"gpuProcessTextureId works only in GPU process."});
4479 } break;
4480 case layers::SurfaceDescriptor::TSurfaceDescriptorGPUVideo: {
4481 const auto& inProcess = mNotLost->inProcess;
4482 MOZ_ASSERT(desc->image);
4483 keepAliveImage = desc->image;
4484 if (inProcess) {
4485 return Some(std::string{
4486 "SurfaceDescriptorGPUVideo works only in GPU process."});
4488 const auto& sdv = sd.get_SurfaceDescriptorGPUVideo();
4489 if (sdv.type() != layers::SurfaceDescriptorGPUVideo::
4490 TSurfaceDescriptorRemoteDecoder) {
4491 return Some(std::string{
4492 "SurfaceDescriptorGPUVideo does not contain RemoteDecoder."});
4494 const auto& sdrd = sdv.get_SurfaceDescriptorRemoteDecoder();
4495 const auto& subdesc = sdrd.subdesc();
4496 if (subdesc.type() !=
4497 layers::RemoteDecoderVideoSubDescriptor::Tnull_t) {
4498 return Some(
4499 std::string{"SurfaceDescriptorGPUVideo does not contain "
4500 "RemoteDecoder null subdesc."});
4502 } break;
4503 case layers::SurfaceDescriptor::TSurfaceDescriptorExternalImage: {
4504 const auto& inProcess = mNotLost->inProcess;
4505 MOZ_ASSERT(desc->dataSurf);
4506 keepAliveSurf = desc->dataSurf;
4507 if (inProcess) {
4508 return Some(std::string{
4509 "SurfaceDescriptorExternalImage works only in GPU process."});
4511 } break;
4514 switch (respecFormat) {
4515 case LOCAL_GL_SRGB:
4516 case LOCAL_GL_SRGB8:
4517 case LOCAL_GL_SRGB_ALPHA:
4518 case LOCAL_GL_SRGB8_ALPHA8: {
4519 const nsPrintfCString msg(
4520 "srgb-encoded formats (like %s) are not supported.",
4521 EnumString(respecFormat).c_str());
4522 return Some(ToString(msg));
4526 if (StaticPrefs::webgl_disable_DOM_blit_uploads()) {
4527 return Some(std::string{"DOM blit uploads are disabled."});
4529 return {};
4530 }();
4532 if (fallbackReason) {
4533 EnqueuePerfWarning("Missed GPU-copy fast-path: %s",
4534 fallbackReason->c_str());
4536 const auto& image = desc->image;
4537 if (image) {
4538 const RefPtr<gfx::SourceSurface> surf = image->GetAsSourceSurface();
4539 if (surf) {
4540 // WARNING: OSX can lose our MakeCurrent here.
4541 desc->dataSurf = surf->GetDataSurface();
4544 if (!desc->dataSurf) {
4545 EnqueueError(LOCAL_GL_OUT_OF_MEMORY,
4546 "Failed to retrieve source bytes for CPU upload.");
4547 return;
4549 desc->sd = Nothing();
4552 desc->image = nullptr;
4553 if (desc->sd) {
4554 desc->dataSurf = nullptr;
4557 desc->Shrink(pi);
4559 // -
4561 std::shared_ptr<webgl::RaiiShmem> tempShmem;
4563 const bool doInlineUpload = !desc->sd;
4564 // Why always de-inline SDs here?
4565 // 1. This way we always send SDs down the same handling path, which
4566 // should keep things from breaking if things flip between paths because of
4567 // what we get handed by SurfaceFromElement etc.
4568 // 2. We don't actually always grab strong-refs to the resources in the SDs,
4569 // so we should try to use them sooner rather than later. Yes we should fix
4570 // this, but for now let's give the SDs the best chance of lucking out, eh?
4571 // :)
4572 // 3. It means we don't need to write QueueParamTraits<SurfaceDescriptor>.
4573 if (doInlineUpload) {
4574 // We definitely want e.g. TexImage(PBO) here.
4575 Run<RPROC(TexImage)>(static_cast<uint32_t>(level), respecFormat,
4576 CastUvec3(offset), pi, std::move(*desc));
4577 } else {
4578 // We can't handle shmems like SurfaceDescriptorBuffer inline, so use ipdl.
4579 const auto& inProcess = mNotLost->inProcess;
4580 if (inProcess) {
4581 return inProcess->TexImage(static_cast<uint32_t>(level), respecFormat,
4582 CastUvec3(offset), pi, *desc);
4584 const auto& child = mNotLost->outOfProcess;
4585 child->FlushPendingCmds();
4587 // The shmem we're handling was only shared from RDD to Content, and
4588 // immediately on Content receiving it, it was closed! RIP
4589 // Eventually we'll be able to make shmems that can traverse multiple
4590 // endpoints, but for now we need to make a new Content->WebGLParent shmem
4591 // and memcpy into it. We don't use `desc` elsewhere, so just replace the
4592 // Shmem buried within it with one that's valid for WebGLChild->Parent
4593 // transport.
4594 if (pShmem) {
4595 MOZ_ASSERT(desc->sd);
4596 const auto srcBytes = ShmemRange<uint8_t>(*pShmem);
4597 tempShmem = std::make_shared<webgl::RaiiShmem>();
4599 // We need Unsafe because we want to dictate when to destroy it from the
4600 // client side.
4601 *tempShmem = webgl::RaiiShmem::AllocUnsafe(child, srcBytes.length());
4602 if (!*tempShmem) {
4603 NS_WARNING("AllocShmem failed in TexImage");
4604 return;
4606 const auto dstBytes = ShmemRange<uint8_t>(tempShmem->Shmem());
4607 Memcpy(&dstBytes, srcBytes.begin());
4609 *pShmem = tempShmem->Shmem();
4610 // Not Extract, because we free tempShmem manually below, after the remote
4611 // side has finished executing SendTexImage.
4614 (void)child->SendTexImage(static_cast<uint32_t>(level), respecFormat,
4615 CastUvec3(offset), pi, std::move(*desc));
4617 if (tempShmem || keepAliveImage || keepAliveSurf) {
4618 const auto eventTarget = GetCurrentSerialEventTarget();
4619 MOZ_ASSERT(eventTarget);
4620 child->SendPing()->Then(eventTarget, __func__,
4621 [tempShmem, keepAliveImage, keepAliveSurf]() {
4622 // Cleans up when (our copy of)
4623 // sendableShmem/image goes out of scope.
4629 // -
4631 void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims,
4632 GLenum imageTarget, GLint level,
4633 GLenum format, const ivec3& offset,
4634 const ivec3& isize, GLint border,
4635 const TexImageSource& src,
4636 GLsizei pboImageSize) const {
4637 const FuncScope funcScope(*this, "compressedTex(Sub)Image[23]D");
4638 if (IsContextLost()) return;
4639 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
4640 EnqueueError_ArgEnum("imageTarget", imageTarget);
4641 return;
4643 if (border != 0) {
4644 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
4645 return;
4648 if (src.mView) {
4649 src.mView->ProcessData([&](const Span<uint8_t>& aData,
4650 JS::AutoCheckCannotGC&& aNoGC) {
4651 const auto range =
4652 GetRangeFromData(aData, SizeOfViewElem(*src.mView),
4653 src.mViewElemOffset, src.mViewElemLengthOverride);
4654 if (!range) {
4655 aNoGC.reset();
4656 EnqueueError(LOCAL_GL_INVALID_VALUE, "`source` too small.");
4657 return;
4660 // We don't need to shrink `range` because valid calls require
4661 // `range` to match requirements exactly.
4663 RunWithGCData<RPROC(CompressedTexImage)>(
4664 std::move(aNoGC), sub, imageTarget, static_cast<uint32_t>(level),
4665 format, CastUvec3(offset), CastUvec3(isize), *range,
4666 static_cast<uint32_t>(pboImageSize), Maybe<uint64_t>());
4667 return;
4669 return;
4671 if (!src.mPboOffset) {
4672 MOZ_CRASH("impossible");
4674 if (!ValidateNonNegative("offset", *src.mPboOffset)) {
4675 return;
4678 Run<RPROC(CompressedTexImage)>(
4679 sub, imageTarget, static_cast<uint32_t>(level), format, CastUvec3(offset),
4680 CastUvec3(isize), Span<const uint8_t>{},
4681 static_cast<uint32_t>(pboImageSize), Some(*src.mPboOffset));
4684 void ClientWebGLContext::CopyTexImage(uint8_t funcDims, GLenum imageTarget,
4685 GLint level, GLenum respecFormat,
4686 const ivec3& dstOffset,
4687 const ivec2& srcOffset, const ivec2& size,
4688 GLint border) const {
4689 const FuncScope funcScope(*this, "copy(Sub)Image[23]D");
4690 if (IsContextLost()) return;
4691 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
4692 EnqueueError_ArgEnum("imageTarget", imageTarget);
4693 return;
4695 if (border != 0) {
4696 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
4697 return;
4699 Run<RPROC(CopyTexImage)>(imageTarget, static_cast<uint32_t>(level),
4700 respecFormat, CastUvec3(dstOffset), srcOffset,
4701 CastUvec2(size));
4704 // ------------------- Programs and shaders --------------------------------
4706 void ClientWebGLContext::UseProgram(WebGLProgramJS* const prog) {
4707 const FuncScope funcScope(*this, "useProgram");
4708 if (IsContextLost()) return;
4709 if (prog && !prog->ValidateUsable(*this, "prog")) return;
4711 auto& state = State();
4713 if (state.mTfActiveAndNotPaused) {
4714 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4715 "Transform feedback is active and not paused.");
4716 return;
4719 if (prog) {
4720 const auto& res = GetLinkResult(*prog);
4721 if (!res.success) {
4722 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4723 "Program must be linked successfully.");
4724 return;
4728 // -
4730 state.mCurrentProgram = prog;
4731 state.mProgramKeepAlive = prog ? prog->mKeepAliveWeak.lock() : nullptr;
4732 state.mActiveLinkResult = prog ? prog->mResult : nullptr;
4734 Run<RPROC(UseProgram)>(prog ? prog->mId : 0);
4737 void ClientWebGLContext::ValidateProgram(WebGLProgramJS& prog) const {
4738 const FuncScope funcScope(*this, "validateProgram");
4739 if (IsContextLost()) return;
4740 if (!prog.ValidateUsable(*this, "prog")) return;
4742 prog.mLastValidate = [&]() {
4743 const auto& inProcess = mNotLost->inProcess;
4744 if (inProcess) {
4745 return inProcess->ValidateProgram(prog.mId);
4747 const auto& child = mNotLost->outOfProcess;
4748 child->FlushPendingCmds();
4749 bool ret = {};
4750 if (!child->SendValidateProgram(prog.mId, &ret)) {
4751 ret = {};
4753 return ret;
4754 }();
4757 // ------------------------ Uniforms and attributes ------------------------
4759 Maybe<double> ClientWebGLContext::GetVertexAttribPriv(const GLuint index,
4760 const GLenum pname) {
4761 const auto& inProcess = mNotLost->inProcess;
4762 if (inProcess) {
4763 return inProcess->GetVertexAttrib(index, pname);
4765 const auto& child = mNotLost->outOfProcess;
4766 child->FlushPendingCmds();
4767 Maybe<double> ret;
4768 if (!child->SendGetVertexAttrib(index, pname, &ret)) {
4769 ret.reset();
4771 return ret;
4774 void ClientWebGLContext::GetVertexAttrib(JSContext* cx, GLuint index,
4775 GLenum pname,
4776 JS::MutableHandle<JS::Value> retval,
4777 ErrorResult& rv) {
4778 retval.set(JS::NullValue());
4779 const FuncScope funcScope(*this, "getVertexAttrib");
4780 if (IsContextLost()) return;
4781 const auto& state = State();
4783 const auto& genericAttribs = state.mGenericVertexAttribs;
4784 if (index >= genericAttribs.size()) {
4785 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` (%u) >= MAX_VERTEX_ATTRIBS",
4786 index);
4787 return;
4790 switch (pname) {
4791 case LOCAL_GL_CURRENT_VERTEX_ATTRIB: {
4792 const auto& attrib = genericAttribs[index];
4793 switch (attrib.type) {
4794 case webgl::AttribBaseType::Float: {
4795 const auto ptr = reinterpret_cast<const float*>(attrib.data.data());
4796 retval.setObjectOrNull(
4797 dom::Float32Array::Create(cx, this, Span(ptr, 4), rv));
4798 break;
4800 case webgl::AttribBaseType::Int: {
4801 const auto ptr = reinterpret_cast<const int32_t*>(attrib.data.data());
4802 retval.setObjectOrNull(
4803 dom::Int32Array::Create(cx, this, Span(ptr, 4), rv));
4804 break;
4806 case webgl::AttribBaseType::Uint: {
4807 const auto ptr =
4808 reinterpret_cast<const uint32_t*>(attrib.data.data());
4809 retval.setObjectOrNull(
4810 dom::Uint32Array::Create(cx, this, Span(ptr, 4), rv));
4811 break;
4813 case webgl::AttribBaseType::Boolean:
4814 MOZ_CRASH("impossible");
4817 return;
4820 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: {
4821 const auto& buffers = state.mBoundVao->mAttribBuffers;
4822 const auto& buffer = buffers[index];
4823 (void)ToJSValueOrNull(cx, buffer, retval);
4824 return;
4827 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER:
4828 // Disallowed from JS, but allowed in Host.
4829 EnqueueError_ArgEnum("pname", pname);
4830 return;
4832 default:
4833 break;
4836 const auto maybe = GetVertexAttribPriv(index, pname);
4837 if (maybe) {
4838 switch (pname) {
4839 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED:
4840 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
4841 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_INTEGER:
4842 retval.set(JS::BooleanValue(*maybe));
4843 break;
4845 default:
4846 retval.set(JS::NumberValue(*maybe));
4847 break;
4852 void ClientWebGLContext::UniformData(const GLenum funcElemType,
4853 const WebGLUniformLocationJS* const loc,
4854 bool transpose,
4855 const Range<const uint8_t>& bytes,
4856 JS::AutoCheckCannotGC&& nogc,
4857 GLuint elemOffset,
4858 GLuint elemCountOverride) const {
4859 // FuncScope::~FuncScope() can GC in a failure case, so all `return`
4860 // statements need to `nogc.reset()` up until the `nogc` is consumed by
4861 // `RunWithGCData`.
4862 const FuncScope funcScope(*this, "uniform setter");
4863 if (IsContextLost()) {
4864 nogc.reset();
4865 return;
4868 const auto& activeLinkResult = GetActiveLinkResult();
4869 if (!activeLinkResult) {
4870 nogc.reset();
4871 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No active linked Program.");
4872 return;
4875 // -
4877 auto availCount = bytes.length() / sizeof(float);
4878 if (elemOffset > availCount) {
4879 nogc.reset();
4880 EnqueueError(LOCAL_GL_INVALID_VALUE, "`elemOffset` too large for `data`.");
4881 return;
4883 availCount -= elemOffset;
4884 if (elemCountOverride) {
4885 if (elemCountOverride > availCount) {
4886 nogc.reset();
4887 EnqueueError(LOCAL_GL_INVALID_VALUE,
4888 "`elemCountOverride` too large for `data`.");
4889 return;
4891 availCount = elemCountOverride;
4894 // -
4896 const auto channels = ElemTypeComponents(funcElemType);
4897 if (!availCount || availCount % channels != 0) {
4898 nogc.reset();
4899 EnqueueError(LOCAL_GL_INVALID_VALUE,
4900 "`values` length (%u) must be a positive "
4901 "integer multiple of size of %s.",
4902 availCount, EnumString(funcElemType).c_str());
4903 return;
4906 // -
4908 uint32_t locId = -1;
4909 if (MOZ_LIKELY(loc)) {
4910 locId = loc->mLocation;
4911 if (!loc->ValidateUsable(*this, "location")) {
4912 nogc.reset();
4913 return;
4916 // -
4918 const auto& reqLinkInfo = loc->mParent.lock();
4919 if (reqLinkInfo.get() != activeLinkResult) {
4920 nogc.reset();
4921 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4922 "UniformLocation is not from the current active Program.");
4923 return;
4926 // -
4928 bool funcMatchesLocation = false;
4929 for (const auto allowed : loc->mValidUploadElemTypes) {
4930 funcMatchesLocation |= (funcElemType == allowed);
4932 if (MOZ_UNLIKELY(!funcMatchesLocation)) {
4933 std::string validSetters;
4934 for (const auto allowed : loc->mValidUploadElemTypes) {
4935 validSetters += EnumString(allowed);
4936 validSetters += '/';
4938 validSetters.pop_back(); // Cheekily discard the extra trailing '/'.
4940 nogc.reset();
4941 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4942 "Uniform's `type` requires uniform setter of type %s.",
4943 validSetters.c_str());
4944 return;
4948 // -
4950 const auto begin =
4951 reinterpret_cast<const webgl::UniformDataVal*>(bytes.begin().get()) +
4952 elemOffset;
4953 const auto range = Span{begin, availCount};
4954 RunWithGCData<RPROC(UniformData)>(std::move(nogc), locId, transpose, range);
4957 // -
4959 void ClientWebGLContext::BindVertexArray(WebGLVertexArrayJS* const vao) {
4960 const FuncScope funcScope(*this, "bindVertexArray");
4961 if (IsContextLost()) return;
4962 if (vao && !vao->ValidateUsable(*this, "vao")) return;
4963 auto& state = State();
4965 if (vao) {
4966 vao->mHasBeenBound = true;
4967 state.mBoundVao = vao;
4968 } else {
4969 state.mBoundVao = state.mDefaultVao;
4972 Run<RPROC(BindVertexArray)>(vao ? vao->mId : 0);
4975 void ClientWebGLContext::EnableVertexAttribArray(GLuint index) {
4976 Run<RPROC(EnableVertexAttribArray)>(index);
4979 void ClientWebGLContext::DisableVertexAttribArray(GLuint index) {
4980 Run<RPROC(DisableVertexAttribArray)>(index);
4983 WebGLsizeiptr ClientWebGLContext::GetVertexAttribOffset(GLuint index,
4984 GLenum pname) {
4985 const FuncScope funcScope(*this, "getVertexAttribOffset");
4986 if (IsContextLost()) return 0;
4988 if (pname != LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER) {
4989 EnqueueError_ArgEnum("pname", pname);
4990 return 0;
4993 const auto maybe = GetVertexAttribPriv(index, pname);
4994 if (!maybe) return 0;
4995 return *maybe;
4998 void ClientWebGLContext::VertexAttrib4Tv(GLuint index, webgl::AttribBaseType t,
4999 const Range<const uint8_t>& src) {
5000 const FuncScope funcScope(*this, "vertexAttrib[1234]u?[fi]{v}");
5001 if (IsContextLost()) return;
5002 auto& state = State();
5004 if (src.length() / sizeof(float) < 4) {
5005 EnqueueError(LOCAL_GL_INVALID_VALUE, "Array must have >=4 elements.");
5006 return;
5009 auto& list = state.mGenericVertexAttribs;
5010 if (index >= list.size()) {
5011 EnqueueError(LOCAL_GL_INVALID_VALUE,
5012 "`index` must be < MAX_VERTEX_ATTRIBS.");
5013 return;
5016 auto& attrib = list[index];
5017 attrib.type = t;
5018 memcpy(attrib.data.data(), src.begin().get(), attrib.data.size());
5020 Run<RPROC(VertexAttrib4T)>(index, attrib);
5023 // -
5025 void ClientWebGLContext::VertexAttribDivisor(GLuint index, GLuint divisor) {
5026 Run<RPROC(VertexAttribDivisor)>(index, divisor);
5029 // -
5031 void ClientWebGLContext::VertexAttribPointerImpl(bool isFuncInt, GLuint index,
5032 GLint rawChannels, GLenum type,
5033 bool normalized,
5034 GLsizei rawByteStrideOrZero,
5035 WebGLintptr rawByteOffset) {
5036 const FuncScope funcScope(*this, "vertexAttribI?Pointer");
5037 if (IsContextLost()) return;
5038 auto& state = State();
5040 const auto channels = MaybeAs<uint8_t>(rawChannels);
5041 if (!channels) {
5042 EnqueueError(LOCAL_GL_INVALID_VALUE,
5043 "Channel count `size` must be within [1,4].");
5044 return;
5047 const auto byteStrideOrZero = MaybeAs<uint8_t>(rawByteStrideOrZero);
5048 if (!byteStrideOrZero) {
5049 EnqueueError(LOCAL_GL_INVALID_VALUE, "`stride` must be within [0,255].");
5050 return;
5053 if (!ValidateNonNegative("byteOffset", rawByteOffset)) return;
5054 const auto byteOffset = static_cast<uint64_t>(rawByteOffset);
5056 // -
5058 const webgl::VertAttribPointerDesc desc{
5059 isFuncInt, *channels, normalized, *byteStrideOrZero, type, byteOffset};
5061 const auto res = CheckVertexAttribPointer(mIsWebGL2, desc);
5062 if (res.isErr()) {
5063 const auto& err = res.inspectErr();
5064 EnqueueError(err.type, "%s", err.info.c_str());
5065 return;
5068 auto& list = state.mBoundVao->mAttribBuffers;
5069 if (index >= list.size()) {
5070 EnqueueError(LOCAL_GL_INVALID_VALUE,
5071 "`index` (%u) must be < MAX_VERTEX_ATTRIBS.", index);
5072 return;
5075 const auto buffer = state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER];
5076 if (!buffer && byteOffset) {
5077 return EnqueueError(LOCAL_GL_INVALID_OPERATION,
5078 "If ARRAY_BUFFER is null, byteOffset must be zero.");
5081 Run<RPROC(VertexAttribPointer)>(index, desc);
5083 list[index] = buffer;
5086 // -------------------------------- Drawing -------------------------------
5088 void ClientWebGLContext::DrawArraysInstanced(GLenum mode, GLint first,
5089 GLsizei count, GLsizei primcount) {
5090 Run<RPROC(DrawArraysInstanced)>(mode, first, count, primcount);
5091 AfterDrawCall();
5094 void ClientWebGLContext::DrawElementsInstanced(GLenum mode, GLsizei count,
5095 GLenum type, WebGLintptr offset,
5096 GLsizei primcount) {
5097 Run<RPROC(DrawElementsInstanced)>(mode, count, type, offset, primcount);
5098 AfterDrawCall();
5101 // ------------------------------ Readback -------------------------------
5102 void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
5103 GLsizei height, GLenum format, GLenum type,
5104 WebGLsizeiptr offset,
5105 dom::CallerType aCallerType,
5106 ErrorResult& out_error) const {
5107 const FuncScope funcScope(*this, "readPixels");
5108 if (!ReadPixels_SharedPrecheck(aCallerType, out_error)) return;
5109 const auto& state = State();
5110 if (!ValidateNonNegative("width", width)) return;
5111 if (!ValidateNonNegative("height", height)) return;
5112 if (!ValidateNonNegative("offset", offset)) return;
5114 const auto desc = webgl::ReadPixelsDesc{{x, y},
5115 *uvec2::From(width, height),
5116 {format, type},
5117 state.mPixelPackState};
5118 Run<RPROC(ReadPixelsPbo)>(desc, static_cast<uint64_t>(offset));
5121 void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
5122 GLsizei height, GLenum format, GLenum type,
5123 const dom::ArrayBufferView& dstData,
5124 GLuint dstElemOffset,
5125 dom::CallerType aCallerType,
5126 ErrorResult& out_error) const {
5127 const FuncScope funcScope(*this, "readPixels");
5128 if (!ReadPixels_SharedPrecheck(aCallerType, out_error)) return;
5129 const auto& state = State();
5130 if (!ValidateNonNegative("width", width)) return;
5131 if (!ValidateNonNegative("height", height)) return;
5133 ////
5135 js::Scalar::Type reqScalarType;
5136 if (!GetJSScalarFromGLType(type, &reqScalarType)) {
5137 nsCString name;
5138 WebGLContext::EnumName(type, &name);
5139 EnqueueError(LOCAL_GL_INVALID_ENUM, "type: invalid enum value %s",
5140 name.BeginReading());
5141 return;
5144 auto viewElemType = dstData.Type();
5145 if (viewElemType == js::Scalar::Uint8Clamped) {
5146 viewElemType = js::Scalar::Uint8;
5148 if (viewElemType != reqScalarType) {
5149 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5150 "`pixels` type does not match `type`.");
5151 return;
5154 size_t elemSize = SizeOfViewElem(dstData);
5155 dstData.ProcessFixedData([&](const Span<uint8_t>& aData) {
5156 const auto& range = ValidateArrayBufferView(aData, elemSize, dstElemOffset,
5157 0, LOCAL_GL_INVALID_VALUE);
5158 if (!range) {
5159 return;
5162 const auto desc = webgl::ReadPixelsDesc{{x, y},
5163 *uvec2::From(width, height),
5164 {format, type},
5165 state.mPixelPackState};
5166 (void)DoReadPixels(desc, *range);
5170 bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
5171 const Span<uint8_t> dest) const {
5172 const auto notLost =
5173 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
5174 if (!notLost) return false;
5175 const auto& inProcess = notLost->inProcess;
5176 if (inProcess) {
5177 inProcess->ReadPixelsInto(desc, dest);
5178 return true;
5180 const auto& child = notLost->outOfProcess;
5181 child->FlushPendingCmds();
5182 webgl::ReadPixelsResultIpc res = {};
5183 if (!child->SendReadPixels(desc, dest.size(), &res)) {
5184 res = {};
5186 if (!res.byteStride || !res.shmem) return false;
5187 const auto& byteStride = res.byteStride;
5188 const auto& subrect = res.subrect;
5189 const webgl::RaiiShmem shmem{child, res.shmem.ref()};
5190 if (!shmem) {
5191 EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "Failed to map in back buffer.");
5192 return false;
5195 const auto& shmemBytes = Span{shmem.ByteRange()};
5197 const auto pii = webgl::PackingInfoInfo::For(desc.pi);
5198 if (!pii) {
5199 gfxCriticalError() << "ReadPixels: Bad " << desc.pi;
5200 return false;
5202 const auto bpp = pii->BytesPerPixel();
5204 const auto& packing = desc.packState;
5205 auto packRect = *uvec2::From(subrect.x, subrect.y);
5206 packRect.x += packing.skipPixels;
5207 packRect.y += packing.skipRows;
5209 const auto xByteSize = bpp * static_cast<uint32_t>(subrect.width);
5210 const ptrdiff_t byteOffset = packRect.y * byteStride + packRect.x * bpp;
5212 const auto srcSubrect = shmemBytes.subspan(byteOffset);
5213 const auto destSubrect = dest.subspan(byteOffset);
5215 for (const auto i : IntegerRange(subrect.height)) {
5216 const auto srcRow = srcSubrect.subspan(i * byteStride, xByteSize);
5217 const auto destRow = destSubrect.subspan(i * byteStride, xByteSize);
5218 Memcpy(&destRow, srcRow);
5221 return true;
5224 bool ClientWebGLContext::ReadPixels_SharedPrecheck(
5225 dom::CallerType aCallerType, ErrorResult& out_error) const {
5226 if (IsContextLost()) return false;
5228 if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
5229 aCallerType != dom::CallerType::System) {
5230 JsWarning("readPixels: Not allowed");
5231 out_error.Throw(NS_ERROR_DOM_SECURITY_ERR);
5232 return false;
5235 return true;
5238 // --------------------------------- GL Query ---------------------------------
5240 static inline GLenum QuerySlotTarget(const GLenum specificTarget) {
5241 if (specificTarget == LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
5242 return LOCAL_GL_ANY_SAMPLES_PASSED;
5244 return specificTarget;
5247 void ClientWebGLContext::GetQuery(JSContext* cx, GLenum specificTarget,
5248 GLenum pname,
5249 JS::MutableHandle<JS::Value> retval) const {
5250 retval.set(JS::NullValue());
5251 const FuncScope funcScope(*this, "getQuery");
5252 if (IsContextLost()) return;
5253 const auto& limits = Limits();
5254 auto& state = State();
5256 if (IsExtensionEnabled(WebGLExtensionID::EXT_disjoint_timer_query)) {
5257 if (pname == LOCAL_GL_QUERY_COUNTER_BITS) {
5258 switch (specificTarget) {
5259 case LOCAL_GL_TIME_ELAPSED_EXT:
5260 retval.set(JS::NumberValue(limits.queryCounterBitsTimeElapsed));
5261 return;
5263 case LOCAL_GL_TIMESTAMP_EXT:
5264 retval.set(JS::NumberValue(limits.queryCounterBitsTimestamp));
5265 return;
5267 default:
5268 EnqueueError_ArgEnum("target", specificTarget);
5269 return;
5274 if (pname != LOCAL_GL_CURRENT_QUERY) {
5275 EnqueueError_ArgEnum("pname", pname);
5276 return;
5279 const auto slotTarget = QuerySlotTarget(specificTarget);
5280 const auto& slot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
5281 if (!slot) {
5282 EnqueueError_ArgEnum("target", specificTarget);
5283 return;
5286 auto query = *slot;
5287 if (query && query->mTarget != specificTarget) {
5288 query = nullptr;
5291 (void)ToJSValueOrNull(cx, query, retval);
5294 void ClientWebGLContext::GetQueryParameter(
5295 JSContext*, WebGLQueryJS& query, const GLenum pname,
5296 JS::MutableHandle<JS::Value> retval) const {
5297 retval.set(JS::NullValue());
5298 const FuncScope funcScope(*this, "getQueryParameter");
5299 if (IsContextLost()) return;
5300 if (!query.ValidateUsable(*this, "query")) return;
5302 auto maybe = [&]() {
5303 const auto& inProcess = mNotLost->inProcess;
5304 if (inProcess) {
5305 return inProcess->GetQueryParameter(query.mId, pname);
5307 const auto& child = mNotLost->outOfProcess;
5308 child->FlushPendingCmds();
5309 Maybe<double> ret;
5310 if (!child->SendGetQueryParameter(query.mId, pname, &ret)) {
5311 ret.reset();
5313 return ret;
5314 }();
5315 if (!maybe) return;
5317 // We must usually wait for an event loop before the query can be available.
5318 const bool canBeAvailable =
5319 (query.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
5320 if (!canBeAvailable) {
5321 if (pname != LOCAL_GL_QUERY_RESULT_AVAILABLE) {
5322 return;
5324 maybe = Some(0.0);
5327 switch (pname) {
5328 case LOCAL_GL_QUERY_RESULT_AVAILABLE:
5329 retval.set(JS::BooleanValue(*maybe));
5330 break;
5332 default:
5333 retval.set(JS::NumberValue(*maybe));
5334 break;
5338 void ClientWebGLContext::BeginQuery(const GLenum specificTarget,
5339 WebGLQueryJS& query) {
5340 const FuncScope funcScope(*this, "beginQuery");
5341 if (IsContextLost()) return;
5342 if (!query.ValidateUsable(*this, "query")) return;
5343 auto& state = State();
5345 const auto slotTarget = QuerySlotTarget(specificTarget);
5346 const auto& slot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
5347 if (!slot) {
5348 EnqueueError_ArgEnum("target", specificTarget);
5349 return;
5352 if (*slot) {
5353 auto enumStr = EnumString(slotTarget);
5354 if (slotTarget == LOCAL_GL_ANY_SAMPLES_PASSED) {
5355 enumStr += "/ANY_SAMPLES_PASSED_CONSERVATIVE";
5357 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5358 "A Query is already active for %s.", enumStr.c_str());
5359 return;
5362 if (query.mTarget && query.mTarget != specificTarget) {
5363 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5364 "`query` cannot be changed to a different target.");
5365 return;
5368 *slot = &query;
5369 query.mTarget = specificTarget;
5371 Run<RPROC(BeginQuery)>(specificTarget, query.mId);
5374 void ClientWebGLContext::EndQuery(const GLenum specificTarget) {
5375 const FuncScope funcScope(*this, "endQuery");
5376 if (IsContextLost()) return;
5377 auto& state = State();
5379 const auto slotTarget = QuerySlotTarget(specificTarget);
5380 const auto& maybeSlot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
5381 if (!maybeSlot) {
5382 EnqueueError_ArgEnum("target", specificTarget);
5383 return;
5385 auto& slot = *maybeSlot;
5386 if (!slot || slot->mTarget != specificTarget) {
5387 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No Query is active for %s.",
5388 EnumString(specificTarget).c_str());
5389 return;
5391 const auto query = slot;
5392 slot = nullptr;
5394 Run<RPROC(EndQuery)>(specificTarget);
5396 auto& availRunnable = EnsureAvailabilityRunnable();
5397 availRunnable.mQueries.push_back(query.get());
5398 query->mCanBeAvailable = false;
5401 void ClientWebGLContext::QueryCounter(WebGLQueryJS& query,
5402 const GLenum target) const {
5403 const FuncScope funcScope(*this, "queryCounter");
5404 if (IsContextLost()) return;
5405 if (!query.ValidateUsable(*this, "query")) return;
5407 if (target != LOCAL_GL_TIMESTAMP) {
5408 EnqueueError(LOCAL_GL_INVALID_ENUM, "`target` must be TIMESTAMP.");
5409 return;
5412 if (query.mTarget && query.mTarget != target) {
5413 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5414 "`query` cannot be changed to a different target.");
5415 return;
5417 query.mTarget = target;
5419 Run<RPROC(QueryCounter)>(query.mId);
5421 auto& availRunnable = EnsureAvailabilityRunnable();
5422 availRunnable.mQueries.push_back(&query);
5423 query.mCanBeAvailable = false;
5426 // -------------------------------- Sampler -------------------------------
5427 void ClientWebGLContext::GetSamplerParameter(
5428 JSContext* cx, const WebGLSamplerJS& sampler, const GLenum pname,
5429 JS::MutableHandle<JS::Value> retval) const {
5430 retval.set(JS::NullValue());
5431 const FuncScope funcScope(*this, "getSamplerParameter");
5432 if (IsContextLost()) return;
5433 if (!sampler.ValidateUsable(*this, "sampler")) return;
5435 const auto maybe = [&]() {
5436 const auto& inProcess = mNotLost->inProcess;
5437 if (inProcess) {
5438 return inProcess->GetSamplerParameter(sampler.mId, pname);
5440 const auto& child = mNotLost->outOfProcess;
5441 child->FlushPendingCmds();
5442 Maybe<double> ret;
5443 if (!child->SendGetSamplerParameter(sampler.mId, pname, &ret)) {
5444 ret.reset();
5446 return ret;
5447 }();
5448 if (maybe) {
5449 retval.set(JS::NumberValue(*maybe));
5453 void ClientWebGLContext::BindSampler(const GLuint unit,
5454 WebGLSamplerJS* const sampler) {
5455 const FuncScope funcScope(*this, "bindSampler");
5456 if (IsContextLost()) return;
5457 if (sampler && !sampler->ValidateUsable(*this, "sampler")) return;
5458 auto& state = State();
5460 auto& texUnits = state.mTexUnits;
5461 if (unit >= texUnits.size()) {
5462 EnqueueError(LOCAL_GL_INVALID_VALUE, "`unit` (%u) larger than %zu.", unit,
5463 texUnits.size());
5464 return;
5467 // -
5469 texUnits[unit].sampler = sampler;
5471 Run<RPROC(BindSampler)>(unit, sampler ? sampler->mId : 0);
5474 void ClientWebGLContext::SamplerParameteri(WebGLSamplerJS& sampler,
5475 const GLenum pname,
5476 const GLint param) const {
5477 const FuncScope funcScope(*this, "samplerParameteri");
5478 if (IsContextLost()) return;
5479 if (!sampler.ValidateUsable(*this, "sampler")) return;
5481 Run<RPROC(SamplerParameteri)>(sampler.mId, pname, param);
5484 void ClientWebGLContext::SamplerParameterf(WebGLSamplerJS& sampler,
5485 const GLenum pname,
5486 const GLfloat param) const {
5487 const FuncScope funcScope(*this, "samplerParameterf");
5488 if (IsContextLost()) return;
5489 if (!sampler.ValidateUsable(*this, "sampler")) return;
5491 Run<RPROC(SamplerParameterf)>(sampler.mId, pname, param);
5494 // ------------------------------- GL Sync ---------------------------------
5496 void ClientWebGLContext::GetSyncParameter(
5497 JSContext* const cx, WebGLSyncJS& sync, const GLenum pname,
5498 JS::MutableHandle<JS::Value> retval) const {
5499 retval.set(JS::NullValue());
5500 const FuncScope funcScope(*this, "getSyncParameter");
5501 if (IsContextLost()) return;
5502 if (!sync.ValidateUsable(*this, "sync")) return;
5504 retval.set([&]() -> JS::Value {
5505 switch (pname) {
5506 case LOCAL_GL_OBJECT_TYPE:
5507 return JS::NumberValue(LOCAL_GL_SYNC_FENCE);
5508 case LOCAL_GL_SYNC_CONDITION:
5509 return JS::NumberValue(LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE);
5510 case LOCAL_GL_SYNC_FLAGS:
5511 return JS::NumberValue(0);
5512 case LOCAL_GL_SYNC_STATUS: {
5513 const auto res = ClientWaitSync(sync, 0, 0);
5514 const auto signaled = (res == LOCAL_GL_ALREADY_SIGNALED ||
5515 res == LOCAL_GL_CONDITION_SATISFIED);
5516 return JS::NumberValue(signaled ? LOCAL_GL_SIGNALED
5517 : LOCAL_GL_UNSIGNALED);
5519 default:
5520 EnqueueError_ArgEnum("pname", pname);
5521 return JS::NullValue();
5523 }());
5526 // -
5528 GLenum ClientWebGLContext::ClientWaitSync(WebGLSyncJS& sync,
5529 const GLbitfield flags,
5530 const GLuint64 timeout) const {
5531 const FuncScope funcScope(*this, "clientWaitSync");
5532 if (IsContextLost()) return LOCAL_GL_WAIT_FAILED;
5533 if (!sync.ValidateUsable(*this, "sync")) return LOCAL_GL_WAIT_FAILED;
5535 static constexpr auto VALID_BITS = LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT;
5536 if ((flags | VALID_BITS) != VALID_BITS) {
5537 EnqueueError(LOCAL_GL_INVALID_VALUE,
5538 "`flags` must be SYNC_FLUSH_COMMANDS_BIT or 0.");
5539 return LOCAL_GL_WAIT_FAILED;
5542 if (timeout > webgl::kMaxClientWaitSyncTimeoutNS) {
5543 EnqueueError(
5544 LOCAL_GL_INVALID_OPERATION,
5545 "`timeout` (%sns) must be less than MAX_CLIENT_WAIT_TIMEOUT_WEBGL "
5546 "(%sns).",
5547 ToStringWithCommas(timeout).c_str(),
5548 ToStringWithCommas(webgl::kMaxClientWaitSyncTimeoutNS).c_str());
5549 return LOCAL_GL_WAIT_FAILED;
5552 const bool canBeAvailable =
5553 (sync.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
5554 if (!canBeAvailable) {
5555 constexpr uint8_t WARN_AT = 100;
5556 if (sync.mNumQueriesBeforeFirstFrameBoundary <= WARN_AT) {
5557 sync.mNumQueriesBeforeFirstFrameBoundary += 1;
5558 if (sync.mNumQueriesBeforeFirstFrameBoundary == WARN_AT) {
5559 EnqueueWarning(
5560 "ClientWaitSync must return TIMEOUT_EXPIRED until control has"
5561 " returned to the user agent's main loop, but was polled %hhu "
5562 "times. Are you spin-locking? (only warns once)",
5563 sync.mNumQueriesBeforeFirstFrameBoundary);
5566 return LOCAL_GL_TIMEOUT_EXPIRED;
5569 if (mCompletedSyncId >= sync.mId) {
5570 return LOCAL_GL_ALREADY_SIGNALED;
5572 if (flags & LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT) {
5573 Flush();
5574 } else {
5575 constexpr uint8_t WARN_AT = 100;
5576 if (sync.mNumQueriesWithoutFlushCommandsBit <= WARN_AT) {
5577 sync.mNumQueriesWithoutFlushCommandsBit += 1;
5578 if (sync.mNumQueriesWithoutFlushCommandsBit == WARN_AT) {
5579 EnqueueWarning(
5580 "ClientWaitSync with timeout=0 (or GetSyncParameter(SYNC_STATUS)) "
5581 "called %hhu times without SYNC_FLUSH_COMMANDS_BIT. If you do not "
5582 "flush, this sync object is not guaranteed to ever complete.",
5583 sync.mNumQueriesWithoutFlushCommandsBit);
5587 if (!timeout) return LOCAL_GL_TIMEOUT_EXPIRED;
5589 // -
5590 // Fine, time to block:
5592 const auto ret = [&]() {
5593 const auto& inProcess = mNotLost->inProcess;
5594 if (inProcess) {
5595 return inProcess->ClientWaitSync(sync.mId, flags, timeout);
5597 const auto& child = mNotLost->outOfProcess;
5598 child->FlushPendingCmds();
5599 GLenum ret = {};
5600 if (!child->SendClientWaitSync(sync.mId, flags, timeout, &ret)) {
5601 ret = {};
5603 return ret;
5604 }();
5606 switch (ret) {
5607 case LOCAL_GL_CONDITION_SATISFIED:
5608 case LOCAL_GL_ALREADY_SIGNALED:
5609 OnSyncComplete(sync.mId);
5610 break;
5613 return ret;
5616 void ClientWebGLContext::WaitSync(const WebGLSyncJS& sync,
5617 const GLbitfield flags,
5618 const GLint64 timeout) const {
5619 const FuncScope funcScope(*this, "waitSync");
5620 if (IsContextLost()) return;
5621 if (!sync.ValidateUsable(*this, "sync")) return;
5623 if (flags != 0) {
5624 EnqueueError(LOCAL_GL_INVALID_VALUE, "`flags` must be 0.");
5625 return;
5627 if (timeout != -1) {
5628 EnqueueError(LOCAL_GL_INVALID_VALUE, "`timeout` must be TIMEOUT_IGNORED.");
5629 return;
5632 JsWarning("waitSync is a no-op.");
5635 // -------------------------- Transform Feedback ---------------------------
5637 void ClientWebGLContext::BindTransformFeedback(
5638 const GLenum target, WebGLTransformFeedbackJS* const tf) {
5639 const FuncScope funcScope(*this, "bindTransformFeedback");
5640 if (IsContextLost()) return;
5641 if (tf && !tf->ValidateUsable(*this, "tf")) return;
5642 auto& state = State();
5644 if (target != LOCAL_GL_TRANSFORM_FEEDBACK) {
5645 EnqueueError(LOCAL_GL_INVALID_ENUM, "`target` must be TRANSFORM_FEEDBACK.");
5646 return;
5648 if (state.mTfActiveAndNotPaused) {
5649 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5650 "Current Transform Feedback object is active and not paused.");
5651 return;
5654 if (tf) {
5655 tf->mHasBeenBound = true;
5656 state.mBoundTfo = tf;
5657 } else {
5658 state.mBoundTfo = state.mDefaultTfo;
5661 Run<RPROC(BindTransformFeedback)>(tf ? tf->mId : 0);
5664 void ClientWebGLContext::BeginTransformFeedback(const GLenum primMode) {
5665 const FuncScope funcScope(*this, "beginTransformFeedback");
5666 if (IsContextLost()) return;
5667 auto& state = State();
5668 auto& tfo = *(state.mBoundTfo);
5670 if (tfo.mActiveOrPaused) {
5671 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5672 "Transform Feedback is already active or paused.");
5673 return;
5675 MOZ_ASSERT(!state.mTfActiveAndNotPaused);
5677 auto& prog = state.mCurrentProgram;
5678 if (!prog) {
5679 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No program in use.");
5680 return;
5682 const auto& linkResult = GetLinkResult(*prog);
5683 if (!linkResult.success) {
5684 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5685 "Program is not successfully linked.");
5686 return;
5689 auto tfBufferCount = linkResult.active.activeTfVaryings.size();
5690 if (tfBufferCount &&
5691 linkResult.tfBufferMode == LOCAL_GL_INTERLEAVED_ATTRIBS) {
5692 tfBufferCount = 1;
5694 if (!tfBufferCount) {
5695 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5696 "Program does not use Transform Feedback.");
5697 return;
5700 const auto& buffers = tfo.mAttribBuffers;
5701 for (const auto i : IntegerRange(tfBufferCount)) {
5702 if (!buffers[i]) {
5703 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5704 "Transform Feedback buffer %u is null.", i);
5705 return;
5709 switch (primMode) {
5710 case LOCAL_GL_POINTS:
5711 case LOCAL_GL_LINES:
5712 case LOCAL_GL_TRIANGLES:
5713 break;
5714 default:
5715 EnqueueError(LOCAL_GL_INVALID_ENUM,
5716 "`primitiveMode` must be POINTS, LINES< or TRIANGLES.");
5717 return;
5720 // -
5722 tfo.mActiveOrPaused = true;
5723 tfo.mActiveProgram = prog;
5724 tfo.mActiveProgramKeepAlive = prog->mKeepAliveWeak.lock();
5725 prog->mActiveTfos.insert(&tfo);
5726 state.mTfActiveAndNotPaused = true;
5728 Run<RPROC(BeginTransformFeedback)>(primMode);
5731 void ClientWebGLContext::EndTransformFeedback() {
5732 const FuncScope funcScope(*this, "endTransformFeedback");
5733 if (IsContextLost()) return;
5734 auto& state = State();
5735 auto& tfo = *(state.mBoundTfo);
5737 if (!tfo.mActiveOrPaused) {
5738 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5739 "Transform Feedback is not active or paused.");
5740 return;
5743 tfo.mActiveOrPaused = false;
5744 tfo.mActiveProgram->mActiveTfos.erase(&tfo);
5745 tfo.mActiveProgram = nullptr;
5746 tfo.mActiveProgramKeepAlive = nullptr;
5747 state.mTfActiveAndNotPaused = false;
5748 Run<RPROC(EndTransformFeedback)>();
5751 void ClientWebGLContext::PauseTransformFeedback() {
5752 const FuncScope funcScope(*this, "pauseTransformFeedback");
5753 if (IsContextLost()) return;
5754 auto& state = State();
5755 auto& tfo = *(state.mBoundTfo);
5757 if (!tfo.mActiveOrPaused) {
5758 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5759 "Transform Feedback is not active.");
5760 return;
5762 if (!state.mTfActiveAndNotPaused) {
5763 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5764 "Transform Feedback is already paused.");
5765 return;
5768 state.mTfActiveAndNotPaused = false;
5769 Run<RPROC(PauseTransformFeedback)>();
5772 void ClientWebGLContext::ResumeTransformFeedback() {
5773 const FuncScope funcScope(*this, "resumeTransformFeedback");
5774 if (IsContextLost()) return;
5775 auto& state = State();
5776 auto& tfo = *(state.mBoundTfo);
5778 if (!tfo.mActiveOrPaused) {
5779 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5780 "Transform Feedback is not active and paused.");
5781 return;
5783 if (state.mTfActiveAndNotPaused) {
5784 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5785 "Transform Feedback is not paused.");
5786 return;
5788 if (state.mCurrentProgram != tfo.mActiveProgram) {
5789 EnqueueError(
5790 LOCAL_GL_INVALID_OPERATION,
5791 "Cannot Resume Transform Feedback with a program link result different"
5792 " from when Begin was called.");
5793 return;
5796 state.mTfActiveAndNotPaused = true;
5797 Run<RPROC(ResumeTransformFeedback)>();
5800 void ClientWebGLContext::SetFramebufferIsInOpaqueRAF(WebGLFramebufferJS* fb,
5801 bool value) {
5802 fb->mInOpaqueRAF = value;
5803 Run<RPROC(SetFramebufferIsInOpaqueRAF)>(fb->mId, value);
5806 // ---------------------------- Misc Extensions ----------------------------
5807 void ClientWebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers) {
5808 const auto range = MakeRange(buffers);
5809 const auto vec = std::vector<GLenum>(range.begin().get(), range.end().get());
5810 Run<RPROC(DrawBuffers)>(vec);
5813 void ClientWebGLContext::EnqueueErrorImpl(const GLenum error,
5814 const nsACString& text) const {
5815 if (!mNotLost) return; // Ignored if context is lost.
5816 AutoEnqueueFlush();
5817 Run<RPROC(GenerateError)>(error, ToString(text));
5820 void ClientWebGLContext::RequestExtension(const WebGLExtensionID ext) const {
5821 Run<RPROC(RequestExtension)>(ext);
5824 // -
5826 bool ClientWebGLContext::IsExtensionForbiddenForCaller(
5827 const WebGLExtensionID ext, const dom::CallerType callerType) const {
5828 if (callerType == dom::CallerType::System) {
5829 return false;
5832 if (StaticPrefs::webgl_enable_privileged_extensions()) {
5833 return false;
5836 switch (ext) {
5837 case WebGLExtensionID::MOZ_debug:
5838 return true;
5840 case WebGLExtensionID::WEBGL_debug_renderer_info:
5841 return ShouldResistFingerprinting(RFPTarget::WebGLRenderInfo) ||
5842 !StaticPrefs::webgl_enable_debug_renderer_info();
5844 case WebGLExtensionID::WEBGL_debug_shaders:
5845 return ShouldResistFingerprinting(RFPTarget::WebGLRenderInfo);
5847 default:
5848 return false;
5852 bool ClientWebGLContext::IsSupported(const WebGLExtensionID ext,
5853 const dom::CallerType callerType) const {
5854 if (IsExtensionForbiddenForCaller(ext, callerType)) {
5855 return false;
5858 const auto& limits = Limits();
5859 return limits.supportedExtensions[ext];
5862 void ClientWebGLContext::GetSupportedExtensions(
5863 dom::Nullable<nsTArray<nsString>>& retval,
5864 const dom::CallerType callerType) const {
5865 retval.SetNull();
5866 if (!mNotLost) return;
5868 auto& retarr = retval.SetValue();
5869 for (const auto i : MakeEnumeratedRange(WebGLExtensionID::Max)) {
5870 if (!IsSupported(i, callerType)) continue;
5872 const auto& extStr = GetExtensionName(i);
5873 retarr.AppendElement(NS_ConvertUTF8toUTF16(extStr));
5877 // -
5879 void ClientWebGLContext::GetSupportedProfilesASTC(
5880 dom::Nullable<nsTArray<nsString>>& retval) const {
5881 retval.SetNull();
5882 if (!mNotLost) return;
5883 const auto& limits = Limits();
5885 auto& retarr = retval.SetValue();
5886 retarr.AppendElement(u"ldr"_ns);
5887 if (limits.astcHdr) {
5888 retarr.AppendElement(u"hdr"_ns);
5892 void ClientWebGLContext::ProvokingVertex(const GLenum rawMode) const {
5893 const FuncScope funcScope(*this, "provokingVertex");
5894 if (IsContextLost()) return;
5896 const auto mode = AsEnumCase<webgl::ProvokingVertex>(rawMode);
5897 if (!mode) {
5898 EnqueueError_ArgEnum("mode", rawMode);
5899 return;
5902 Run<RPROC(ProvokingVertex)>(*mode);
5904 funcScope.mKeepNotLostOrNull->state.mProvokingVertex = *mode;
5907 // -
5909 uint32_t ClientWebGLContext::GetPrincipalHashValue() const {
5910 if (mCanvasElement) {
5911 return mCanvasElement->NodePrincipal()->GetHashValue();
5913 if (mOffscreenCanvas) {
5914 nsIGlobalObject* global = mOffscreenCanvas->GetOwnerGlobal();
5915 if (global) {
5916 nsIPrincipal* principal = global->PrincipalOrNull();
5917 if (principal) {
5918 return principal->GetHashValue();
5922 return 0;
5925 // ---------------------------
5927 void ClientWebGLContext::EnqueueError_ArgEnum(const char* const argName,
5928 const GLenum val) const {
5929 EnqueueError(LOCAL_GL_INVALID_ENUM, "Bad `%s`: 0x%04x", argName, val);
5932 // -
5933 // WebGLProgramJS
5935 void ClientWebGLContext::AttachShader(WebGLProgramJS& prog,
5936 WebGLShaderJS& shader) const {
5937 const FuncScope funcScope(*this, "attachShader");
5938 if (IsContextLost()) return;
5939 if (!prog.ValidateUsable(*this, "program")) return;
5940 if (!shader.ValidateUsable(*this, "shader")) return;
5942 auto& slot = *MaybeFind(prog.mNextLink_Shaders, shader.mType);
5943 if (slot.shader) {
5944 if (&shader == slot.shader) {
5945 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`shader` is already attached.");
5946 } else {
5947 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5948 "Only one of each type of"
5949 " shader may be attached to a program.");
5951 return;
5953 slot = {&shader, shader.mKeepAliveWeak.lock()};
5955 Run<RPROC(AttachShader)>(prog.mId, shader.mId);
5958 void ClientWebGLContext::BindAttribLocation(WebGLProgramJS& prog,
5959 const GLuint location,
5960 const nsAString& name) const {
5961 const FuncScope funcScope(*this, "bindAttribLocation");
5962 if (IsContextLost()) return;
5963 if (!prog.ValidateUsable(*this, "program")) return;
5965 const auto& nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
5966 Run<RPROC(BindAttribLocation)>(prog.mId, location, nameU8);
5969 void ClientWebGLContext::DetachShader(WebGLProgramJS& prog,
5970 const WebGLShaderJS& shader) const {
5971 const FuncScope funcScope(*this, "detachShader");
5972 if (IsContextLost()) return;
5973 if (!prog.ValidateUsable(*this, "program")) return;
5974 if (!shader.ValidateUsable(*this, "shader")) return;
5976 auto& slot = *MaybeFind(prog.mNextLink_Shaders, shader.mType);
5978 if (slot.shader != &shader) {
5979 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`shader` is not attached.");
5980 return;
5982 slot = {};
5984 Run<RPROC(DetachShader)>(prog.mId, shader.mId);
5987 void ClientWebGLContext::GetAttachedShaders(
5988 const WebGLProgramJS& prog,
5989 dom::Nullable<nsTArray<RefPtr<WebGLShaderJS>>>& retval) const {
5990 const FuncScope funcScope(*this, "getAttachedShaders");
5991 if (IsContextLost()) return;
5992 if (!prog.ValidateUsable(*this, "program")) return;
5994 auto& arr = retval.SetValue();
5995 for (const auto& pair : prog.mNextLink_Shaders) {
5996 const auto& attachment = pair.second;
5997 if (!attachment.shader) continue;
5998 arr.AppendElement(attachment.shader);
6002 void ClientWebGLContext::LinkProgram(WebGLProgramJS& prog) const {
6003 const FuncScope funcScope(*this, "linkProgram");
6004 if (IsContextLost()) return;
6005 if (!prog.ValidateUsable(*this, "program")) return;
6007 if (!prog.mActiveTfos.empty()) {
6008 EnqueueError(LOCAL_GL_INVALID_OPERATION,
6009 "Program still in use by active or paused"
6010 " Transform Feedback objects.");
6011 return;
6014 prog.mResult = std::make_shared<webgl::LinkResult>();
6015 prog.mUniformLocByName = Nothing();
6016 prog.mUniformBlockBindings = {};
6017 Run<RPROC(LinkProgram)>(prog.mId);
6020 void ClientWebGLContext::TransformFeedbackVaryings(
6021 WebGLProgramJS& prog, const dom::Sequence<nsString>& varyings,
6022 const GLenum bufferMode) const {
6023 const FuncScope funcScope(*this, "transformFeedbackVaryings");
6024 if (IsContextLost()) return;
6025 if (!prog.ValidateUsable(*this, "program")) return;
6027 std::vector<std::string> varyingsU8;
6028 varyingsU8.reserve(varyings.Length());
6029 for (const auto& cur : varyings) {
6030 const auto curU8 = ToString(NS_ConvertUTF16toUTF8(cur));
6031 varyingsU8.push_back(curU8);
6034 Run<RPROC(TransformFeedbackVaryings)>(prog.mId, varyingsU8, bufferMode);
6037 void ClientWebGLContext::UniformBlockBinding(WebGLProgramJS& prog,
6038 const GLuint blockIndex,
6039 const GLuint blockBinding) const {
6040 const FuncScope funcScope(*this, "uniformBlockBinding");
6041 if (IsContextLost()) return;
6042 if (!prog.ValidateUsable(*this, "program")) return;
6043 const auto& state = State();
6045 (void)GetLinkResult(prog);
6046 auto& list = prog.mUniformBlockBindings;
6047 if (blockIndex >= list.size()) {
6048 EnqueueError(
6049 LOCAL_GL_INVALID_VALUE,
6050 "`blockIndex` (%u) must be less than ACTIVE_UNIFORM_BLOCKS (%zu).",
6051 blockIndex, list.size());
6052 return;
6054 if (blockBinding >= state.mBoundUbos.size()) {
6055 EnqueueError(LOCAL_GL_INVALID_VALUE,
6056 "`blockBinding` (%u) must be less than "
6057 "MAX_UNIFORM_BUFFER_BINDINGS (%zu).",
6058 blockBinding, state.mBoundUbos.size());
6059 return;
6062 list[blockIndex] = blockBinding;
6063 Run<RPROC(UniformBlockBinding)>(prog.mId, blockIndex, blockBinding);
6066 // WebGLProgramJS link result reflection
6068 already_AddRefed<WebGLActiveInfoJS> ClientWebGLContext::GetActiveAttrib(
6069 const WebGLProgramJS& prog, const GLuint index) {
6070 const FuncScope funcScope(*this, "getActiveAttrib");
6071 if (IsContextLost()) return nullptr;
6072 if (!prog.ValidateUsable(*this, "program")) return nullptr;
6074 const auto& res = GetLinkResult(prog);
6075 const auto& list = res.active.activeAttribs;
6076 if (index >= list.size()) {
6077 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
6078 return nullptr;
6081 const auto& info = list[index];
6082 return AsAddRefed(new WebGLActiveInfoJS(info));
6085 already_AddRefed<WebGLActiveInfoJS> ClientWebGLContext::GetActiveUniform(
6086 const WebGLProgramJS& prog, const GLuint index) {
6087 const FuncScope funcScope(*this, "getActiveUniform");
6088 if (IsContextLost()) return nullptr;
6089 if (!prog.ValidateUsable(*this, "program")) return nullptr;
6091 const auto& res = GetLinkResult(prog);
6092 const auto& list = res.active.activeUniforms;
6093 if (index >= list.size()) {
6094 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
6095 return nullptr;
6098 const auto& info = list[index];
6099 return AsAddRefed(new WebGLActiveInfoJS(info));
6102 void ClientWebGLContext::GetActiveUniformBlockName(const WebGLProgramJS& prog,
6103 const GLuint index,
6104 nsAString& retval) const {
6105 retval.SetIsVoid(true);
6106 const FuncScope funcScope(*this, "getActiveUniformBlockName");
6107 if (IsContextLost()) return;
6108 if (!prog.ValidateUsable(*this, "program")) return;
6110 const auto& res = GetLinkResult(prog);
6111 if (!res.success) {
6112 EnqueueError(LOCAL_GL_INVALID_OPERATION, "Program has not been linked.");
6113 return;
6116 const auto& list = res.active.activeUniformBlocks;
6117 if (index >= list.size()) {
6118 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
6119 return;
6122 const auto& block = list[index];
6123 CopyUTF8toUTF16(block.name, retval);
6126 void ClientWebGLContext::GetActiveUniformBlockParameter(
6127 JSContext* const cx, const WebGLProgramJS& prog, const GLuint index,
6128 const GLenum pname, JS::MutableHandle<JS::Value> retval, ErrorResult& rv) {
6129 retval.set(JS::NullValue());
6130 const FuncScope funcScope(*this, "getActiveUniformBlockParameter");
6131 if (IsContextLost()) return;
6132 if (!prog.ValidateUsable(*this, "program")) return;
6134 const auto& res = GetLinkResult(prog);
6135 const auto& list = res.active.activeUniformBlocks;
6136 if (index >= list.size()) {
6137 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
6138 return;
6140 const auto& block = list[index];
6142 retval.set([&]() -> JS::Value {
6143 switch (pname) {
6144 case LOCAL_GL_UNIFORM_BLOCK_BINDING:
6145 return JS::NumberValue(prog.mUniformBlockBindings[index]);
6147 case LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE:
6148 return JS::NumberValue(block.dataSize);
6150 case LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS:
6151 return JS::NumberValue(block.activeUniformIndices.size());
6153 case LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: {
6154 const auto& indices = block.activeUniformIndices;
6155 return Create<dom::Uint32Array>(cx, this, indices, rv);
6158 case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER:
6159 return JS::BooleanValue(block.referencedByVertexShader);
6161 case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER:
6162 return JS::BooleanValue(block.referencedByFragmentShader);
6164 default:
6165 EnqueueError_ArgEnum("pname", pname);
6166 return JS::NullValue();
6168 }());
6171 void ClientWebGLContext::GetActiveUniforms(
6172 JSContext* const cx, const WebGLProgramJS& prog,
6173 const dom::Sequence<GLuint>& uniformIndices, const GLenum pname,
6174 JS::MutableHandle<JS::Value> retval) const {
6175 retval.set(JS::NullValue());
6176 const FuncScope funcScope(*this, "getActiveUniforms");
6177 if (IsContextLost()) return;
6178 if (!prog.ValidateUsable(*this, "program")) return;
6180 const auto& res = GetLinkResult(prog);
6181 const auto& list = res.active.activeUniforms;
6183 const auto count = uniformIndices.Length();
6184 JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, count));
6185 if (!array) return; // Just bail.
6187 for (const auto i : IntegerRange(count)) {
6188 const auto index = uniformIndices[i];
6189 if (index >= list.size()) {
6190 EnqueueError(LOCAL_GL_INVALID_VALUE,
6191 "`uniformIndices[%u]`: `%u` too large.", i, index);
6192 return;
6194 const auto& uniform = list[index];
6196 JS::Rooted<JS::Value> value(cx);
6197 switch (pname) {
6198 case LOCAL_GL_UNIFORM_TYPE:
6199 value = JS::NumberValue(uniform.elemType);
6200 break;
6202 case LOCAL_GL_UNIFORM_SIZE:
6203 value = JS::NumberValue(uniform.elemCount);
6204 break;
6206 case LOCAL_GL_UNIFORM_BLOCK_INDEX:
6207 value = JS::NumberValue(uniform.block_index);
6208 break;
6210 case LOCAL_GL_UNIFORM_OFFSET:
6211 value = JS::NumberValue(uniform.block_offset);
6212 break;
6214 case LOCAL_GL_UNIFORM_ARRAY_STRIDE:
6215 value = JS::NumberValue(uniform.block_arrayStride);
6216 break;
6218 case LOCAL_GL_UNIFORM_MATRIX_STRIDE:
6219 value = JS::NumberValue(uniform.block_matrixStride);
6220 break;
6222 case LOCAL_GL_UNIFORM_IS_ROW_MAJOR:
6223 value = JS::BooleanValue(uniform.block_isRowMajor);
6224 break;
6226 default:
6227 EnqueueError_ArgEnum("pname", pname);
6228 return;
6230 if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE)) return;
6233 retval.setObject(*array);
6236 already_AddRefed<WebGLActiveInfoJS>
6237 ClientWebGLContext::GetTransformFeedbackVarying(const WebGLProgramJS& prog,
6238 const GLuint index) {
6239 const FuncScope funcScope(*this, "getTransformFeedbackVarying");
6240 if (IsContextLost()) return nullptr;
6241 if (!prog.ValidateUsable(*this, "program")) return nullptr;
6243 const auto& res = GetLinkResult(prog);
6244 const auto& list = res.active.activeTfVaryings;
6245 if (index >= list.size()) {
6246 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
6247 return nullptr;
6250 const auto& info = list[index];
6251 return AsAddRefed(new WebGLActiveInfoJS(info));
6254 GLint ClientWebGLContext::GetAttribLocation(const WebGLProgramJS& prog,
6255 const nsAString& name) const {
6256 const FuncScope funcScope(*this, "getAttribLocation");
6257 if (IsContextLost()) return -1;
6258 if (!prog.ValidateUsable(*this, "program")) return -1;
6260 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
6261 const auto& res = GetLinkResult(prog);
6262 for (const auto& cur : res.active.activeAttribs) {
6263 if (cur.name == nameU8) return cur.location;
6266 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
6267 if (err) {
6268 EnqueueError(err->type, "%s", err->info.c_str());
6270 return -1;
6273 GLint ClientWebGLContext::GetFragDataLocation(const WebGLProgramJS& prog,
6274 const nsAString& name) const {
6275 const FuncScope funcScope(*this, "getFragDataLocation");
6276 if (IsContextLost()) return -1;
6277 if (!prog.ValidateUsable(*this, "program")) return -1;
6279 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
6281 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
6282 if (err) {
6283 EnqueueError(*err);
6284 return -1;
6287 return [&]() {
6288 const auto& inProcess = mNotLost->inProcess;
6289 if (inProcess) {
6290 return inProcess->GetFragDataLocation(prog.mId, nameU8);
6292 const auto& child = mNotLost->outOfProcess;
6293 child->FlushPendingCmds();
6294 GLint ret = {};
6295 if (!child->SendGetFragDataLocation(prog.mId, nameU8, &ret)) {
6296 ret = {};
6298 return ret;
6299 }();
6302 GLuint ClientWebGLContext::GetUniformBlockIndex(
6303 const WebGLProgramJS& prog, const nsAString& blockName) const {
6304 const FuncScope funcScope(*this, "getUniformBlockIndex");
6305 if (IsContextLost()) return LOCAL_GL_INVALID_INDEX;
6306 if (!prog.ValidateUsable(*this, "program")) return LOCAL_GL_INVALID_INDEX;
6308 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(blockName));
6310 const auto& res = GetLinkResult(prog);
6311 const auto& list = res.active.activeUniformBlocks;
6312 for (const auto i : IntegerRange(list.size())) {
6313 const auto& cur = list[i];
6314 if (cur.name == nameU8) {
6315 return i;
6318 return LOCAL_GL_INVALID_INDEX;
6321 void ClientWebGLContext::GetUniformIndices(
6322 const WebGLProgramJS& prog, const dom::Sequence<nsString>& uniformNames,
6323 dom::Nullable<nsTArray<GLuint>>& retval) const {
6324 const FuncScope funcScope(*this, "getUniformIndices");
6325 if (IsContextLost()) return;
6326 if (!prog.ValidateUsable(*this, "program")) return;
6328 const auto& res = GetLinkResult(prog);
6329 auto ret = nsTArray<GLuint>(uniformNames.Length());
6331 for (const auto& queriedNameU16 : uniformNames) {
6332 const auto queriedName = ToString(NS_ConvertUTF16toUTF8(queriedNameU16));
6333 const auto impliedProperArrayQueriedName = queriedName + "[0]";
6335 GLuint activeId = LOCAL_GL_INVALID_INDEX;
6336 for (const auto i : IntegerRange(res.active.activeUniforms.size())) {
6337 // O(N^2) ok for small N.
6338 const auto& activeInfoForI = res.active.activeUniforms[i];
6339 if (queriedName == activeInfoForI.name ||
6340 impliedProperArrayQueriedName == activeInfoForI.name) {
6341 activeId = i;
6342 break;
6345 ret.AppendElement(activeId);
6348 retval.SetValue(std::move(ret));
6351 already_AddRefed<WebGLUniformLocationJS> ClientWebGLContext::GetUniformLocation(
6352 const WebGLProgramJS& prog, const nsAString& name) const {
6353 const FuncScope funcScope(*this, "getUniformLocation");
6354 if (IsContextLost()) return nullptr;
6355 if (!prog.ValidateUsable(*this, "program")) return nullptr;
6357 const auto& res = GetLinkResult(prog);
6359 if (!prog.mUniformLocByName) {
6360 // Cache a map from name->location.
6361 // Since the only way to set uniforms requires calling GetUniformLocation,
6362 // we expect apps to query most active uniforms once for each scalar or
6363 // array. NB: Uniform array setters do have overflow semantics, even though
6364 // uniform locations aren't guaranteed contiguous, but GetUniformLocation
6365 // must still be called once per array.
6366 prog.mUniformLocByName.emplace();
6368 for (const auto& activeUniform : res.active.activeUniforms) {
6369 if (activeUniform.block_index != -1) continue;
6371 auto locName = activeUniform.name;
6372 const auto indexed = webgl::ParseIndexed(locName);
6373 if (indexed) {
6374 locName = indexed->name;
6377 const auto err = CheckGLSLVariableName(mIsWebGL2, locName);
6378 if (err) continue;
6380 const auto baseLength = locName.size();
6381 for (const auto& pair : activeUniform.locByIndex) {
6382 if (indexed) {
6383 locName.erase(baseLength); // Erase previous "[N]".
6384 locName += '[';
6385 locName += std::to_string(pair.first);
6386 locName += ']';
6388 const auto locInfo =
6389 WebGLProgramJS::UniformLocInfo{pair.second, activeUniform.elemType};
6390 prog.mUniformLocByName->insert({locName, locInfo});
6394 const auto& locByName = *(prog.mUniformLocByName);
6396 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
6397 auto loc = MaybeFind(locByName, nameU8);
6398 if (!loc) {
6399 loc = MaybeFind(locByName, nameU8 + "[0]");
6401 if (!loc) {
6402 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
6403 if (err) {
6404 EnqueueError(err->type, "%s", err->info.c_str());
6406 return nullptr;
6409 return AsAddRefed(new WebGLUniformLocationJS(*this, prog.mResult,
6410 loc->location, loc->elemType));
6413 std::array<uint16_t, 3> ValidUploadElemTypes(const GLenum elemType) {
6414 std::vector<GLenum> ret;
6415 switch (elemType) {
6416 case LOCAL_GL_BOOL:
6417 ret = {LOCAL_GL_FLOAT, LOCAL_GL_INT, LOCAL_GL_UNSIGNED_INT};
6418 break;
6419 case LOCAL_GL_BOOL_VEC2:
6420 ret = {LOCAL_GL_FLOAT_VEC2, LOCAL_GL_INT_VEC2,
6421 LOCAL_GL_UNSIGNED_INT_VEC2};
6422 break;
6423 case LOCAL_GL_BOOL_VEC3:
6424 ret = {LOCAL_GL_FLOAT_VEC3, LOCAL_GL_INT_VEC3,
6425 LOCAL_GL_UNSIGNED_INT_VEC3};
6426 break;
6427 case LOCAL_GL_BOOL_VEC4:
6428 ret = {LOCAL_GL_FLOAT_VEC4, LOCAL_GL_INT_VEC4,
6429 LOCAL_GL_UNSIGNED_INT_VEC4};
6430 break;
6432 case LOCAL_GL_SAMPLER_2D:
6433 case LOCAL_GL_SAMPLER_3D:
6434 case LOCAL_GL_SAMPLER_CUBE:
6435 case LOCAL_GL_SAMPLER_2D_SHADOW:
6436 case LOCAL_GL_SAMPLER_2D_ARRAY:
6437 case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
6438 case LOCAL_GL_SAMPLER_CUBE_SHADOW:
6439 case LOCAL_GL_INT_SAMPLER_2D:
6440 case LOCAL_GL_INT_SAMPLER_3D:
6441 case LOCAL_GL_INT_SAMPLER_CUBE:
6442 case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
6443 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
6444 case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
6445 case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
6446 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
6447 ret = {LOCAL_GL_INT};
6448 break;
6450 default:
6451 ret = {elemType};
6452 break;
6455 std::array<uint16_t, 3> arr = {};
6456 MOZ_ASSERT(arr[2] == 0);
6457 for (const auto i : IntegerRange(ret.size())) {
6458 arr[i] = AssertedCast<uint16_t>(ret[i]);
6460 return arr;
6463 void ClientWebGLContext::GetProgramInfoLog(const WebGLProgramJS& prog,
6464 nsAString& retval) const {
6465 retval.SetIsVoid(true);
6466 const FuncScope funcScope(*this, "getProgramInfoLog");
6467 if (IsContextLost()) return;
6468 if (!prog.ValidateUsable(*this, "program")) return;
6470 const auto& res = GetLinkResult(prog);
6471 CopyUTF8toUTF16(res.log, retval);
6474 void ClientWebGLContext::GetProgramParameter(
6475 JSContext* const js, const WebGLProgramJS& prog, const GLenum pname,
6476 JS::MutableHandle<JS::Value> retval) const {
6477 retval.set(JS::NullValue());
6478 const FuncScope funcScope(*this, "getProgramParameter");
6479 if (IsContextLost()) return;
6480 if (!prog.ValidateUsable(*this, "program")) return;
6482 retval.set([&]() -> JS::Value {
6483 switch (pname) {
6484 case LOCAL_GL_DELETE_STATUS:
6485 // "Is flagged for deletion?"
6486 return JS::BooleanValue(!prog.mKeepAlive);
6487 case LOCAL_GL_VALIDATE_STATUS:
6488 return JS::BooleanValue(prog.mLastValidate);
6489 case LOCAL_GL_ATTACHED_SHADERS: {
6490 size_t shaders = 0;
6491 for (const auto& pair : prog.mNextLink_Shaders) {
6492 const auto& slot = pair.second;
6493 if (slot.shader) {
6494 shaders += 1;
6497 return JS::NumberValue(shaders);
6499 default:
6500 break;
6503 const auto& res = GetLinkResult(prog);
6505 switch (pname) {
6506 case LOCAL_GL_LINK_STATUS:
6507 return JS::BooleanValue(res.success);
6509 case LOCAL_GL_ACTIVE_ATTRIBUTES:
6510 return JS::NumberValue(res.active.activeAttribs.size());
6512 case LOCAL_GL_ACTIVE_UNIFORMS:
6513 return JS::NumberValue(res.active.activeUniforms.size());
6515 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
6516 if (!mIsWebGL2) break;
6517 return JS::NumberValue(res.tfBufferMode);
6519 case LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS:
6520 if (!mIsWebGL2) break;
6521 return JS::NumberValue(res.active.activeTfVaryings.size());
6523 case LOCAL_GL_ACTIVE_UNIFORM_BLOCKS:
6524 if (!mIsWebGL2) break;
6525 return JS::NumberValue(res.active.activeUniformBlocks.size());
6527 default:
6528 break;
6530 EnqueueError_ArgEnum("pname", pname);
6531 return JS::NullValue();
6532 }());
6535 // -
6536 // WebGLShaderJS
6538 void ClientWebGLContext::CompileShader(WebGLShaderJS& shader) const {
6539 const FuncScope funcScope(*this, "compileShader");
6540 if (IsContextLost()) return;
6541 if (!shader.ValidateUsable(*this, "shader")) return;
6543 shader.mResult = {};
6544 Run<RPROC(CompileShader)>(shader.mId);
6547 void ClientWebGLContext::GetShaderInfoLog(const WebGLShaderJS& shader,
6548 nsAString& retval) const {
6549 retval.SetIsVoid(true);
6550 const FuncScope funcScope(*this, "getShaderInfoLog");
6551 if (IsContextLost()) return;
6552 if (!shader.ValidateUsable(*this, "shader")) return;
6554 const auto& result = GetCompileResult(shader);
6555 CopyUTF8toUTF16(result.log, retval);
6558 void ClientWebGLContext::GetShaderParameter(
6559 JSContext* const cx, const WebGLShaderJS& shader, const GLenum pname,
6560 JS::MutableHandle<JS::Value> retval) const {
6561 retval.set(JS::NullValue());
6562 const FuncScope funcScope(*this, "getShaderParameter");
6563 if (IsContextLost()) return;
6564 if (!shader.ValidateUsable(*this, "shader")) return;
6566 retval.set([&]() -> JS::Value {
6567 switch (pname) {
6568 case LOCAL_GL_SHADER_TYPE:
6569 return JS::NumberValue(shader.mType);
6571 case LOCAL_GL_DELETE_STATUS: // "Is flagged for deletion?"
6572 return JS::BooleanValue(!shader.mKeepAlive);
6574 case LOCAL_GL_COMPILE_STATUS: {
6575 const auto& result = GetCompileResult(shader);
6576 return JS::BooleanValue(result.success);
6579 default:
6580 EnqueueError_ArgEnum("pname", pname);
6581 return JS::NullValue();
6583 }());
6586 void ClientWebGLContext::GetShaderSource(const WebGLShaderJS& shader,
6587 nsAString& retval) const {
6588 retval.SetIsVoid(true);
6589 const FuncScope funcScope(*this, "getShaderSource");
6590 if (IsContextLost()) return;
6591 if (!shader.ValidateUsable(*this, "shader")) return;
6593 CopyUTF8toUTF16(shader.mSource, retval);
6596 void ClientWebGLContext::GetTranslatedShaderSource(const WebGLShaderJS& shader,
6597 nsAString& retval) const {
6598 retval.SetIsVoid(true);
6599 const FuncScope funcScope(*this, "getTranslatedShaderSource");
6600 if (IsContextLost()) return;
6601 if (!shader.ValidateUsable(*this, "shader")) return;
6603 const auto& result = GetCompileResult(shader);
6604 CopyUTF8toUTF16(result.translatedSource, retval);
6607 void ClientWebGLContext::ShaderSource(WebGLShaderJS& shader,
6608 const nsAString& sourceU16) const {
6609 const FuncScope funcScope(*this, "shaderSource");
6610 if (IsContextLost()) return;
6611 if (!shader.ValidateUsable(*this, "shader")) return;
6613 shader.mSource = ToString(NS_ConvertUTF16toUTF8(sourceU16));
6614 Run<RPROC(ShaderSource)>(shader.mId, shader.mSource);
6617 // -
6619 const webgl::CompileResult& ClientWebGLContext::GetCompileResult(
6620 const WebGLShaderJS& shader) const {
6621 if (shader.mResult.pending) {
6622 shader.mResult = [&]() {
6623 const auto& inProcess = mNotLost->inProcess;
6624 if (inProcess) {
6625 return inProcess->GetCompileResult(shader.mId);
6627 const auto& child = mNotLost->outOfProcess;
6628 child->FlushPendingCmds();
6629 webgl::CompileResult ret = {};
6630 if (!child->SendGetCompileResult(shader.mId, &ret)) {
6631 ret = {};
6633 return ret;
6634 }();
6636 return shader.mResult;
6639 const webgl::LinkResult& ClientWebGLContext::GetLinkResult(
6640 const WebGLProgramJS& prog) const {
6641 if (prog.mResult->pending) {
6642 const auto notLost =
6643 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
6644 if (!notLost) return *(prog.mResult);
6646 *(prog.mResult) = [&]() {
6647 const auto& inProcess = mNotLost->inProcess;
6648 if (inProcess) {
6649 return inProcess->GetLinkResult(prog.mId);
6651 const auto& child = mNotLost->outOfProcess;
6652 child->FlushPendingCmds();
6653 webgl::LinkResult ret;
6654 if (!child->SendGetLinkResult(prog.mId, &ret)) {
6655 ret = {};
6657 return ret;
6658 }();
6660 prog.mUniformBlockBindings.resize(
6661 prog.mResult->active.activeUniformBlocks.size());
6663 auto& state = State();
6664 if (state.mCurrentProgram == &prog && prog.mResult->success) {
6665 state.mActiveLinkResult = prog.mResult;
6668 return *(prog.mResult);
6671 #undef RPROC
6673 // ---------------------------
6675 Maybe<Span<uint8_t>> ClientWebGLContext::ValidateArrayBufferView(
6676 const Span<uint8_t>& bytes, size_t elemSize, GLuint elemOffset,
6677 GLuint elemCountOverride, const GLenum errorEnum) const {
6678 size_t elemCount = bytes.Length() / elemSize;
6679 if (elemOffset > elemCount) {
6680 EnqueueError(errorEnum, "Invalid offset into ArrayBufferView.");
6681 return Nothing();
6683 elemCount -= elemOffset;
6685 if (elemCountOverride) {
6686 if (elemCountOverride > elemCount) {
6687 EnqueueError(errorEnum, "Invalid sub-length for ArrayBufferView.");
6688 return Nothing();
6690 elemCount = elemCountOverride;
6693 return Some(bytes.Subspan(elemOffset * elemSize, elemCount * elemSize));
6696 // ---------------------------
6698 webgl::ObjectJS::ObjectJS(const ClientWebGLContext* const webgl)
6699 : mGeneration(webgl ? webgl->mNotLost : nullptr),
6700 mId(webgl ? webgl->NextId() : 0) {}
6702 // -
6704 WebGLFramebufferJS::WebGLFramebufferJS(const ClientWebGLContext& webgl,
6705 bool opaque)
6706 : webgl::ObjectJS(&webgl), mOpaque(opaque) {
6707 (void)mAttachments[LOCAL_GL_DEPTH_ATTACHMENT];
6708 (void)mAttachments[LOCAL_GL_STENCIL_ATTACHMENT];
6709 if (!webgl.mIsWebGL2) {
6710 (void)mAttachments[LOCAL_GL_DEPTH_STENCIL_ATTACHMENT];
6713 EnsureColorAttachments();
6716 void WebGLFramebufferJS::EnsureColorAttachments() {
6717 const auto& webgl = Context();
6718 if (!webgl) return; // Context is lost.
6720 const auto& limits = webgl->Limits();
6721 auto maxColorDrawBuffers = limits.maxColorDrawBuffers;
6722 if (!webgl->mIsWebGL2 &&
6723 !webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers)) {
6724 maxColorDrawBuffers = 1;
6726 for (const auto i : IntegerRange(maxColorDrawBuffers)) {
6727 (void)mAttachments[LOCAL_GL_COLOR_ATTACHMENT0 + i];
6731 WebGLProgramJS::WebGLProgramJS(const ClientWebGLContext& webgl)
6732 : webgl::ObjectJS(&webgl),
6733 mKeepAlive(std::make_shared<webgl::ProgramKeepAlive>(*this)),
6734 mKeepAliveWeak(mKeepAlive) {
6735 (void)mNextLink_Shaders[LOCAL_GL_VERTEX_SHADER];
6736 (void)mNextLink_Shaders[LOCAL_GL_FRAGMENT_SHADER];
6738 mResult = std::make_shared<webgl::LinkResult>();
6741 WebGLShaderJS::WebGLShaderJS(const ClientWebGLContext& webgl, const GLenum type)
6742 : webgl::ObjectJS(&webgl),
6743 mType(type),
6744 mKeepAlive(std::make_shared<webgl::ShaderKeepAlive>(*this)),
6745 mKeepAliveWeak(mKeepAlive) {}
6747 WebGLTransformFeedbackJS::WebGLTransformFeedbackJS(
6748 const ClientWebGLContext& webgl)
6749 : webgl::ObjectJS(&webgl),
6750 mAttribBuffers(webgl::kMaxTransformFeedbackSeparateAttribs) {}
6752 WebGLVertexArrayJS::WebGLVertexArrayJS(const ClientWebGLContext* const webgl)
6753 : webgl::ObjectJS(webgl),
6754 mAttribBuffers(Context() ? Context()->Limits().maxVertexAttribs : 0) {}
6756 // -
6758 #define _(WebGLType) \
6759 JSObject* WebGLType##JS::WrapObject(JSContext* const cx, \
6760 JS::Handle<JSObject*> givenProto) { \
6761 return dom::WebGLType##_Binding::Wrap(cx, this, givenProto); \
6764 _(WebGLBuffer)
6765 _(WebGLFramebuffer)
6766 _(WebGLProgram)
6767 _(WebGLQuery)
6768 _(WebGLRenderbuffer)
6769 _(WebGLSampler)
6770 _(WebGLShader)
6771 _(WebGLSync)
6772 _(WebGLTexture)
6773 _(WebGLTransformFeedback)
6774 _(WebGLUniformLocation)
6775 //_(WebGLVertexArray) // The webidl is `WebGLVertexArrayObject` :(
6777 #undef _
6779 JSObject* WebGLVertexArrayJS::WrapObject(JSContext* const cx,
6780 JS::Handle<JSObject*> givenProto) {
6781 return dom::WebGLVertexArrayObject_Binding::Wrap(cx, this, givenProto);
6784 bool WebGLActiveInfoJS::WrapObject(JSContext* const cx,
6785 JS::Handle<JSObject*> givenProto,
6786 JS::MutableHandle<JSObject*> reflector) {
6787 return dom::WebGLActiveInfo_Binding::Wrap(cx, this, givenProto, reflector);
6790 bool WebGLShaderPrecisionFormatJS::WrapObject(
6791 JSContext* const cx, JS::Handle<JSObject*> givenProto,
6792 JS::MutableHandle<JSObject*> reflector) {
6793 return dom::WebGLShaderPrecisionFormat_Binding::Wrap(cx, this, givenProto,
6794 reflector);
6797 // ---------------------
6799 template <typename T>
6800 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
6801 const std::vector<RefPtr<T>>& field,
6802 const char* name, uint32_t flags) {
6803 for (const auto& cur : field) {
6804 ImplCycleCollectionTraverse(callback, cur, name, flags);
6808 template <typename T>
6809 void ImplCycleCollectionUnlink(std::vector<RefPtr<T>>& field) {
6810 field = {};
6813 // -
6815 template <typename T, size_t N>
6816 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
6817 const std::array<RefPtr<T>, N>& field,
6818 const char* name, uint32_t flags) {
6819 for (const auto& cur : field) {
6820 ImplCycleCollectionTraverse(callback, cur, name, flags);
6824 template <typename T, size_t N>
6825 void ImplCycleCollectionUnlink(std::array<RefPtr<T>, N>& field) {
6826 field = {};
6829 // -
6831 template <typename T>
6832 void ImplCycleCollectionTraverse(
6833 nsCycleCollectionTraversalCallback& callback,
6834 const std::unordered_map<GLenum, RefPtr<T>>& field, const char* name,
6835 uint32_t flags) {
6836 for (const auto& pair : field) {
6837 ImplCycleCollectionTraverse(callback, pair.second, name, flags);
6841 template <typename T>
6842 void ImplCycleCollectionUnlink(std::unordered_map<GLenum, RefPtr<T>>& field) {
6843 field = {};
6846 // -
6848 void ImplCycleCollectionTraverse(
6849 nsCycleCollectionTraversalCallback& callback,
6850 const std::unordered_map<GLenum, WebGLFramebufferJS::Attachment>& field,
6851 const char* name, uint32_t flags) {
6852 for (const auto& pair : field) {
6853 const auto& attach = pair.second;
6854 ImplCycleCollectionTraverse(callback, attach.rb, name, flags);
6855 ImplCycleCollectionTraverse(callback, attach.tex, name, flags);
6859 void ImplCycleCollectionUnlink(
6860 std::unordered_map<GLenum, WebGLFramebufferJS::Attachment>& field) {
6861 field = {};
6864 // -
6866 void ImplCycleCollectionTraverse(
6867 nsCycleCollectionTraversalCallback& callback,
6868 const std::unordered_map<GLenum, WebGLProgramJS::Attachment>& field,
6869 const char* name, uint32_t flags) {
6870 for (const auto& pair : field) {
6871 const auto& attach = pair.second;
6872 ImplCycleCollectionTraverse(callback, attach.shader, name, flags);
6876 void ImplCycleCollectionUnlink(
6877 std::unordered_map<GLenum, WebGLProgramJS::Attachment>& field) {
6878 field = {};
6881 // -
6883 void ImplCycleCollectionUnlink(
6884 const RefPtr<ClientWebGLExtensionLoseContext>& field) {
6885 const_cast<RefPtr<ClientWebGLExtensionLoseContext>&>(field) = nullptr;
6887 void ImplCycleCollectionUnlink(const RefPtr<WebGLProgramJS>& field) {
6888 const_cast<RefPtr<WebGLProgramJS>&>(field) = nullptr;
6890 void ImplCycleCollectionUnlink(const RefPtr<WebGLShaderJS>& field) {
6891 const_cast<RefPtr<WebGLShaderJS>&>(field) = nullptr;
6894 // ----------------------
6896 void ImplCycleCollectionTraverse(
6897 nsCycleCollectionTraversalCallback& callback,
6898 const std::shared_ptr<webgl::NotLostData>& field, const char* name,
6899 uint32_t flags) {
6900 if (!field) return;
6902 ImplCycleCollectionTraverse(callback, field->extensions,
6903 "NotLostData.extensions", flags);
6905 const auto& state = field->state;
6907 ImplCycleCollectionTraverse(callback, state.mDefaultTfo, "state.mDefaultTfo",
6908 flags);
6909 ImplCycleCollectionTraverse(callback, state.mDefaultVao, "state.mDefaultVao",
6910 flags);
6912 ImplCycleCollectionTraverse(callback, state.mCurrentProgram,
6913 "state.mCurrentProgram", flags);
6915 ImplCycleCollectionTraverse(callback, state.mBoundBufferByTarget,
6916 "state.mBoundBufferByTarget", flags);
6917 ImplCycleCollectionTraverse(callback, state.mBoundUbos, "state.mBoundUbos",
6918 flags);
6919 ImplCycleCollectionTraverse(callback, state.mBoundDrawFb,
6920 "state.mBoundDrawFb", flags);
6921 ImplCycleCollectionTraverse(callback, state.mBoundReadFb,
6922 "state.mBoundReadFb", flags);
6923 ImplCycleCollectionTraverse(callback, state.mBoundRb, "state.mBoundRb",
6924 flags);
6925 ImplCycleCollectionTraverse(callback, state.mBoundTfo, "state.mBoundTfo",
6926 flags);
6927 ImplCycleCollectionTraverse(callback, state.mBoundVao, "state.mBoundVao",
6928 flags);
6929 ImplCycleCollectionTraverse(callback, state.mCurrentQueryByTarget,
6930 "state.state.mCurrentQueryByTarget", flags);
6932 for (const auto& texUnit : state.mTexUnits) {
6933 ImplCycleCollectionTraverse(callback, texUnit.sampler,
6934 "state.mTexUnits[].sampler", flags);
6935 ImplCycleCollectionTraverse(callback, texUnit.texByTarget,
6936 "state.mTexUnits[].texByTarget", flags);
6940 void ImplCycleCollectionUnlink(std::shared_ptr<webgl::NotLostData>& field) {
6941 if (!field) return;
6942 const auto keepAlive = field;
6943 keepAlive->extensions = {};
6944 keepAlive->state = {};
6945 field = nullptr;
6948 // -----------------------------------------------------
6950 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLBufferJS)
6951 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLFramebufferJS, mAttachments)
6952 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLProgramJS, mNextLink_Shaders)
6953 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLQueryJS)
6954 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLRenderbufferJS)
6955 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLSamplerJS)
6956 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLShaderJS)
6957 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLSyncJS)
6958 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLTextureJS)
6959 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLTransformFeedbackJS, mAttribBuffers,
6960 mActiveProgram)
6961 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLUniformLocationJS)
6962 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLVertexArrayJS, mIndexBuffer,
6963 mAttribBuffers)
6965 // -
6967 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ClientWebGLContext)
6968 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
6969 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
6970 NS_INTERFACE_MAP_ENTRY(nsISupports)
6971 NS_INTERFACE_MAP_END
6973 NS_IMPL_CYCLE_COLLECTING_ADDREF(ClientWebGLContext)
6974 NS_IMPL_CYCLE_COLLECTING_RELEASE(ClientWebGLContext)
6976 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(
6977 ClientWebGLContext, mExtLoseContext, mNotLost,
6978 // Don't forget nsICanvasRenderingContextInternal:
6979 mCanvasElement, mOffscreenCanvas)
6981 // -----------------------------
6983 } // namespace mozilla