Bug 1798703 - Enforce alignment for UniformData via union rather than overalignment...
[gecko.git] / dom / canvas / ClientWebGLContext.cpp
blob4cdcfd467d6fb324da7f864b17b715879d2a6775
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "ClientWebGLContext.h"
8 #include <bitset>
10 #include "ClientWebGLExtensions.h"
11 #include "Layers.h"
12 #include "gfxCrashReporterUtils.h"
13 #include "HostWebGLContext.h"
14 #include "js/PropertyAndElement.h" // JS_DefineElement
15 #include "js/ScalarType.h" // js::Scalar::Type
16 #include "mozilla/dom/Document.h"
17 #include "mozilla/dom/ToJSValue.h"
18 #include "mozilla/dom/WebGLContextEvent.h"
19 #include "mozilla/dom/WorkerCommon.h"
20 #include "mozilla/EnumeratedRange.h"
21 #include "mozilla/gfx/gfxVars.h"
22 #include "mozilla/gfx/CanvasManagerChild.h"
23 #include "mozilla/ipc/Shmem.h"
24 #include "mozilla/gfx/Swizzle.h"
25 #include "mozilla/layers/CompositorBridgeChild.h"
26 #include "mozilla/layers/ImageBridgeChild.h"
27 #include "mozilla/layers/OOPCanvasRenderer.h"
28 #include "mozilla/layers/TextureClientSharedSurface.h"
29 #include "mozilla/layers/WebRenderUserData.h"
30 #include "mozilla/layers/WebRenderCanvasRenderer.h"
31 #include "mozilla/ResultVariant.h"
32 #include "mozilla/ScopeExit.h"
33 #include "mozilla/StaticPrefs_webgl.h"
34 #include "nsContentUtils.h"
35 #include "nsDisplayList.h"
36 #include "TexUnpackBlob.h"
37 #include "WebGLMethodDispatcher.h"
38 #include "WebGLChild.h"
39 #include "WebGLValidateStrings.h"
41 namespace mozilla {
43 namespace webgl {
44 std::string SanitizeRenderer(const std::string&);
45 } // namespace webgl
47 // -
49 webgl::NotLostData::NotLostData(ClientWebGLContext& _context)
50 : context(_context) {}
52 webgl::NotLostData::~NotLostData() {
53 if (outOfProcess) {
54 Unused << dom::WebGLChild::Send__delete__(outOfProcess.get());
58 // -
60 bool webgl::ObjectJS::ValidateForContext(
61 const ClientWebGLContext& targetContext, const char* const argName) const {
62 if (!IsForContext(targetContext)) {
63 targetContext.EnqueueError(
64 LOCAL_GL_INVALID_OPERATION,
65 "`%s` is from a different (or lost) WebGL context.", argName);
66 return false;
68 return true;
71 void webgl::ObjectJS::WarnInvalidUse(const ClientWebGLContext& targetContext,
72 const char* const argName) const {
73 if (!ValidateForContext(targetContext, argName)) return;
75 const auto errEnum = ErrorOnDeleted();
76 targetContext.EnqueueError(errEnum, "Object `%s` is already deleted.",
77 argName);
80 static bool GetJSScalarFromGLType(GLenum type,
81 js::Scalar::Type* const out_scalarType) {
82 switch (type) {
83 case LOCAL_GL_BYTE:
84 *out_scalarType = js::Scalar::Int8;
85 return true;
87 case LOCAL_GL_UNSIGNED_BYTE:
88 *out_scalarType = js::Scalar::Uint8;
89 return true;
91 case LOCAL_GL_SHORT:
92 *out_scalarType = js::Scalar::Int16;
93 return true;
95 case LOCAL_GL_HALF_FLOAT:
96 case LOCAL_GL_HALF_FLOAT_OES:
97 case LOCAL_GL_UNSIGNED_SHORT:
98 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
99 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
100 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
101 *out_scalarType = js::Scalar::Uint16;
102 return true;
104 case LOCAL_GL_UNSIGNED_INT:
105 case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
106 case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
107 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
108 case LOCAL_GL_UNSIGNED_INT_24_8:
109 *out_scalarType = js::Scalar::Uint32;
110 return true;
111 case LOCAL_GL_INT:
112 *out_scalarType = js::Scalar::Int32;
113 return true;
115 case LOCAL_GL_FLOAT:
116 *out_scalarType = js::Scalar::Float32;
117 return true;
119 default:
120 return false;
124 ClientWebGLContext::ClientWebGLContext(const bool webgl2)
125 : mIsWebGL2(webgl2),
126 mExtLoseContext(new ClientWebGLExtensionLoseContext(*this)) {}
128 ClientWebGLContext::~ClientWebGLContext() { RemovePostRefreshObserver(); }
130 void ClientWebGLContext::JsWarning(const std::string& utf8) const {
131 nsIGlobalObject* global = nullptr;
132 if (mCanvasElement) {
133 mozilla::dom::Document* doc = mCanvasElement->OwnerDoc();
134 if (doc) {
135 global = doc->GetScopeObject();
137 } else if (mOffscreenCanvas) {
138 global = mOffscreenCanvas->GetOwnerGlobal();
141 dom::AutoJSAPI api;
142 if (!api.Init(global)) {
143 return;
145 const auto& cx = api.cx();
146 JS::WarnUTF8(cx, "%s", utf8.c_str());
149 void AutoJsWarning(const std::string& utf8) {
150 if (NS_IsMainThread()) {
151 const AutoJSContext cx;
152 JS::WarnUTF8(cx, "%s", utf8.c_str());
153 return;
156 JSContext* cx = dom::GetCurrentWorkerThreadJSContext();
157 if (NS_WARN_IF(!cx)) {
158 return;
161 JS::WarnUTF8(cx, "%s", utf8.c_str());
164 // ---------
166 bool ClientWebGLContext::DispatchEvent(const nsAString& eventName) const {
167 const auto kCanBubble = CanBubble::eYes;
168 const auto kIsCancelable = Cancelable::eYes;
169 bool useDefaultHandler = true;
171 if (mCanvasElement) {
172 nsContentUtils::DispatchTrustedEvent(
173 mCanvasElement->OwnerDoc(), static_cast<nsIContent*>(mCanvasElement),
174 eventName, kCanBubble, kIsCancelable, &useDefaultHandler);
175 } else if (mOffscreenCanvas) {
176 // OffscreenCanvas case
177 RefPtr<dom::Event> event =
178 new dom::Event(mOffscreenCanvas, nullptr, nullptr);
179 event->InitEvent(eventName, kCanBubble, kIsCancelable);
180 event->SetTrusted(true);
181 useDefaultHandler = mOffscreenCanvas->DispatchEvent(
182 *event, dom::CallerType::System, IgnoreErrors());
184 return useDefaultHandler;
187 // -
189 void ClientWebGLContext::EmulateLoseContext() const {
190 const FuncScope funcScope(*this, "loseContext");
191 if (mLossStatus != webgl::LossStatus::Ready) {
192 JsWarning("loseContext: Already lost.");
193 if (!mNextError) {
194 mNextError = LOCAL_GL_INVALID_OPERATION;
196 return;
198 OnContextLoss(webgl::ContextLossReason::Manual);
201 void ClientWebGLContext::OnContextLoss(
202 const webgl::ContextLossReason reason) const {
203 JsWarning("WebGL context was lost.");
205 if (mNotLost) {
206 for (const auto& ext : mNotLost->extensions) {
207 if (!ext) continue;
208 ext->mContext = nullptr; // Detach.
210 mNotLost = {}; // Lost now!
211 mNextError = LOCAL_GL_CONTEXT_LOST_WEBGL;
214 switch (reason) {
215 case webgl::ContextLossReason::Guilty:
216 mLossStatus = webgl::LossStatus::LostForever;
217 break;
219 case webgl::ContextLossReason::None:
220 mLossStatus = webgl::LossStatus::Lost;
221 break;
223 case webgl::ContextLossReason::Manual:
224 mLossStatus = webgl::LossStatus::LostManually;
225 break;
228 const auto weak = WeakPtr<const ClientWebGLContext>(this);
229 const auto fnRun = [weak]() {
230 const auto strong = RefPtr<const ClientWebGLContext>(weak);
231 if (!strong) return;
232 strong->Event_webglcontextlost();
234 already_AddRefed<mozilla::CancelableRunnable> runnable =
235 NS_NewCancelableRunnableFunction("enqueue Event_webglcontextlost", fnRun);
236 NS_DispatchToCurrentThread(std::move(runnable));
239 void ClientWebGLContext::Event_webglcontextlost() const {
240 const bool useDefaultHandler = DispatchEvent(u"webglcontextlost"_ns);
241 if (useDefaultHandler) {
242 mLossStatus = webgl::LossStatus::LostForever;
245 if (mLossStatus == webgl::LossStatus::Lost) {
246 RestoreContext(webgl::LossStatus::Lost);
250 void ClientWebGLContext::RestoreContext(
251 const webgl::LossStatus requiredStatus) const {
252 if (requiredStatus != mLossStatus) {
253 JsWarning(
254 "restoreContext: Only valid iff context lost with loseContext().");
255 if (!mNextError) {
256 mNextError = LOCAL_GL_INVALID_OPERATION;
258 return;
260 MOZ_RELEASE_ASSERT(mLossStatus == webgl::LossStatus::Lost ||
261 mLossStatus == webgl::LossStatus::LostManually);
263 if (mAwaitingRestore) return;
264 mAwaitingRestore = true;
266 const auto weak = WeakPtr<const ClientWebGLContext>(this);
267 const auto fnRun = [weak]() {
268 const auto strong = RefPtr<const ClientWebGLContext>(weak);
269 if (!strong) return;
270 strong->Event_webglcontextrestored();
272 already_AddRefed<mozilla::CancelableRunnable> runnable =
273 NS_NewCancelableRunnableFunction("enqueue Event_webglcontextrestored",
274 fnRun);
275 NS_DispatchToCurrentThread(std::move(runnable));
278 void ClientWebGLContext::Event_webglcontextrestored() const {
279 mAwaitingRestore = false;
280 mLossStatus = webgl::LossStatus::Ready;
281 mNextError = 0;
283 uvec2 requestSize;
284 if (mCanvasElement) {
285 requestSize = {mCanvasElement->Width(), mCanvasElement->Height()};
286 } else if (mOffscreenCanvas) {
287 requestSize = {mOffscreenCanvas->Width(), mOffscreenCanvas->Height()};
288 } else {
289 MOZ_ASSERT_UNREACHABLE("no HTMLCanvasElement or OffscreenCanvas!");
290 return;
293 if (!requestSize.x) {
294 requestSize.x = 1;
296 if (!requestSize.y) {
297 requestSize.y = 1;
300 const auto mutThis = const_cast<ClientWebGLContext*>(
301 this); // TODO: Make context loss non-mutable.
302 if (!mutThis->CreateHostContext(requestSize)) {
303 mLossStatus = webgl::LossStatus::LostForever;
304 return;
307 mResetLayer = true;
309 (void)DispatchEvent(u"webglcontextrestored"_ns);
312 // ---------
314 void ClientWebGLContext::ThrowEvent_WebGLContextCreationError(
315 const std::string& text) const {
316 nsCString msg;
317 msg.AppendPrintf("Failed to create WebGL context: %s", text.c_str());
318 JsWarning(msg.BeginReading());
320 RefPtr<dom::EventTarget> target = mCanvasElement;
321 if (!target && mOffscreenCanvas) {
322 target = mOffscreenCanvas;
323 } else if (!target) {
324 return;
327 const auto kEventName = u"webglcontextcreationerror"_ns;
329 dom::WebGLContextEventInit eventInit;
330 // eventInit.mCancelable = true; // The spec says this, but it's silly.
331 eventInit.mStatusMessage = NS_ConvertASCIItoUTF16(text.c_str());
333 const RefPtr<dom::WebGLContextEvent> event =
334 dom::WebGLContextEvent::Constructor(target, kEventName, eventInit);
335 event->SetTrusted(true);
337 target->DispatchEvent(*event);
340 // -
342 // If we are running WebGL in this process then call the HostWebGLContext
343 // method directly. Otherwise, dispatch over IPC.
344 template <typename MethodType, MethodType method, typename... Args>
345 void ClientWebGLContext::Run(Args&&... args) const {
346 const auto notLost =
347 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
348 if (IsContextLost()) return;
350 const auto& inProcess = notLost->inProcess;
351 if (inProcess) {
352 return (inProcess.get()->*method)(std::forward<Args>(args)...);
355 const auto& child = notLost->outOfProcess;
357 const auto id = IdByMethod<MethodType, method>();
359 const auto info = webgl::SerializationInfo(id, args...);
360 const auto maybeDest = child->AllocPendingCmdBytes(info.requiredByteCount,
361 info.alignmentOverhead);
362 if (!maybeDest) {
363 JsWarning("Failed to allocate internal command buffer.");
364 OnContextLoss(webgl::ContextLossReason::None);
365 return;
367 const auto& destBytes = *maybeDest;
368 webgl::Serialize(destBytes, id, args...);
371 // -------------------------------------------------------------------------
372 // Client-side helper methods. Dispatch to a Host method.
373 // -------------------------------------------------------------------------
375 #define RPROC(_METHOD) \
376 decltype(&HostWebGLContext::_METHOD), &HostWebGLContext::_METHOD
378 // ------------------------- Composition, etc -------------------------
380 void ClientWebGLContext::OnBeforePaintTransaction() { Present(nullptr); }
382 void ClientWebGLContext::EndComposition() {
383 // Mark ourselves as no longer invalidated.
384 MarkContextClean();
387 // -
389 layers::TextureType ClientWebGLContext::GetTexTypeForSwapChain() const {
390 const RefPtr<layers::ImageBridgeChild> imageBridge =
391 layers::ImageBridgeChild::GetSingleton();
392 return layers::TexTypeForWebgl(imageBridge);
395 void ClientWebGLContext::Present(WebGLFramebufferJS* const xrFb,
396 const bool webvr,
397 const webgl::SwapChainOptions& options) {
398 const auto texType = GetTexTypeForSwapChain();
399 Present(xrFb, texType, webvr, options);
402 // Fill in remote texture ids to SwapChainOptions if async present is enabled.
403 webgl::SwapChainOptions ClientWebGLContext::PrepareAsyncSwapChainOptions(
404 WebGLFramebufferJS* fb, bool webvr,
405 const webgl::SwapChainOptions& options) {
406 // Currently remote texture ids should only be set internally.
407 MOZ_ASSERT(!options.remoteTextureOwnerId.IsValid() &&
408 !options.remoteTextureId.IsValid());
409 auto& ownerId = fb ? fb->mRemoteTextureOwnerId : mRemoteTextureOwnerId;
410 auto& textureId = fb ? fb->mLastRemoteTextureId : mLastRemoteTextureId;
411 // Async present only works when out-of-process. It is not supported in WebVR.
412 // Allow it if it is either forced or if the pref is set.
413 if (!IsContextLost() && !mNotLost->inProcess && !webvr &&
414 (options.forceAsyncPresent ||
415 StaticPrefs::webgl_out_of_process_async_present())) {
416 if (!ownerId) {
417 ownerId = Some(layers::RemoteTextureOwnerId::GetNext());
419 textureId = Some(layers::RemoteTextureId::GetNext());
420 webgl::SwapChainOptions asyncOptions = options;
421 asyncOptions.remoteTextureOwnerId = *ownerId;
422 asyncOptions.remoteTextureId = *textureId;
423 return asyncOptions;
425 // Clear the current remote texture id so that we disable async.
426 textureId = Nothing();
427 return options;
430 void ClientWebGLContext::Present(WebGLFramebufferJS* const xrFb,
431 const layers::TextureType type,
432 const bool webvr,
433 const webgl::SwapChainOptions& options) {
434 if (!mIsCanvasDirty && !xrFb) return;
435 if (!xrFb) {
436 mIsCanvasDirty = false;
438 CancelAutoFlush();
439 webgl::SwapChainOptions asyncOptions =
440 PrepareAsyncSwapChainOptions(xrFb, webvr, options);
441 Run<RPROC(Present)>(xrFb ? xrFb->mId : 0, type, webvr, asyncOptions);
444 void ClientWebGLContext::CopyToSwapChain(
445 WebGLFramebufferJS* const fb, const webgl::SwapChainOptions& options) {
446 CancelAutoFlush();
447 const auto texType = GetTexTypeForSwapChain();
448 webgl::SwapChainOptions asyncOptions =
449 PrepareAsyncSwapChainOptions(fb, false, options);
450 Run<RPROC(CopyToSwapChain)>(fb ? fb->mId : 0, texType, asyncOptions);
453 void ClientWebGLContext::EndOfFrame() {
454 CancelAutoFlush();
455 Run<RPROC(EndOfFrame)>();
458 Maybe<layers::SurfaceDescriptor> ClientWebGLContext::GetFrontBuffer(
459 WebGLFramebufferJS* const fb, bool vr) {
460 const FuncScope funcScope(*this, "<GetFrontBuffer>");
461 if (IsContextLost()) return {};
463 const auto& inProcess = mNotLost->inProcess;
464 if (inProcess) {
465 return inProcess->GetFrontBuffer(fb ? fb->mId : 0, vr);
468 const auto& child = mNotLost->outOfProcess;
469 child->FlushPendingCmds();
471 Maybe<layers::SurfaceDescriptor> ret;
473 // If valid remote texture data was set for async present, then use it.
474 const auto& ownerId = fb ? fb->mRemoteTextureOwnerId : mRemoteTextureOwnerId;
475 const auto& textureId = fb ? fb->mLastRemoteTextureId : mLastRemoteTextureId;
476 if (ownerId && textureId) {
477 if (StaticPrefs::webgl_out_of_process_async_present_force_sync()) {
478 // Request the front buffer from IPDL to cause a sync, even though we
479 // will continue to use the remote texture descriptor after.
480 (void)child->SendGetFrontBuffer(fb ? fb->mId : 0, vr, &ret);
482 return Some(layers::SurfaceDescriptorRemoteTexture(*textureId, *ownerId));
485 if (!child->SendGetFrontBuffer(fb ? fb->mId : 0, vr, &ret)) return {};
487 return ret;
490 Maybe<layers::SurfaceDescriptor> ClientWebGLContext::PresentFrontBuffer(
491 WebGLFramebufferJS* const fb, const layers::TextureType type, bool webvr) {
492 Present(fb, type, webvr);
493 return GetFrontBuffer(fb, webvr);
496 void ClientWebGLContext::ClearVRSwapChain() { Run<RPROC(ClearVRSwapChain)>(); }
498 // -
500 bool ClientWebGLContext::UpdateWebRenderCanvasData(
501 nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
502 CanvasRenderer* renderer = aCanvasData->GetCanvasRenderer();
504 if (!IsContextLost() && !mResetLayer && renderer) {
505 return true;
508 const auto& size = DrawingBufferSize();
510 if (!IsContextLost() && !renderer && mNotLost->mCanvasRenderer &&
511 mNotLost->mCanvasRenderer->GetSize() == gfx::IntSize(size.x, size.y) &&
512 aCanvasData->SetCanvasRenderer(mNotLost->mCanvasRenderer)) {
513 mNotLost->mCanvasRenderer->SetDirty();
514 mResetLayer = false;
515 return true;
518 renderer = aCanvasData->CreateCanvasRenderer();
519 if (!InitializeCanvasRenderer(aBuilder, renderer)) {
520 // Clear CanvasRenderer of WebRenderCanvasData
521 aCanvasData->ClearCanvasRenderer();
522 return false;
525 mNotLost->mCanvasRenderer = renderer;
527 MOZ_ASSERT(renderer);
528 mResetLayer = false;
530 return true;
533 bool ClientWebGLContext::InitializeCanvasRenderer(
534 nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) {
535 const FuncScope funcScope(*this, "<InitializeCanvasRenderer>");
536 if (IsContextLost()) return false;
538 layers::CanvasRendererData data;
539 data.mContext = this;
540 data.mOriginPos = gl::OriginPos::BottomLeft;
542 const auto& options = *mInitialOptions;
543 const auto& size = DrawingBufferSize();
545 if (IsContextLost()) return false;
547 data.mIsOpaque = !options.alpha;
548 data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
549 data.mSize = {size.x, size.y};
551 if (aBuilder->IsPaintingToWindow() && mCanvasElement) {
552 data.mDoPaintCallbacks = true;
555 aRenderer->Initialize(data);
556 aRenderer->SetDirty();
557 return true;
560 void ClientWebGLContext::UpdateCanvasParameters() {
561 if (!mOffscreenCanvas) {
562 return;
565 const auto& options = *mInitialOptions;
566 const auto& size = DrawingBufferSize();
568 mozilla::dom::OffscreenCanvasDisplayData data;
569 data.mOriginPos = gl::OriginPos::BottomLeft;
570 data.mIsOpaque = !options.alpha;
571 data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
572 data.mSize = {size.x, size.y};
573 data.mDoPaintCallbacks = false;
575 mOffscreenCanvas->UpdateDisplayData(data);
578 layers::LayersBackend ClientWebGLContext::GetCompositorBackendType() const {
579 if (mCanvasElement) {
580 return mCanvasElement->GetCompositorBackendType();
581 } else if (mOffscreenCanvas) {
582 return mOffscreenCanvas->GetCompositorBackendType();
585 return layers::LayersBackend::LAYERS_NONE;
588 mozilla::dom::Document* ClientWebGLContext::GetOwnerDoc() const {
589 MOZ_ASSERT(mCanvasElement);
590 if (!mCanvasElement) {
591 return nullptr;
593 return mCanvasElement->OwnerDoc();
596 void ClientWebGLContext::Commit() {
597 if (mOffscreenCanvas) {
598 mOffscreenCanvas->CommitFrameToCompositor();
602 void ClientWebGLContext::GetCanvas(
603 dom::Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& retval) {
604 if (mCanvasElement) {
605 MOZ_RELEASE_ASSERT(!mOffscreenCanvas, "GFX: Canvas is offscreen.");
607 if (mCanvasElement->IsInNativeAnonymousSubtree()) {
608 retval.SetNull();
609 } else {
610 retval.SetValue().SetAsHTMLCanvasElement() = mCanvasElement;
612 } else if (mOffscreenCanvas) {
613 retval.SetValue().SetAsOffscreenCanvas() = mOffscreenCanvas;
614 } else {
615 retval.SetNull();
619 void ClientWebGLContext::GetContextAttributes(
620 dom::Nullable<dom::WebGLContextAttributes>& retval) {
621 retval.SetNull();
622 const FuncScope funcScope(*this, "getContextAttributes");
623 if (IsContextLost()) return;
625 dom::WebGLContextAttributes& result = retval.SetValue();
627 const auto& options = mNotLost->info.options;
629 result.mAlpha.Construct(options.alpha);
630 result.mDepth = options.depth;
631 result.mStencil = options.stencil;
632 result.mAntialias.Construct(options.antialias);
633 result.mPremultipliedAlpha = options.premultipliedAlpha;
634 result.mPreserveDrawingBuffer = options.preserveDrawingBuffer;
635 result.mFailIfMajorPerformanceCaveat = options.failIfMajorPerformanceCaveat;
636 result.mPowerPreference = options.powerPreference;
639 // -----------------------
641 NS_IMETHODIMP
642 ClientWebGLContext::SetDimensions(const int32_t signedWidth,
643 const int32_t signedHeight) {
644 const FuncScope funcScope(*this, "<SetDimensions>");
645 MOZ_ASSERT(mInitialOptions);
647 if (mLossStatus != webgl::LossStatus::Ready) {
648 // Attempted resize of a lost context.
649 return NS_OK;
652 uvec2 size = {static_cast<uint32_t>(signedWidth),
653 static_cast<uint32_t>(signedHeight)};
654 if (!size.x) {
655 size.x = 1;
657 if (!size.y) {
658 size.y = 1;
660 const auto prevRequestedSize = mRequestedSize;
661 mRequestedSize = size;
663 mResetLayer = true; // Always treat this as resize.
665 if (mNotLost) {
666 auto& state = State();
668 auto curSize = prevRequestedSize;
669 if (state.mDrawingBufferSize) {
670 curSize = *state.mDrawingBufferSize;
672 if (size == curSize) return NS_OK; // MUST skip no-op resize
674 state.mDrawingBufferSize = Nothing();
675 Run<RPROC(Resize)>(size);
677 UpdateCanvasParameters();
678 MarkCanvasDirty();
679 return NS_OK;
682 // -
683 // Context (re-)creation
685 if (!CreateHostContext(size)) {
686 return NS_ERROR_FAILURE;
688 return NS_OK;
691 void ClientWebGLContext::ResetBitmap() {
692 const auto size = DrawingBufferSize();
693 Run<RPROC(Resize)>(size); // No-change resize still clears/resets everything.
696 static bool IsWebglOutOfProcessEnabled() {
697 if (StaticPrefs::webgl_out_of_process_force()) {
698 return true;
700 if (!gfx::gfxVars::AllowWebglOop()) {
701 return false;
703 if (!NS_IsMainThread()) {
704 return StaticPrefs::webgl_out_of_process_worker();
706 return StaticPrefs::webgl_out_of_process();
709 static inline bool StartsWith(const std::string& haystack,
710 const std::string& needle) {
711 return haystack.find(needle) == 0;
714 bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
715 const auto pNotLost = std::make_shared<webgl::NotLostData>(*this);
716 auto& notLost = *pNotLost;
718 auto res = [&]() -> Result<Ok, std::string> {
719 auto options = *mInitialOptions;
720 if (StaticPrefs::webgl_disable_fail_if_major_performance_caveat()) {
721 options.failIfMajorPerformanceCaveat = false;
724 if (options.failIfMajorPerformanceCaveat) {
725 const auto backend = GetCompositorBackendType();
726 bool isCompositorSlow = false;
727 isCompositorSlow |= (backend == layers::LayersBackend::LAYERS_WR &&
728 gfx::gfxVars::UseSoftwareWebRender());
730 if (isCompositorSlow) {
731 return Err(
732 "failIfMajorPerformanceCaveat: Compositor is not"
733 " hardware-accelerated.");
737 const bool resistFingerprinting = ShouldResistFingerprinting();
738 const auto principalKey = GetPrincipalHashValue();
739 const auto initDesc = webgl::InitContextDesc{
740 mIsWebGL2, resistFingerprinting, requestedSize, options, principalKey};
742 // -
744 auto useOop = IsWebglOutOfProcessEnabled();
745 if (XRE_IsParentProcess()) {
746 useOop = false;
749 if (!useOop) {
750 notLost.inProcess =
751 HostWebGLContext::Create({this, nullptr}, initDesc, &notLost.info);
752 return Ok();
755 // -
757 ScopedGfxFeatureReporter reporter("IpcWebGL");
759 auto* const cm = gfx::CanvasManagerChild::Get();
760 if (NS_WARN_IF(!cm)) {
761 return Err("!CanvasManagerChild::Get()");
764 RefPtr<dom::WebGLChild> outOfProcess = new dom::WebGLChild(*this);
765 outOfProcess =
766 static_cast<dom::WebGLChild*>(cm->SendPWebGLConstructor(outOfProcess));
767 if (!outOfProcess) {
768 return Err("SendPWebGLConstructor failed");
771 if (!outOfProcess->SendInitialize(initDesc, &notLost.info)) {
772 return Err("WebGL actor Initialize failed");
775 notLost.outOfProcess = outOfProcess;
776 reporter.SetSuccessful();
777 return Ok();
778 }();
779 if (!res.isOk()) {
780 auto str = res.unwrapErr();
781 if (StartsWith(str, "failIfMajorPerformanceCaveat")) {
782 str +=
783 " (about:config override available:"
784 " webgl.disable-fail-if-major-performance-caveat)";
786 notLost.info.error = str;
788 if (!notLost.info.error.empty()) {
789 ThrowEvent_WebGLContextCreationError(notLost.info.error);
790 return false;
792 mNotLost = pNotLost;
793 UpdateCanvasParameters();
794 MarkCanvasDirty();
796 // Init state
797 const auto& limits = Limits();
798 auto& state = State();
799 state.mDefaultTfo = new WebGLTransformFeedbackJS(*this);
800 state.mDefaultVao = new WebGLVertexArrayJS(*this);
802 state.mBoundTfo = state.mDefaultTfo;
803 state.mBoundVao = state.mDefaultVao;
805 (void)state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER];
807 state.mTexUnits.resize(limits.maxTexUnits);
808 state.mBoundUbos.resize(limits.maxUniformBufferBindings);
811 webgl::TypedQuad initVal;
812 const float fData[4] = {0, 0, 0, 1};
813 memcpy(initVal.data.data(), fData, initVal.data.size());
814 state.mGenericVertexAttribs.resize(limits.maxVertexAttribs, initVal);
817 const auto& size = DrawingBufferSize();
818 state.mViewport = {0, 0, static_cast<int32_t>(size.x),
819 static_cast<int32_t>(size.y)};
820 state.mScissor = state.mViewport;
822 if (mIsWebGL2) {
823 // Insert keys to enable slots:
824 (void)state.mBoundBufferByTarget[LOCAL_GL_COPY_READ_BUFFER];
825 (void)state.mBoundBufferByTarget[LOCAL_GL_COPY_WRITE_BUFFER];
826 (void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_PACK_BUFFER];
827 (void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_UNPACK_BUFFER];
828 (void)state.mBoundBufferByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER];
829 (void)state.mBoundBufferByTarget[LOCAL_GL_UNIFORM_BUFFER];
831 (void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED];
832 //(void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE];
833 //// Same slot as ANY_SAMPLES_PASSED.
834 (void)state
835 .mCurrentQueryByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN];
838 return true;
841 // -------
843 uvec2 ClientWebGLContext::DrawingBufferSize() {
844 if (IsContextLost()) return {};
845 const auto notLost =
846 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
847 auto& state = State();
848 auto& size = state.mDrawingBufferSize;
850 if (!size) {
851 const auto& inProcess = mNotLost->inProcess;
852 if (inProcess) {
853 size = Some(inProcess->DrawingBufferSize());
854 } else {
855 const auto& child = mNotLost->outOfProcess;
856 child->FlushPendingCmds();
857 uvec2 actual = {};
858 if (!child->SendDrawingBufferSize(&actual)) return {};
859 size = Some(actual);
863 return *size;
866 void ClientWebGLContext::OnMemoryPressure() {
867 if (IsContextLost()) return;
869 const auto& inProcess = mNotLost->inProcess;
870 if (inProcess) {
871 return inProcess->OnMemoryPressure();
873 const auto& child = mNotLost->outOfProcess;
874 (void)child->SendOnMemoryPressure();
877 NS_IMETHODIMP
878 ClientWebGLContext::SetContextOptions(JSContext* cx,
879 JS::Handle<JS::Value> options,
880 ErrorResult& aRvForDictionaryInit) {
881 if (mInitialOptions && options.isNullOrUndefined()) return NS_OK;
883 dom::WebGLContextAttributes attributes;
884 if (!attributes.Init(cx, options)) {
885 aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED);
886 return NS_ERROR_UNEXPECTED;
889 WebGLContextOptions newOpts;
891 newOpts.stencil = attributes.mStencil;
892 newOpts.depth = attributes.mDepth;
893 newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha;
894 newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer;
895 newOpts.failIfMajorPerformanceCaveat =
896 attributes.mFailIfMajorPerformanceCaveat;
897 newOpts.xrCompatible = attributes.mXrCompatible;
898 newOpts.powerPreference = attributes.mPowerPreference;
899 newOpts.enableDebugRendererInfo =
900 StaticPrefs::webgl_enable_debug_renderer_info();
901 MOZ_ASSERT(mCanvasElement || mOffscreenCanvas);
902 newOpts.shouldResistFingerprinting = ShouldResistFingerprinting();
904 if (attributes.mAlpha.WasPassed()) {
905 newOpts.alpha = attributes.mAlpha.Value();
907 if (attributes.mAntialias.WasPassed()) {
908 newOpts.antialias = attributes.mAntialias.Value();
910 newOpts.ignoreColorSpace = true;
911 if (attributes.mColorSpace.WasPassed()) {
912 newOpts.ignoreColorSpace = false;
913 newOpts.colorSpace = attributes.mColorSpace.Value();
916 // Don't do antialiasing if we've disabled MSAA.
917 if (!StaticPrefs::webgl_msaa_samples()) {
918 newOpts.antialias = false;
921 // -
923 if (mInitialOptions && *mInitialOptions != newOpts) {
924 // Err if the options asked for aren't the same as what they were
925 // originally.
926 return NS_ERROR_FAILURE;
929 mXRCompatible = attributes.mXrCompatible;
931 mInitialOptions.emplace(newOpts);
932 return NS_OK;
935 void ClientWebGLContext::DidRefresh() { Run<RPROC(DidRefresh)>(); }
937 already_AddRefed<gfx::SourceSurface> ClientWebGLContext::GetSurfaceSnapshot(
938 gfxAlphaType* const out_alphaType) {
939 const FuncScope funcScope(*this, "<GetSurfaceSnapshot>");
940 if (IsContextLost()) return nullptr;
941 const auto notLost =
942 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
944 auto ret = BackBufferSnapshot();
945 if (!ret) return nullptr;
947 // -
949 const auto& options = mNotLost->info.options;
951 auto srcAlphaType = gfxAlphaType::Opaque;
952 if (options.alpha) {
953 if (options.premultipliedAlpha) {
954 srcAlphaType = gfxAlphaType::Premult;
955 } else {
956 srcAlphaType = gfxAlphaType::NonPremult;
960 if (out_alphaType) {
961 *out_alphaType = srcAlphaType;
962 } else {
963 // Expects Opaque or Premult
964 if (srcAlphaType == gfxAlphaType::NonPremult) {
965 const gfx::DataSourceSurface::ScopedMap map(
966 ret, gfx::DataSourceSurface::READ_WRITE);
967 MOZ_RELEASE_ASSERT(map.IsMapped(), "Failed to map snapshot surface!");
969 const auto& size = ret->GetSize();
970 const auto format = ret->GetFormat();
971 bool rv =
972 gfx::PremultiplyData(map.GetData(), map.GetStride(), format,
973 map.GetData(), map.GetStride(), format, size);
974 MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
978 return ret.forget();
981 RefPtr<gfx::SourceSurface> ClientWebGLContext::GetFrontBufferSnapshot(
982 const bool requireAlphaPremult) {
983 const FuncScope funcScope(*this, "<GetSurfaceSnapshot>");
984 if (IsContextLost()) return nullptr;
985 const auto notLost =
986 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
988 const auto& options = mNotLost->info.options;
990 const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
991 : gfx::SurfaceFormat::B8G8R8X8;
993 const auto fnNewSurf = [&](const uvec2 size) {
994 const auto stride = size.x * 4;
995 return RefPtr<gfx::DataSourceSurface>(
996 gfx::Factory::CreateDataSourceSurfaceWithStride({size.x, size.y},
997 surfFormat, stride,
998 /*zero=*/true));
1001 const auto& inProcess = mNotLost->inProcess;
1002 if (inProcess) {
1003 const auto maybeSize = inProcess->FrontBufferSnapshotInto({});
1004 if (!maybeSize) return nullptr;
1005 const auto& surfSize = *maybeSize;
1006 const auto stride = surfSize.x * 4;
1007 const auto byteSize = stride * surfSize.y;
1008 const auto surf = fnNewSurf(surfSize);
1009 if (!surf) return nullptr;
1011 const gfx::DataSourceSurface::ScopedMap map(
1012 surf, gfx::DataSourceSurface::READ_WRITE);
1013 if (!map.IsMapped()) {
1014 MOZ_ASSERT(false);
1015 return nullptr;
1017 MOZ_RELEASE_ASSERT(map.GetStride() == static_cast<int64_t>(stride));
1018 auto range = Range<uint8_t>{map.GetData(), byteSize};
1019 if (!inProcess->FrontBufferSnapshotInto(Some(range))) {
1020 gfxCriticalNote << "ClientWebGLContext::GetFrontBufferSnapshot: "
1021 "FrontBufferSnapshotInto(some) failed after "
1022 "FrontBufferSnapshotInto(none)";
1023 return nullptr;
1025 if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
1026 bool rv = gfx::PremultiplyData(
1027 map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
1028 map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1029 surf->GetSize());
1030 MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
1031 } else {
1032 bool rv = gfx::SwizzleData(
1033 map.GetData(), map.GetStride(), gfx::SurfaceFormat::R8G8B8A8,
1034 map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1035 surf->GetSize());
1036 MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
1039 return surf;
1041 const auto& child = mNotLost->outOfProcess;
1042 child->FlushPendingCmds();
1043 webgl::FrontBufferSnapshotIpc res;
1044 if (!child->SendGetFrontBufferSnapshot(&res)) {
1045 res = {};
1047 if (!res.shmem) return nullptr;
1049 const auto& surfSize = res.surfSize;
1050 const webgl::RaiiShmem shmem{child, res.shmem.ref()};
1051 if (!shmem) return nullptr;
1052 const auto& shmemBytes = shmem.ByteRange();
1053 if (!surfSize.x) return nullptr; // Zero means failure.
1055 const auto stride = surfSize.x * 4;
1056 const auto byteSize = stride * surfSize.y;
1058 const auto surf = fnNewSurf(surfSize);
1059 if (!surf) return nullptr;
1062 const gfx::DataSourceSurface::ScopedMap map(
1063 surf, gfx::DataSourceSurface::READ_WRITE);
1064 if (!map.IsMapped()) {
1065 MOZ_ASSERT(false);
1066 return nullptr;
1068 MOZ_RELEASE_ASSERT(shmemBytes.length() == byteSize);
1069 if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
1070 bool rv = gfx::PremultiplyData(
1071 shmemBytes.begin().get(), stride, gfx::SurfaceFormat::R8G8B8A8,
1072 map.GetData(), map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1073 surf->GetSize());
1074 MOZ_RELEASE_ASSERT(rv, "PremultiplyData failed!");
1075 } else {
1076 bool rv = gfx::SwizzleData(shmemBytes.begin().get(), stride,
1077 gfx::SurfaceFormat::R8G8B8A8, map.GetData(),
1078 map.GetStride(), gfx::SurfaceFormat::B8G8R8A8,
1079 surf->GetSize());
1080 MOZ_RELEASE_ASSERT(rv, "SwizzleData failed!");
1083 return surf;
1086 RefPtr<gfx::DataSourceSurface> ClientWebGLContext::BackBufferSnapshot() {
1087 if (IsContextLost()) return nullptr;
1088 const auto notLost =
1089 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
1091 const auto& options = mNotLost->info.options;
1092 const auto& state = State();
1094 const auto drawFbWas = state.mBoundDrawFb;
1095 const auto readFbWas = state.mBoundReadFb;
1096 const auto pboWas =
1097 Find(state.mBoundBufferByTarget, LOCAL_GL_PIXEL_PACK_BUFFER);
1099 const auto size = DrawingBufferSize();
1101 // -
1103 BindFramebuffer(LOCAL_GL_FRAMEBUFFER, nullptr);
1104 if (pboWas) {
1105 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
1108 auto reset = MakeScopeExit([&] {
1109 if (drawFbWas == readFbWas) {
1110 BindFramebuffer(LOCAL_GL_FRAMEBUFFER, drawFbWas);
1111 } else {
1112 BindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, drawFbWas);
1113 BindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, readFbWas);
1115 if (pboWas) {
1116 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
1120 const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
1121 : gfx::SurfaceFormat::B8G8R8X8;
1122 const auto stride = size.x * 4;
1123 RefPtr<gfx::DataSourceSurface> surf =
1124 gfx::Factory::CreateDataSourceSurfaceWithStride(
1125 {size.x, size.y}, surfFormat, stride, /*zero=*/true);
1126 if (NS_WARN_IF(!surf)) {
1127 // Was this an OOM or alloc-limit? (500MB is our default resource size
1128 // limit)
1129 surf = gfx::Factory::CreateDataSourceSurfaceWithStride({1, 1}, surfFormat,
1130 4, /*zero=*/true);
1131 if (!surf) {
1132 // Still failed for a 1x1 size.
1133 gfxCriticalError() << "CreateDataSourceSurfaceWithStride(surfFormat="
1134 << surfFormat << ") failed.";
1136 return nullptr;
1140 const gfx::DataSourceSurface::ScopedMap map(
1141 surf, gfx::DataSourceSurface::READ_WRITE);
1142 if (!map.IsMapped()) {
1143 MOZ_ASSERT(false);
1144 return nullptr;
1146 MOZ_ASSERT(static_cast<uint32_t>(map.GetStride()) == stride);
1148 const auto desc = webgl::ReadPixelsDesc{{0, 0}, size};
1149 const auto range = Range<uint8_t>(map.GetData(), stride * size.y);
1150 if (!DoReadPixels(desc, range)) return nullptr;
1152 const auto begin = range.begin().get();
1154 std::vector<uint8_t> temp;
1155 temp.resize(stride);
1156 for (const auto i : IntegerRange(size.y / 2)) {
1157 const auto top = begin + stride * i;
1158 const auto bottom = begin + stride * (size.y - 1 - i);
1159 memcpy(temp.data(), top, stride);
1160 memcpy(top, bottom, stride);
1161 gfxUtils::ConvertBGRAtoRGBA(top, stride);
1163 memcpy(bottom, temp.data(), stride);
1164 gfxUtils::ConvertBGRAtoRGBA(bottom, stride);
1167 if (size.y % 2) {
1168 const auto middle = begin + stride * (size.y / 2);
1169 gfxUtils::ConvertBGRAtoRGBA(middle, stride);
1173 return surf;
1176 UniquePtr<uint8_t[]> ClientWebGLContext::GetImageBuffer(int32_t* out_format) {
1177 *out_format = 0;
1179 // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
1180 gfxAlphaType any;
1181 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
1182 if (!snapshot) return nullptr;
1184 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
1186 const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;
1187 return gfxUtils::GetImageBuffer(dataSurface, premultAlpha, out_format);
1190 NS_IMETHODIMP
1191 ClientWebGLContext::GetInputStream(const char* mimeType,
1192 const nsAString& encoderOptions,
1193 nsIInputStream** out_stream) {
1194 // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
1195 gfxAlphaType any;
1196 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
1197 if (!snapshot) return NS_ERROR_FAILURE;
1199 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
1200 const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;
1201 return gfxUtils::GetInputStream(dataSurface, premultAlpha, mimeType,
1202 encoderOptions, out_stream);
1205 // ------------------------- Client WebGL Objects -------------------------
1206 // ------------------------- Create/Destroy/Is -------------------------
1208 template <typename T>
1209 static already_AddRefed<T> AsAddRefed(T* ptr) {
1210 RefPtr<T> rp = ptr;
1211 return rp.forget();
1214 template <typename T>
1215 static RefPtr<T> AsRefPtr(T* ptr) {
1216 return {ptr};
1219 already_AddRefed<WebGLBufferJS> ClientWebGLContext::CreateBuffer() const {
1220 const FuncScope funcScope(*this, "createBuffer");
1221 if (IsContextLost()) return nullptr;
1223 auto ret = AsRefPtr(new WebGLBufferJS(*this));
1224 Run<RPROC(CreateBuffer)>(ret->mId);
1225 return ret.forget();
1228 already_AddRefed<WebGLFramebufferJS> ClientWebGLContext::CreateFramebuffer()
1229 const {
1230 const FuncScope funcScope(*this, "createFramebuffer");
1231 if (IsContextLost()) return nullptr;
1233 auto ret = AsRefPtr(new WebGLFramebufferJS(*this));
1234 Run<RPROC(CreateFramebuffer)>(ret->mId);
1235 return ret.forget();
1238 already_AddRefed<WebGLFramebufferJS>
1239 ClientWebGLContext::CreateOpaqueFramebuffer(
1240 const webgl::OpaqueFramebufferOptions& options) const {
1241 const FuncScope funcScope(*this, "createOpaqueFramebuffer");
1242 if (IsContextLost()) return nullptr;
1244 auto ret = AsRefPtr(new WebGLFramebufferJS(*this, true));
1246 const auto& inProcess = mNotLost->inProcess;
1247 if (inProcess) {
1248 if (!inProcess->CreateOpaqueFramebuffer(ret->mId, options)) {
1249 ret = nullptr;
1251 return ret.forget();
1253 const auto& child = mNotLost->outOfProcess;
1254 child->FlushPendingCmds();
1255 bool ok = false;
1256 if (!child->SendCreateOpaqueFramebuffer(ret->mId, options, &ok))
1257 return nullptr;
1258 if (!ok) return nullptr;
1259 return ret.forget();
1262 already_AddRefed<WebGLProgramJS> ClientWebGLContext::CreateProgram() const {
1263 const FuncScope funcScope(*this, "createProgram");
1264 if (IsContextLost()) return nullptr;
1266 auto ret = AsRefPtr(new WebGLProgramJS(*this));
1267 Run<RPROC(CreateProgram)>(ret->mId);
1268 return ret.forget();
1271 already_AddRefed<WebGLQueryJS> ClientWebGLContext::CreateQuery() const {
1272 const FuncScope funcScope(*this, "createQuery");
1273 if (IsContextLost()) return nullptr;
1275 auto ret = AsRefPtr(new WebGLQueryJS(*this));
1276 Run<RPROC(CreateQuery)>(ret->mId);
1277 return ret.forget();
1280 already_AddRefed<WebGLRenderbufferJS> ClientWebGLContext::CreateRenderbuffer()
1281 const {
1282 const FuncScope funcScope(*this, "createRenderbuffer");
1283 if (IsContextLost()) return nullptr;
1285 auto ret = AsRefPtr(new WebGLRenderbufferJS(*this));
1286 Run<RPROC(CreateRenderbuffer)>(ret->mId);
1287 return ret.forget();
1290 already_AddRefed<WebGLSamplerJS> ClientWebGLContext::CreateSampler() const {
1291 const FuncScope funcScope(*this, "createSampler");
1292 if (IsContextLost()) return nullptr;
1294 auto ret = AsRefPtr(new WebGLSamplerJS(*this));
1295 Run<RPROC(CreateSampler)>(ret->mId);
1296 return ret.forget();
1299 already_AddRefed<WebGLShaderJS> ClientWebGLContext::CreateShader(
1300 const GLenum type) const {
1301 const FuncScope funcScope(*this, "createShader");
1302 if (IsContextLost()) return nullptr;
1304 switch (type) {
1305 case LOCAL_GL_VERTEX_SHADER:
1306 case LOCAL_GL_FRAGMENT_SHADER:
1307 break;
1308 default:
1309 EnqueueError_ArgEnum("type", type);
1310 return nullptr;
1313 auto ret = AsRefPtr(new WebGLShaderJS(*this, type));
1314 Run<RPROC(CreateShader)>(ret->mId, ret->mType);
1315 return ret.forget();
1318 already_AddRefed<WebGLSyncJS> ClientWebGLContext::FenceSync(
1319 const GLenum condition, const GLbitfield flags) const {
1320 const FuncScope funcScope(*this, "fenceSync");
1321 if (IsContextLost()) return nullptr;
1323 if (condition != LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE) {
1324 EnqueueError_ArgEnum("condition", condition);
1325 return nullptr;
1328 if (flags) {
1329 EnqueueError(LOCAL_GL_INVALID_VALUE, "`flags` must be 0.");
1330 return nullptr;
1333 auto ret = AsRefPtr(new WebGLSyncJS(*this));
1334 Run<RPROC(CreateSync)>(ret->mId);
1336 auto& availRunnable = EnsureAvailabilityRunnable();
1337 availRunnable.mSyncs.push_back(ret.get());
1338 ret->mCanBeAvailable = false;
1340 return ret.forget();
1343 already_AddRefed<WebGLTextureJS> ClientWebGLContext::CreateTexture() const {
1344 const FuncScope funcScope(*this, "createTexture");
1345 if (IsContextLost()) return nullptr;
1347 auto ret = AsRefPtr(new WebGLTextureJS(*this));
1348 Run<RPROC(CreateTexture)>(ret->mId);
1349 return ret.forget();
1352 already_AddRefed<WebGLTransformFeedbackJS>
1353 ClientWebGLContext::CreateTransformFeedback() const {
1354 const FuncScope funcScope(*this, "createTransformFeedback");
1355 if (IsContextLost()) return nullptr;
1357 auto ret = AsRefPtr(new WebGLTransformFeedbackJS(*this));
1358 Run<RPROC(CreateTransformFeedback)>(ret->mId);
1359 return ret.forget();
1362 already_AddRefed<WebGLVertexArrayJS> ClientWebGLContext::CreateVertexArray()
1363 const {
1364 const FuncScope funcScope(*this, "createVertexArray");
1365 if (IsContextLost()) return nullptr;
1367 auto ret = AsRefPtr(new WebGLVertexArrayJS(*this));
1368 Run<RPROC(CreateVertexArray)>(ret->mId);
1369 return ret.forget();
1372 // -
1374 static bool ValidateOrSkipForDelete(const ClientWebGLContext& context,
1375 const webgl::ObjectJS* const obj) {
1376 if (!obj) return false;
1377 if (!obj->ValidateForContext(context, "obj")) return false;
1378 if (obj->IsDeleted()) return false;
1379 return true;
1382 void ClientWebGLContext::DeleteBuffer(WebGLBufferJS* const obj) {
1383 const FuncScope funcScope(*this, "deleteBuffer");
1384 if (IsContextLost()) return;
1385 if (!ValidateOrSkipForDelete(*this, obj)) return;
1386 auto& state = State();
1388 // Unbind from all bind points and bound containers
1390 // UBOs
1391 for (const auto i : IntegerRange(state.mBoundUbos.size())) {
1392 if (state.mBoundUbos[i] == obj) {
1393 BindBufferBase(LOCAL_GL_UNIFORM_BUFFER, i, nullptr);
1397 // TFO only if not active
1398 if (!state.mBoundTfo->mActiveOrPaused) {
1399 const auto& buffers = state.mBoundTfo->mAttribBuffers;
1400 for (const auto i : IntegerRange(buffers.size())) {
1401 if (buffers[i] == obj) {
1402 BindBufferBase(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, i, nullptr);
1407 // Generic/global bind points
1408 for (const auto& pair : state.mBoundBufferByTarget) {
1409 if (pair.second == obj) {
1410 BindBuffer(pair.first, nullptr);
1414 // VAO attachments
1415 if (state.mBoundVao->mIndexBuffer == obj) {
1416 BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, nullptr);
1419 const auto& vaoBuffers = state.mBoundVao->mAttribBuffers;
1420 Maybe<WebGLBufferJS*> toRestore;
1421 for (const auto i : IntegerRange(vaoBuffers.size())) {
1422 if (vaoBuffers[i] == obj) {
1423 if (!toRestore) {
1424 toRestore =
1425 Some(state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER].get());
1426 if (*toRestore) {
1427 BindBuffer(LOCAL_GL_ARRAY_BUFFER, nullptr);
1430 VertexAttribPointer(i, 4, LOCAL_GL_FLOAT, false, 0, 0);
1433 if (toRestore && *toRestore) {
1434 BindBuffer(LOCAL_GL_ARRAY_BUFFER, *toRestore);
1437 // -
1439 obj->mDeleteRequested = true;
1440 Run<RPROC(DeleteBuffer)>(obj->mId);
1443 void ClientWebGLContext::DeleteFramebuffer(WebGLFramebufferJS* const obj,
1444 bool canDeleteOpaque) {
1445 const FuncScope funcScope(*this, "deleteFramebuffer");
1446 if (IsContextLost()) return;
1447 if (!ValidateOrSkipForDelete(*this, obj)) return;
1448 if (!canDeleteOpaque && obj->mOpaque) {
1449 EnqueueError(
1450 LOCAL_GL_INVALID_OPERATION,
1451 "An opaque framebuffer's attachments cannot be inspected or changed.");
1452 return;
1454 const auto& state = State();
1456 // Unbind
1457 const auto fnDetach = [&](const GLenum target,
1458 const WebGLFramebufferJS* const fb) {
1459 if (obj == fb) {
1460 BindFramebuffer(target, nullptr);
1463 if (state.mBoundDrawFb == state.mBoundReadFb) {
1464 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1465 } else {
1466 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1467 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1470 obj->mDeleteRequested = true;
1471 Run<RPROC(DeleteFramebuffer)>(obj->mId);
1474 void ClientWebGLContext::DeleteProgram(WebGLProgramJS* const obj) const {
1475 const FuncScope funcScope(*this, "deleteProgram");
1476 if (IsContextLost()) return;
1477 if (!ValidateOrSkipForDelete(*this, obj)) return;
1479 // Don't unbind
1481 obj->mKeepAlive = nullptr;
1484 webgl::ProgramKeepAlive::~ProgramKeepAlive() {
1485 if (!mParent) return;
1486 const auto& context = mParent->Context();
1487 if (!context) return;
1488 context->DoDeleteProgram(*mParent);
1491 void ClientWebGLContext::DoDeleteProgram(WebGLProgramJS& obj) const {
1492 obj.mNextLink_Shaders = {};
1493 Run<RPROC(DeleteProgram)>(obj.mId);
1496 static GLenum QuerySlotTarget(const GLenum specificTarget);
1498 void ClientWebGLContext::DeleteQuery(WebGLQueryJS* const obj) {
1499 const FuncScope funcScope(*this, "deleteQuery");
1500 if (IsContextLost()) return;
1501 if (!ValidateOrSkipForDelete(*this, obj)) return;
1502 const auto& state = State();
1504 // Unbind if current
1506 if (obj->mTarget) {
1507 // Despite mTarget being set, we may not have called BeginQuery on this
1508 // object. QueryCounter may also set mTarget.
1509 const auto slotTarget = QuerySlotTarget(obj->mTarget);
1510 const auto curForTarget =
1511 MaybeFind(state.mCurrentQueryByTarget, slotTarget);
1513 if (curForTarget && *curForTarget == obj) {
1514 EndQuery(obj->mTarget);
1518 obj->mDeleteRequested = true;
1519 Run<RPROC(DeleteQuery)>(obj->mId);
1522 void ClientWebGLContext::DeleteRenderbuffer(WebGLRenderbufferJS* const obj) {
1523 const FuncScope funcScope(*this, "deleteRenderbuffer");
1524 if (IsContextLost()) return;
1525 if (!ValidateOrSkipForDelete(*this, obj)) return;
1526 const auto& state = State();
1528 // Unbind
1529 if (state.mBoundRb == obj) {
1530 BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nullptr);
1533 // Unbind from bound FBs
1534 const auto fnDetach = [&](const GLenum target,
1535 const WebGLFramebufferJS* const fb) {
1536 if (!fb) return;
1537 for (const auto& pair : fb->mAttachments) {
1538 if (pair.second.rb == obj) {
1539 FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
1540 nullptr);
1544 if (state.mBoundDrawFb == state.mBoundReadFb) {
1545 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1546 } else {
1547 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1548 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1551 obj->mDeleteRequested = true;
1552 Run<RPROC(DeleteRenderbuffer)>(obj->mId);
1555 void ClientWebGLContext::DeleteSampler(WebGLSamplerJS* const obj) {
1556 const FuncScope funcScope(*this, "deleteSampler");
1557 if (IsContextLost()) return;
1558 if (!ValidateOrSkipForDelete(*this, obj)) return;
1559 const auto& state = State();
1561 // Unbind
1562 for (const auto i : IntegerRange(state.mTexUnits.size())) {
1563 if (state.mTexUnits[i].sampler == obj) {
1564 BindSampler(i, nullptr);
1568 obj->mDeleteRequested = true;
1569 Run<RPROC(DeleteSampler)>(obj->mId);
1572 void ClientWebGLContext::DeleteShader(WebGLShaderJS* const obj) const {
1573 const FuncScope funcScope(*this, "deleteShader");
1574 if (IsContextLost()) return;
1575 if (!ValidateOrSkipForDelete(*this, obj)) return;
1577 // Don't unbind
1579 obj->mKeepAlive = nullptr;
1582 webgl::ShaderKeepAlive::~ShaderKeepAlive() {
1583 if (!mParent) return;
1584 const auto& context = mParent->Context();
1585 if (!context) return;
1586 context->DoDeleteShader(*mParent);
1589 void ClientWebGLContext::DoDeleteShader(const WebGLShaderJS& obj) const {
1590 Run<RPROC(DeleteShader)>(obj.mId);
1593 void ClientWebGLContext::DeleteSync(WebGLSyncJS* const obj) const {
1594 const FuncScope funcScope(*this, "deleteSync");
1595 if (IsContextLost()) return;
1596 if (!ValidateOrSkipForDelete(*this, obj)) return;
1598 // Nothing to unbind
1600 obj->mDeleteRequested = true;
1601 Run<RPROC(DeleteSync)>(obj->mId);
1604 void ClientWebGLContext::DeleteTexture(WebGLTextureJS* const obj) {
1605 const FuncScope funcScope(*this, "deleteTexture");
1606 if (IsContextLost()) return;
1607 if (!ValidateOrSkipForDelete(*this, obj)) return;
1608 auto& state = State();
1610 // Unbind
1611 const auto& target = obj->mTarget;
1612 if (target) {
1613 // Unbind from tex units
1614 Maybe<uint32_t> restoreTexUnit;
1615 for (const auto i : IntegerRange(state.mTexUnits.size())) {
1616 if (state.mTexUnits[i].texByTarget[target] == obj) {
1617 if (!restoreTexUnit) {
1618 restoreTexUnit = Some(state.mActiveTexUnit);
1620 ActiveTexture(LOCAL_GL_TEXTURE0 + i);
1621 BindTexture(target, nullptr);
1624 if (restoreTexUnit) {
1625 ActiveTexture(LOCAL_GL_TEXTURE0 + *restoreTexUnit);
1628 // Unbind from bound FBs
1629 const auto fnDetach = [&](const GLenum target,
1630 const WebGLFramebufferJS* const fb) {
1631 if (!fb) return;
1632 for (const auto& pair : fb->mAttachments) {
1633 if (pair.second.tex == obj) {
1634 FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
1635 nullptr);
1639 if (state.mBoundDrawFb == state.mBoundReadFb) {
1640 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1641 } else {
1642 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1643 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1647 obj->mDeleteRequested = true;
1648 Run<RPROC(DeleteTexture)>(obj->mId);
1651 void ClientWebGLContext::DeleteTransformFeedback(
1652 WebGLTransformFeedbackJS* const obj) {
1653 const FuncScope funcScope(*this, "deleteTransformFeedback");
1654 if (IsContextLost()) return;
1655 if (!ValidateOrSkipForDelete(*this, obj)) return;
1656 const auto& state = State();
1658 if (obj->mActiveOrPaused) {
1659 EnqueueError(LOCAL_GL_INVALID_OPERATION,
1660 "Transform Feedback object still active or paused.");
1661 return;
1664 // Unbind
1665 if (state.mBoundTfo == obj) {
1666 BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
1669 obj->mDeleteRequested = true;
1670 Run<RPROC(DeleteTransformFeedback)>(obj->mId);
1673 void ClientWebGLContext::DeleteVertexArray(WebGLVertexArrayJS* const obj) {
1674 const FuncScope funcScope(*this, "deleteVertexArray");
1675 if (IsContextLost()) return;
1676 if (!ValidateOrSkipForDelete(*this, obj)) return;
1677 const auto& state = State();
1679 // Unbind
1680 if (state.mBoundVao == obj) {
1681 BindVertexArray(nullptr);
1684 obj->mDeleteRequested = true;
1685 Run<RPROC(DeleteVertexArray)>(obj->mId);
1688 // -
1690 bool ClientWebGLContext::IsBuffer(const WebGLBufferJS* const obj) const {
1691 const FuncScope funcScope(*this, "isBuffer");
1692 if (IsContextLost()) return false;
1694 return obj && obj->IsUsable(*this) &&
1695 obj->mKind != webgl::BufferKind::Undefined;
1698 bool ClientWebGLContext::IsFramebuffer(
1699 const WebGLFramebufferJS* const obj) const {
1700 const FuncScope funcScope(*this, "isFramebuffer");
1701 if (IsContextLost()) return false;
1703 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1706 bool ClientWebGLContext::IsProgram(const WebGLProgramJS* const obj) const {
1707 const FuncScope funcScope(*this, "isProgram");
1708 if (IsContextLost()) return false;
1710 return obj && obj->IsUsable(*this);
1713 bool ClientWebGLContext::IsQuery(const WebGLQueryJS* const obj) const {
1714 const FuncScope funcScope(*this, "isQuery");
1715 if (IsContextLost()) return false;
1717 return obj && obj->IsUsable(*this) && obj->mTarget;
1720 bool ClientWebGLContext::IsRenderbuffer(
1721 const WebGLRenderbufferJS* const obj) const {
1722 const FuncScope funcScope(*this, "isRenderbuffer");
1723 if (IsContextLost()) return false;
1725 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1728 bool ClientWebGLContext::IsSampler(const WebGLSamplerJS* const obj) const {
1729 const FuncScope funcScope(*this, "isSampler");
1730 if (IsContextLost()) return false;
1732 return obj && obj->IsUsable(*this);
1735 bool ClientWebGLContext::IsShader(const WebGLShaderJS* const obj) const {
1736 const FuncScope funcScope(*this, "isShader");
1737 if (IsContextLost()) return false;
1739 return obj && obj->IsUsable(*this);
1742 bool ClientWebGLContext::IsSync(const WebGLSyncJS* const obj) const {
1743 const FuncScope funcScope(*this, "isSync");
1744 if (IsContextLost()) return false;
1746 return obj && obj->IsUsable(*this);
1749 bool ClientWebGLContext::IsTexture(const WebGLTextureJS* const obj) const {
1750 const FuncScope funcScope(*this, "isTexture");
1751 if (IsContextLost()) return false;
1753 return obj && obj->IsUsable(*this) && obj->mTarget;
1756 bool ClientWebGLContext::IsTransformFeedback(
1757 const WebGLTransformFeedbackJS* const obj) const {
1758 const FuncScope funcScope(*this, "isTransformFeedback");
1759 if (IsContextLost()) return false;
1761 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1764 bool ClientWebGLContext::IsVertexArray(
1765 const WebGLVertexArrayJS* const obj) const {
1766 const FuncScope funcScope(*this, "isVertexArray");
1767 if (IsContextLost()) return false;
1769 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1772 // ------------------------- GL State -------------------------
1774 void ClientWebGLContext::SetEnabledI(GLenum cap, Maybe<GLuint> i,
1775 bool val) const {
1776 Run<RPROC(SetEnabled)>(cap, i, val);
1779 bool ClientWebGLContext::IsEnabled(GLenum cap) const {
1780 const FuncScope funcScope(*this, "isEnabled");
1781 if (IsContextLost()) return false;
1783 const auto& inProcess = mNotLost->inProcess;
1784 if (inProcess) {
1785 return inProcess->IsEnabled(cap);
1787 const auto& child = mNotLost->outOfProcess;
1788 child->FlushPendingCmds();
1789 bool ret = {};
1790 if (!child->SendIsEnabled(cap, &ret)) return false;
1791 return ret;
1794 void ClientWebGLContext::GetInternalformatParameter(
1795 JSContext* cx, GLenum target, GLenum internalformat, GLenum pname,
1796 JS::MutableHandle<JS::Value> retval, ErrorResult& rv) {
1797 const FuncScope funcScope(*this, "getInternalformatParameter");
1798 retval.set(JS::NullValue());
1799 const auto notLost =
1800 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
1801 if (IsContextLost()) return;
1803 const auto& inProcessContext = notLost->inProcess;
1804 Maybe<std::vector<int32_t>> maybe;
1805 if (inProcessContext) {
1806 maybe = inProcessContext->GetInternalformatParameter(target, internalformat,
1807 pname);
1808 } else {
1809 const auto& child = notLost->outOfProcess;
1810 child->FlushPendingCmds();
1811 if (!child->SendGetInternalformatParameter(target, internalformat, pname,
1812 &maybe)) {
1813 return;
1817 if (!maybe) {
1818 return;
1820 // zero-length array indicates out-of-memory
1821 JSObject* obj =
1822 dom::Int32Array::Create(cx, this, maybe->size(), maybe->data());
1823 if (!obj) {
1824 rv = NS_ERROR_OUT_OF_MEMORY;
1826 retval.setObjectOrNull(obj);
1829 static JS::Value StringValue(JSContext* cx, const std::string& str,
1830 ErrorResult& er) {
1831 JSString* jsStr = JS_NewStringCopyN(cx, str.data(), str.size());
1832 if (!jsStr) {
1833 er.Throw(NS_ERROR_OUT_OF_MEMORY);
1834 return JS::NullValue();
1837 return JS::StringValue(jsStr);
1840 template <typename T>
1841 bool ToJSValueOrNull(JSContext* const cx, const RefPtr<T>& ptr,
1842 JS::MutableHandle<JS::Value> retval) {
1843 if (!ptr) {
1844 retval.set(JS::NullValue());
1845 return true;
1847 return dom::ToJSValue(cx, ptr, retval);
1850 template <typename T, typename U, typename S>
1851 static JS::Value CreateAs(JSContext* cx, nsWrapperCache* creator, const S& src,
1852 ErrorResult& rv) {
1853 const auto obj =
1854 T::Create(cx, creator, src.size(), reinterpret_cast<U>(src.data()));
1855 if (!obj) {
1856 rv = NS_ERROR_OUT_OF_MEMORY;
1858 return JS::ObjectOrNullValue(obj);
1861 template <typename T, typename S>
1862 static JS::Value Create(JSContext* cx, nsWrapperCache* creator, const S& src,
1863 ErrorResult& rv) {
1864 return CreateAs<T, decltype(&src[0]), S>(cx, creator, src, rv);
1867 Maybe<double> ClientWebGLContext::GetNumber(const GLenum pname) {
1868 MOZ_ASSERT(!IsContextLost());
1870 const auto& inProcess = mNotLost->inProcess;
1871 if (inProcess) {
1872 return inProcess->GetNumber(pname);
1875 const auto& child = mNotLost->outOfProcess;
1876 child->FlushPendingCmds();
1878 Maybe<double> ret;
1879 if (!child->SendGetNumber(pname, &ret)) {
1880 ret.reset();
1882 return ret;
1885 Maybe<std::string> ClientWebGLContext::GetString(const GLenum pname) {
1886 MOZ_ASSERT(!IsContextLost());
1888 const auto& inProcess = mNotLost->inProcess;
1889 if (inProcess) {
1890 return inProcess->GetString(pname);
1893 const auto& child = mNotLost->outOfProcess;
1894 child->FlushPendingCmds();
1896 Maybe<std::string> ret;
1897 if (!child->SendGetString(pname, &ret)) {
1898 ret.reset();
1900 return ret;
1903 void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
1904 JS::MutableHandle<JS::Value> retval,
1905 ErrorResult& rv, const bool debug) {
1906 retval.set(JS::NullValue());
1907 const FuncScope funcScope(*this, "getParameter");
1908 if (IsContextLost()) return;
1909 const auto& limits = Limits();
1910 const auto& state = State();
1912 // -
1914 const auto fnSetRetval_Buffer = [&](const GLenum target) {
1915 const auto buffer = *MaybeFind(state.mBoundBufferByTarget, target);
1916 (void)ToJSValueOrNull(cx, buffer, retval);
1918 const auto fnSetRetval_Tex = [&](const GLenum texTarget) {
1919 const auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
1920 const auto tex = Find(texUnit.texByTarget, texTarget, nullptr);
1921 (void)ToJSValueOrNull(cx, tex, retval);
1924 switch (pname) {
1925 case LOCAL_GL_ARRAY_BUFFER_BINDING:
1926 fnSetRetval_Buffer(LOCAL_GL_ARRAY_BUFFER);
1927 return;
1929 case LOCAL_GL_CURRENT_PROGRAM:
1930 (void)ToJSValueOrNull(cx, state.mCurrentProgram, retval);
1931 return;
1933 case LOCAL_GL_ELEMENT_ARRAY_BUFFER_BINDING:
1934 (void)ToJSValueOrNull(cx, state.mBoundVao->mIndexBuffer, retval);
1935 return;
1937 case LOCAL_GL_FRAMEBUFFER_BINDING:
1938 (void)ToJSValueOrNull(cx, state.mBoundDrawFb, retval);
1939 return;
1941 case LOCAL_GL_RENDERBUFFER_BINDING:
1942 (void)ToJSValueOrNull(cx, state.mBoundRb, retval);
1943 return;
1945 case LOCAL_GL_TEXTURE_BINDING_2D:
1946 fnSetRetval_Tex(LOCAL_GL_TEXTURE_2D);
1947 return;
1949 case LOCAL_GL_TEXTURE_BINDING_CUBE_MAP:
1950 fnSetRetval_Tex(LOCAL_GL_TEXTURE_CUBE_MAP);
1951 return;
1953 case LOCAL_GL_VERTEX_ARRAY_BINDING: {
1954 if (!mIsWebGL2 &&
1955 !IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object))
1956 break;
1958 auto ret = state.mBoundVao;
1959 if (ret == state.mDefaultVao) {
1960 ret = nullptr;
1962 (void)ToJSValueOrNull(cx, ret, retval);
1963 return;
1966 case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
1967 retval.set(JS::NumberValue(limits.maxTexUnits));
1968 return;
1969 case LOCAL_GL_MAX_TEXTURE_SIZE:
1970 retval.set(JS::NumberValue(limits.maxTex2dSize));
1971 return;
1972 case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE:
1973 retval.set(JS::NumberValue(limits.maxTexCubeSize));
1974 return;
1975 case LOCAL_GL_MAX_VERTEX_ATTRIBS:
1976 retval.set(JS::NumberValue(limits.maxVertexAttribs));
1977 return;
1979 case LOCAL_GL_MAX_VIEWS_OVR:
1980 if (IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) {
1981 retval.set(JS::NumberValue(limits.maxMultiviewLayers));
1982 return;
1984 break;
1986 case LOCAL_GL_PACK_ALIGNMENT:
1987 retval.set(JS::NumberValue(state.mPixelPackState.alignmentInTypeElems));
1988 return;
1989 case LOCAL_GL_UNPACK_ALIGNMENT:
1990 retval.set(JS::NumberValue(state.mPixelUnpackState.alignmentInTypeElems));
1991 return;
1993 case dom::WebGLRenderingContext_Binding::UNPACK_FLIP_Y_WEBGL:
1994 retval.set(JS::BooleanValue(state.mPixelUnpackState.flipY));
1995 return;
1996 case dom::WebGLRenderingContext_Binding::UNPACK_PREMULTIPLY_ALPHA_WEBGL:
1997 retval.set(JS::BooleanValue(state.mPixelUnpackState.premultiplyAlpha));
1998 return;
1999 case dom::WebGLRenderingContext_Binding::UNPACK_COLORSPACE_CONVERSION_WEBGL:
2000 retval.set(JS::NumberValue(state.mPixelUnpackState.colorspaceConversion));
2001 return;
2003 // -
2004 // Array returns
2006 // 2 floats
2007 case LOCAL_GL_DEPTH_RANGE:
2008 retval.set(Create<dom::Float32Array>(cx, this, state.mDepthRange, rv));
2009 return;
2011 case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
2012 retval.set(
2013 Create<dom::Float32Array>(cx, this, limits.pointSizeRange, rv));
2014 return;
2016 case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE:
2017 retval.set(
2018 Create<dom::Float32Array>(cx, this, limits.lineWidthRange, rv));
2019 return;
2021 // 4 floats
2022 case LOCAL_GL_COLOR_CLEAR_VALUE:
2023 retval.set(Create<dom::Float32Array>(cx, this, state.mClearColor, rv));
2024 return;
2026 case LOCAL_GL_BLEND_COLOR:
2027 retval.set(Create<dom::Float32Array>(cx, this, state.mBlendColor, rv));
2028 return;
2030 // 2 ints
2031 case LOCAL_GL_MAX_VIEWPORT_DIMS: {
2032 const auto dims =
2033 std::array<uint32_t, 2>{limits.maxViewportDim, limits.maxViewportDim};
2034 retval.set(CreateAs<dom::Int32Array, const int32_t*>(cx, this, dims, rv));
2035 return;
2038 // 4 ints
2039 case LOCAL_GL_SCISSOR_BOX:
2040 retval.set(Create<dom::Int32Array>(cx, this, state.mScissor, rv));
2041 return;
2043 case LOCAL_GL_VIEWPORT:
2044 retval.set(Create<dom::Int32Array>(cx, this, state.mViewport, rv));
2045 return;
2047 // any
2048 case LOCAL_GL_COMPRESSED_TEXTURE_FORMATS:
2049 retval.set(Create<dom::Uint32Array>(cx, this,
2050 state.mCompressedTextureFormats, rv));
2051 return;
2054 if (mIsWebGL2) {
2055 switch (pname) {
2056 case LOCAL_GL_COPY_READ_BUFFER_BINDING:
2057 fnSetRetval_Buffer(LOCAL_GL_COPY_READ_BUFFER);
2058 return;
2060 case LOCAL_GL_COPY_WRITE_BUFFER_BINDING:
2061 fnSetRetval_Buffer(LOCAL_GL_COPY_WRITE_BUFFER);
2062 return;
2064 case LOCAL_GL_DRAW_FRAMEBUFFER_BINDING:
2065 (void)ToJSValueOrNull(cx, state.mBoundDrawFb, retval);
2066 return;
2068 case LOCAL_GL_PIXEL_PACK_BUFFER_BINDING:
2069 fnSetRetval_Buffer(LOCAL_GL_PIXEL_PACK_BUFFER);
2070 return;
2072 case LOCAL_GL_PIXEL_UNPACK_BUFFER_BINDING:
2073 fnSetRetval_Buffer(LOCAL_GL_PIXEL_UNPACK_BUFFER);
2074 return;
2076 case LOCAL_GL_READ_FRAMEBUFFER_BINDING:
2077 (void)ToJSValueOrNull(cx, state.mBoundReadFb, retval);
2078 return;
2080 case LOCAL_GL_SAMPLER_BINDING: {
2081 const auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
2082 (void)ToJSValueOrNull(cx, texUnit.sampler, retval);
2083 return;
2086 case LOCAL_GL_TEXTURE_BINDING_2D_ARRAY:
2087 fnSetRetval_Tex(LOCAL_GL_TEXTURE_2D_ARRAY);
2088 return;
2090 case LOCAL_GL_TEXTURE_BINDING_3D:
2091 fnSetRetval_Tex(LOCAL_GL_TEXTURE_3D);
2092 return;
2094 case LOCAL_GL_TRANSFORM_FEEDBACK_BINDING: {
2095 auto ret = state.mBoundTfo;
2096 if (ret == state.mDefaultTfo) {
2097 ret = nullptr;
2099 (void)ToJSValueOrNull(cx, ret, retval);
2100 return;
2103 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
2104 fnSetRetval_Buffer(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER);
2105 return;
2107 case LOCAL_GL_UNIFORM_BUFFER_BINDING:
2108 fnSetRetval_Buffer(LOCAL_GL_UNIFORM_BUFFER);
2109 return;
2111 case LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS:
2112 retval.set(
2113 JS::NumberValue(webgl::kMaxTransformFeedbackSeparateAttribs));
2114 return;
2115 case LOCAL_GL_MAX_UNIFORM_BUFFER_BINDINGS:
2116 retval.set(JS::NumberValue(limits.maxUniformBufferBindings));
2117 return;
2118 case LOCAL_GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT:
2119 retval.set(JS::NumberValue(limits.uniformBufferOffsetAlignment));
2120 return;
2121 case LOCAL_GL_MAX_3D_TEXTURE_SIZE:
2122 retval.set(JS::NumberValue(limits.maxTex3dSize));
2123 return;
2124 case LOCAL_GL_MAX_ARRAY_TEXTURE_LAYERS:
2125 retval.set(JS::NumberValue(limits.maxTexArrayLayers));
2126 return;
2128 case LOCAL_GL_PACK_ROW_LENGTH:
2129 retval.set(JS::NumberValue(state.mPixelPackState.rowLength));
2130 return;
2131 case LOCAL_GL_PACK_SKIP_PIXELS:
2132 retval.set(JS::NumberValue(state.mPixelPackState.skipPixels));
2133 return;
2134 case LOCAL_GL_PACK_SKIP_ROWS:
2135 retval.set(JS::NumberValue(state.mPixelPackState.skipRows));
2136 return;
2138 case LOCAL_GL_UNPACK_IMAGE_HEIGHT:
2139 retval.set(JS::NumberValue(state.mPixelUnpackState.imageHeight));
2140 return;
2141 case LOCAL_GL_UNPACK_ROW_LENGTH:
2142 retval.set(JS::NumberValue(state.mPixelUnpackState.rowLength));
2143 return;
2144 case LOCAL_GL_UNPACK_SKIP_IMAGES:
2145 retval.set(JS::NumberValue(state.mPixelUnpackState.skipImages));
2146 return;
2147 case LOCAL_GL_UNPACK_SKIP_PIXELS:
2148 retval.set(JS::NumberValue(state.mPixelUnpackState.skipPixels));
2149 return;
2150 case LOCAL_GL_UNPACK_SKIP_ROWS:
2151 retval.set(JS::NumberValue(state.mPixelUnpackState.skipRows));
2152 return;
2153 } // switch pname
2154 } // if webgl2
2156 // -
2158 if (!debug) {
2159 const auto GetUnmaskedRenderer = [&]() {
2160 const auto prefLock = StaticPrefs::webgl_override_unmasked_renderer();
2161 if (!prefLock->IsEmpty()) {
2162 return Some(ToString(*prefLock));
2164 return GetString(LOCAL_GL_RENDERER);
2167 const auto GetUnmaskedVendor = [&]() {
2168 const auto prefLock = StaticPrefs::webgl_override_unmasked_vendor();
2169 if (!prefLock->IsEmpty()) {
2170 return Some(ToString(*prefLock));
2172 return GetString(LOCAL_GL_VENDOR);
2175 // -
2177 Maybe<std::string> ret;
2179 switch (pname) {
2180 case LOCAL_GL_VENDOR:
2181 ret = Some(std::string{"Mozilla"});
2182 break;
2184 case LOCAL_GL_RENDERER: {
2185 bool allowRenderer = StaticPrefs::webgl_enable_renderer_query();
2186 if (nsContentUtils::ShouldResistFingerprinting()) {
2187 allowRenderer = false;
2189 if (allowRenderer) {
2190 ret = GetUnmaskedRenderer();
2191 if (ret) {
2192 ret = Some(webgl::SanitizeRenderer(*ret));
2195 if (!ret) {
2196 ret = Some(std::string{"Mozilla"});
2198 break;
2201 case LOCAL_GL_VERSION:
2202 if (mIsWebGL2) {
2203 ret = Some(std::string{"WebGL 2.0"});
2204 } else {
2205 ret = Some(std::string{"WebGL 1.0"});
2207 break;
2209 case LOCAL_GL_SHADING_LANGUAGE_VERSION:
2210 if (mIsWebGL2) {
2211 ret = Some(std::string{"WebGL GLSL ES 3.00"});
2212 } else {
2213 ret = Some(std::string{"WebGL GLSL ES 1.0"});
2215 break;
2217 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
2218 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL: {
2219 if (!IsExtensionEnabled(WebGLExtensionID::WEBGL_debug_renderer_info)) {
2220 EnqueueError_ArgEnum("pname", pname);
2221 return;
2224 switch (pname) {
2225 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
2226 ret = GetUnmaskedRenderer();
2227 if (ret && StaticPrefs::webgl_sanitize_unmasked_renderer()) {
2228 *ret = webgl::SanitizeRenderer(*ret);
2230 break;
2232 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
2233 ret = GetUnmaskedVendor();
2234 break;
2236 default:
2237 MOZ_CRASH();
2239 break;
2242 default:
2243 break;
2246 if (ret) {
2247 retval.set(StringValue(cx, *ret, rv));
2248 return;
2250 } // if (!debug)
2252 // -
2254 bool debugOnly = false;
2255 bool asString = false;
2257 switch (pname) {
2258 case LOCAL_GL_EXTENSIONS:
2259 case LOCAL_GL_RENDERER:
2260 case LOCAL_GL_VENDOR:
2261 case LOCAL_GL_VERSION:
2262 case dom::MOZ_debug_Binding::WSI_INFO:
2263 debugOnly = true;
2264 asString = true;
2265 break;
2267 case dom::MOZ_debug_Binding::DOES_INDEX_VALIDATION:
2268 debugOnly = true;
2269 break;
2271 default:
2272 break;
2275 if (debugOnly && !debug) {
2276 EnqueueError_ArgEnum("pname", pname);
2277 return;
2280 // -
2282 if (asString) {
2283 const auto maybe = GetString(pname);
2284 if (maybe) {
2285 auto str = *maybe;
2286 if (pname == dom::MOZ_debug_Binding::WSI_INFO) {
2287 nsPrintfCString more("\nIsWebglOutOfProcessEnabled: %i",
2288 int(IsWebglOutOfProcessEnabled()));
2289 str += more.BeginReading();
2291 retval.set(StringValue(cx, str.c_str(), rv));
2293 } else {
2294 const auto maybe = GetNumber(pname);
2295 if (maybe) {
2296 switch (pname) {
2297 // WebGL 1:
2298 case LOCAL_GL_BLEND:
2299 case LOCAL_GL_CULL_FACE:
2300 case LOCAL_GL_DEPTH_TEST:
2301 case LOCAL_GL_DEPTH_WRITEMASK:
2302 case LOCAL_GL_DITHER:
2303 case LOCAL_GL_POLYGON_OFFSET_FILL:
2304 case LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE:
2305 case LOCAL_GL_SAMPLE_COVERAGE:
2306 case LOCAL_GL_SAMPLE_COVERAGE_INVERT:
2307 case LOCAL_GL_SCISSOR_TEST:
2308 case LOCAL_GL_STENCIL_TEST:
2309 // WebGL 2:
2310 case LOCAL_GL_RASTERIZER_DISCARD:
2311 case LOCAL_GL_TRANSFORM_FEEDBACK_ACTIVE:
2312 case LOCAL_GL_TRANSFORM_FEEDBACK_PAUSED:
2313 retval.set(JS::BooleanValue(*maybe));
2314 break;
2316 // 4 bools
2317 case LOCAL_GL_COLOR_WRITEMASK: {
2318 const auto mask = uint8_t(*maybe);
2319 const auto bs = std::bitset<4>(mask);
2320 const auto src = std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]};
2321 JS::Rooted<JS::Value> arr(cx);
2322 if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
2323 rv = NS_ERROR_OUT_OF_MEMORY;
2325 retval.set(arr);
2326 return;
2329 default:
2330 retval.set(JS::NumberValue(*maybe));
2331 break;
2337 void ClientWebGLContext::GetBufferParameter(
2338 JSContext* cx, GLenum target, GLenum pname,
2339 JS::MutableHandle<JS::Value> retval) const {
2340 retval.set(JS::NullValue());
2341 if (IsContextLost()) return;
2343 const auto maybe = [&]() {
2344 const auto& inProcess = mNotLost->inProcess;
2345 if (inProcess) {
2346 return inProcess->GetBufferParameter(target, pname);
2348 const auto& child = mNotLost->outOfProcess;
2349 child->FlushPendingCmds();
2350 Maybe<double> ret;
2351 if (!child->SendGetBufferParameter(target, pname, &ret)) {
2352 ret.reset();
2354 return ret;
2355 }();
2356 if (maybe) {
2357 retval.set(JS::NumberValue(*maybe));
2361 bool IsFramebufferTarget(const bool isWebgl2, const GLenum target) {
2362 switch (target) {
2363 case LOCAL_GL_FRAMEBUFFER:
2364 return true;
2366 case LOCAL_GL_DRAW_FRAMEBUFFER:
2367 case LOCAL_GL_READ_FRAMEBUFFER:
2368 return isWebgl2;
2370 default:
2371 return false;
2375 void ClientWebGLContext::GetFramebufferAttachmentParameter(
2376 JSContext* const cx, const GLenum target, const GLenum attachment,
2377 const GLenum pname, JS::MutableHandle<JS::Value> retval,
2378 ErrorResult& rv) const {
2379 retval.set(JS::NullValue());
2380 const FuncScope funcScope(*this, "getFramebufferAttachmentParameter");
2381 if (IsContextLost()) return;
2383 const auto& state = State();
2385 if (!IsFramebufferTarget(mIsWebGL2, target)) {
2386 EnqueueError_ArgEnum("target", target);
2387 return;
2389 auto fb = state.mBoundDrawFb;
2390 if (target == LOCAL_GL_READ_FRAMEBUFFER) {
2391 fb = state.mBoundReadFb;
2394 const auto fnGet = [&](const GLenum pname) {
2395 const auto fbId = fb ? fb->mId : 0;
2397 const auto& inProcess = mNotLost->inProcess;
2398 if (inProcess) {
2399 return inProcess->GetFramebufferAttachmentParameter(fbId, attachment,
2400 pname);
2402 const auto& child = mNotLost->outOfProcess;
2403 child->FlushPendingCmds();
2404 Maybe<double> ret;
2405 if (!child->SendGetFramebufferAttachmentParameter(fbId, attachment, pname,
2406 &ret)) {
2407 ret.reset();
2409 return ret;
2412 if (fb) {
2413 if (fb->mOpaque) {
2414 EnqueueError(LOCAL_GL_INVALID_OPERATION,
2415 "An opaque framebuffer's attachments cannot be inspected or "
2416 "changed.");
2417 return;
2419 auto attachmentSlotEnum = attachment;
2420 if (mIsWebGL2 && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
2421 // In webgl2, DEPTH_STENCIL is valid iff the DEPTH and STENCIL images
2422 // match, so check if the server errors.
2423 const auto maybe = fnGet(LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE);
2424 if (!maybe) return;
2425 attachmentSlotEnum = LOCAL_GL_DEPTH_ATTACHMENT;
2428 const auto maybeSlot = fb->GetAttachment(attachmentSlotEnum);
2429 if (!maybeSlot) {
2430 EnqueueError_ArgEnum("attachment", attachment);
2431 return;
2433 const auto& attached = *maybeSlot;
2435 // -
2437 if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) {
2438 if (attached.rb) {
2439 (void)ToJSValueOrNull(cx, attached.rb, retval);
2440 } else {
2441 if (!mIsWebGL2 && !attached.tex) {
2442 EnqueueError_ArgEnum("pname", pname);
2443 return;
2445 (void)ToJSValueOrNull(cx, attached.tex, retval);
2447 return;
2451 const auto maybe = fnGet(pname);
2452 if (maybe) {
2453 retval.set(JS::NumberValue(*maybe));
2457 void ClientWebGLContext::GetRenderbufferParameter(
2458 JSContext* cx, GLenum target, GLenum pname,
2459 JS::MutableHandle<JS::Value> retval) const {
2460 retval.set(JS::NullValue());
2461 const FuncScope funcScope(*this, "getRenderbufferParameter");
2462 if (IsContextLost()) return;
2464 if (target != LOCAL_GL_RENDERBUFFER) {
2465 EnqueueError_ArgEnum("target", target);
2466 return;
2469 const auto& state = State();
2470 const auto& rb = state.mBoundRb;
2471 const auto rbId = rb ? rb->mId : 0;
2472 const auto maybe = [&]() {
2473 const auto& inProcess = mNotLost->inProcess;
2474 if (inProcess) {
2475 return inProcess->GetRenderbufferParameter(rbId, pname);
2477 const auto& child = mNotLost->outOfProcess;
2478 child->FlushPendingCmds();
2479 Maybe<double> ret;
2480 if (!child->SendGetRenderbufferParameter(rbId, pname, &ret)) {
2481 ret.reset();
2483 return ret;
2484 }();
2485 if (maybe) {
2486 retval.set(JS::NumberValue(*maybe));
2490 void ClientWebGLContext::GetIndexedParameter(
2491 JSContext* cx, GLenum target, GLuint index,
2492 JS::MutableHandle<JS::Value> retval, ErrorResult& rv) const {
2493 retval.set(JS::NullValue());
2494 const FuncScope funcScope(*this, "getIndexedParameter");
2495 if (IsContextLost()) return;
2497 const auto& state = State();
2499 switch (target) {
2500 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: {
2501 const auto& list = state.mBoundTfo->mAttribBuffers;
2502 if (index >= list.size()) {
2503 EnqueueError(LOCAL_GL_INVALID_VALUE,
2504 "`index` (%u) >= MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS",
2505 index);
2506 return;
2508 (void)ToJSValueOrNull(cx, list[index], retval);
2509 return;
2512 case LOCAL_GL_UNIFORM_BUFFER_BINDING: {
2513 const auto& list = state.mBoundUbos;
2514 if (index >= list.size()) {
2515 EnqueueError(LOCAL_GL_INVALID_VALUE,
2516 "`index` (%u) >= MAX_UNIFORM_BUFFER_BINDINGS", index);
2517 return;
2519 (void)ToJSValueOrNull(cx, list[index], retval);
2520 return;
2524 const auto maybe = [&]() {
2525 const auto& inProcess = mNotLost->inProcess;
2526 if (inProcess) {
2527 return inProcess->GetIndexedParameter(target, index);
2529 const auto& child = mNotLost->outOfProcess;
2530 child->FlushPendingCmds();
2531 Maybe<double> ret;
2532 if (!child->SendGetIndexedParameter(target, index, &ret)) {
2533 ret.reset();
2535 return ret;
2536 }();
2537 if (maybe) {
2538 switch (target) {
2539 case LOCAL_GL_COLOR_WRITEMASK: {
2540 const auto bs = std::bitset<4>(*maybe);
2541 const auto src = std::array<bool, 4>{bs[0], bs[1], bs[2], bs[3]};
2542 JS::Rooted<JS::Value> arr(cx);
2543 if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
2544 rv = NS_ERROR_OUT_OF_MEMORY;
2546 retval.set(arr);
2547 return;
2550 default:
2551 retval.set(JS::NumberValue(*maybe));
2552 return;
2557 void ClientWebGLContext::GetUniform(JSContext* const cx,
2558 const WebGLProgramJS& prog,
2559 const WebGLUniformLocationJS& loc,
2560 JS::MutableHandle<JS::Value> retval) {
2561 retval.set(JS::NullValue());
2562 const FuncScope funcScope(*this, "getUniform");
2563 if (IsContextLost()) return;
2564 if (!prog.ValidateUsable(*this, "prog")) return;
2565 if (!loc.ValidateUsable(*this, "loc")) return;
2567 const auto& activeLinkResult = GetActiveLinkResult();
2568 if (!activeLinkResult) {
2569 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No active linked Program.");
2570 return;
2572 const auto& reqLinkInfo = loc.mParent.lock();
2573 if (reqLinkInfo.get() != activeLinkResult) {
2574 EnqueueError(LOCAL_GL_INVALID_OPERATION,
2575 "UniformLocation is not from the current active Program.");
2576 return;
2579 const auto res = [&]() {
2580 const auto& inProcess = mNotLost->inProcess;
2581 if (inProcess) {
2582 return inProcess->GetUniform(prog.mId, loc.mLocation);
2584 const auto& child = mNotLost->outOfProcess;
2585 child->FlushPendingCmds();
2586 webgl::GetUniformData ret;
2587 if (!child->SendGetUniform(prog.mId, loc.mLocation, &ret)) {
2588 ret = {};
2590 return ret;
2591 }();
2592 if (!res.type) return;
2594 const auto elemCount = ElemTypeComponents(res.type);
2595 MOZ_ASSERT(elemCount);
2597 switch (res.type) {
2598 case LOCAL_GL_BOOL:
2599 retval.set(JS::BooleanValue(res.data[0]));
2600 return;
2602 case LOCAL_GL_FLOAT: {
2603 const auto ptr = reinterpret_cast<const float*>(res.data);
2604 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2605 return;
2607 case LOCAL_GL_INT: {
2608 const auto ptr = reinterpret_cast<const int32_t*>(res.data);
2609 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2610 return;
2612 case LOCAL_GL_UNSIGNED_INT:
2613 case LOCAL_GL_SAMPLER_2D:
2614 case LOCAL_GL_SAMPLER_3D:
2615 case LOCAL_GL_SAMPLER_CUBE:
2616 case LOCAL_GL_SAMPLER_2D_SHADOW:
2617 case LOCAL_GL_SAMPLER_2D_ARRAY:
2618 case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
2619 case LOCAL_GL_SAMPLER_CUBE_SHADOW:
2620 case LOCAL_GL_INT_SAMPLER_2D:
2621 case LOCAL_GL_INT_SAMPLER_3D:
2622 case LOCAL_GL_INT_SAMPLER_CUBE:
2623 case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
2624 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
2625 case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
2626 case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
2627 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: {
2628 const auto ptr = reinterpret_cast<const uint32_t*>(res.data);
2629 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2630 return;
2633 // -
2635 case LOCAL_GL_BOOL_VEC2:
2636 case LOCAL_GL_BOOL_VEC3:
2637 case LOCAL_GL_BOOL_VEC4: {
2638 const auto intArr = reinterpret_cast<const int32_t*>(res.data);
2639 bool boolArr[4] = {};
2640 for (const auto i : IntegerRange(elemCount)) {
2641 boolArr[i] = bool(intArr[i]);
2643 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, boolArr, elemCount, retval));
2644 return;
2647 case LOCAL_GL_FLOAT_VEC2:
2648 case LOCAL_GL_FLOAT_VEC3:
2649 case LOCAL_GL_FLOAT_VEC4:
2650 case LOCAL_GL_FLOAT_MAT2:
2651 case LOCAL_GL_FLOAT_MAT3:
2652 case LOCAL_GL_FLOAT_MAT4:
2653 case LOCAL_GL_FLOAT_MAT2x3:
2654 case LOCAL_GL_FLOAT_MAT2x4:
2655 case LOCAL_GL_FLOAT_MAT3x2:
2656 case LOCAL_GL_FLOAT_MAT3x4:
2657 case LOCAL_GL_FLOAT_MAT4x2:
2658 case LOCAL_GL_FLOAT_MAT4x3: {
2659 const auto ptr = reinterpret_cast<const float*>(res.data);
2660 JSObject* obj = dom::Float32Array::Create(cx, this, elemCount, ptr);
2661 MOZ_ASSERT(obj);
2662 retval.set(JS::ObjectOrNullValue(obj));
2663 return;
2666 case LOCAL_GL_INT_VEC2:
2667 case LOCAL_GL_INT_VEC3:
2668 case LOCAL_GL_INT_VEC4: {
2669 const auto ptr = reinterpret_cast<const int32_t*>(res.data);
2670 JSObject* obj = dom::Int32Array::Create(cx, this, elemCount, ptr);
2671 MOZ_ASSERT(obj);
2672 retval.set(JS::ObjectOrNullValue(obj));
2673 return;
2676 case LOCAL_GL_UNSIGNED_INT_VEC2:
2677 case LOCAL_GL_UNSIGNED_INT_VEC3:
2678 case LOCAL_GL_UNSIGNED_INT_VEC4: {
2679 const auto ptr = reinterpret_cast<const uint32_t*>(res.data);
2680 JSObject* obj = dom::Uint32Array::Create(cx, this, elemCount, ptr);
2681 MOZ_ASSERT(obj);
2682 retval.set(JS::ObjectOrNullValue(obj));
2683 return;
2686 default:
2687 MOZ_CRASH("GFX: Invalid elemType.");
2691 already_AddRefed<WebGLShaderPrecisionFormatJS>
2692 ClientWebGLContext::GetShaderPrecisionFormat(const GLenum shadertype,
2693 const GLenum precisiontype) {
2694 if (IsContextLost()) return nullptr;
2695 const auto info = [&]() {
2696 const auto& inProcess = mNotLost->inProcess;
2697 if (inProcess) {
2698 return inProcess->GetShaderPrecisionFormat(shadertype, precisiontype);
2700 const auto& child = mNotLost->outOfProcess;
2701 child->FlushPendingCmds();
2702 Maybe<webgl::ShaderPrecisionFormat> ret;
2703 if (!child->SendGetShaderPrecisionFormat(shadertype, precisiontype, &ret)) {
2704 ret.reset();
2706 return ret;
2707 }();
2709 if (!info) return nullptr;
2710 return AsAddRefed(new WebGLShaderPrecisionFormatJS(*info));
2713 void ClientWebGLContext::BlendColor(GLclampf r, GLclampf g, GLclampf b,
2714 GLclampf a) {
2715 const FuncScope funcScope(*this, "blendColor");
2716 if (IsContextLost()) return;
2717 auto& state = State();
2719 auto& cache = state.mBlendColor;
2720 cache[0] = r;
2721 cache[1] = g;
2722 cache[2] = b;
2723 cache[3] = a;
2725 Run<RPROC(BlendColor)>(r, g, b, a);
2728 void ClientWebGLContext::BlendEquationSeparateI(Maybe<GLuint> i, GLenum modeRGB,
2729 GLenum modeAlpha) {
2730 Run<RPROC(BlendEquationSeparate)>(i, modeRGB, modeAlpha);
2733 void ClientWebGLContext::BlendFuncSeparateI(Maybe<GLuint> i, GLenum srcRGB,
2734 GLenum dstRGB, GLenum srcAlpha,
2735 GLenum dstAlpha) {
2736 Run<RPROC(BlendFuncSeparate)>(i, srcRGB, dstRGB, srcAlpha, dstAlpha);
2739 GLenum ClientWebGLContext::CheckFramebufferStatus(GLenum target) {
2740 if (IsContextLost()) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
2742 const auto& inProcess = mNotLost->inProcess;
2743 if (inProcess) {
2744 return inProcess->CheckFramebufferStatus(target);
2746 const auto& child = mNotLost->outOfProcess;
2747 child->FlushPendingCmds();
2748 GLenum ret = 0;
2749 if (!child->SendCheckFramebufferStatus(target, &ret)) {
2750 ret = 0;
2752 return ret;
2755 void ClientWebGLContext::Clear(GLbitfield mask) {
2756 Run<RPROC(Clear)>(mask);
2758 AfterDrawCall();
2761 // -
2763 void ClientWebGLContext::ClearBufferTv(const GLenum buffer,
2764 const GLint drawBuffer,
2765 const webgl::AttribBaseType type,
2766 const Range<const uint8_t>& view,
2767 const GLuint srcElemOffset) {
2768 const FuncScope funcScope(*this, "clearBufferu?[fi]v");
2769 if (IsContextLost()) return;
2771 const auto byteOffset = CheckedInt<size_t>(srcElemOffset) * sizeof(float);
2772 if (!byteOffset.isValid() || byteOffset.value() > view.length()) {
2773 EnqueueError(LOCAL_GL_INVALID_VALUE, "`srcOffset` too large for `values`.");
2774 return;
2776 webgl::TypedQuad data;
2777 data.type = type;
2779 auto dataSize = data.data.size();
2780 switch (buffer) {
2781 case LOCAL_GL_COLOR:
2782 break;
2784 case LOCAL_GL_DEPTH:
2785 dataSize = sizeof(float);
2786 break;
2788 case LOCAL_GL_STENCIL:
2789 dataSize = sizeof(int32_t);
2790 break;
2792 default:
2793 EnqueueError_ArgEnum("buffer", buffer);
2794 return;
2797 const auto requiredBytes = byteOffset + dataSize;
2798 if (!requiredBytes.isValid() || requiredBytes.value() > view.length()) {
2799 EnqueueError(LOCAL_GL_INVALID_VALUE, "`values` too small.");
2800 return;
2803 memcpy(data.data.data(), view.begin().get() + byteOffset.value(), dataSize);
2804 Run<RPROC(ClearBufferTv)>(buffer, drawBuffer, data);
2806 AfterDrawCall();
2809 void ClientWebGLContext::ClearBufferfi(GLenum buffer, GLint drawBuffer,
2810 GLfloat depth, GLint stencil) {
2811 Run<RPROC(ClearBufferfi)>(buffer, drawBuffer, depth, stencil);
2813 AfterDrawCall();
2816 // -
2818 void ClientWebGLContext::ClearColor(GLclampf r, GLclampf g, GLclampf b,
2819 GLclampf a) {
2820 const FuncScope funcScope(*this, "clearColor");
2821 if (IsContextLost()) return;
2822 auto& state = State();
2824 auto& cache = state.mClearColor;
2825 cache[0] = r;
2826 cache[1] = g;
2827 cache[2] = b;
2828 cache[3] = a;
2830 Run<RPROC(ClearColor)>(r, g, b, a);
2833 void ClientWebGLContext::ClearDepth(GLclampf v) { Run<RPROC(ClearDepth)>(v); }
2835 void ClientWebGLContext::ClearStencil(GLint v) { Run<RPROC(ClearStencil)>(v); }
2837 void ClientWebGLContext::ColorMaskI(Maybe<GLuint> i, bool r, bool g, bool b,
2838 bool a) const {
2839 const FuncScope funcScope(*this, "colorMask");
2840 if (IsContextLost()) return;
2842 const uint8_t mask =
2843 uint8_t(r << 0) | uint8_t(g << 1) | uint8_t(b << 2) | uint8_t(a << 3);
2844 Run<RPROC(ColorMask)>(i, mask);
2847 void ClientWebGLContext::CullFace(GLenum face) { Run<RPROC(CullFace)>(face); }
2849 void ClientWebGLContext::DepthFunc(GLenum func) { Run<RPROC(DepthFunc)>(func); }
2851 void ClientWebGLContext::DepthMask(WebGLboolean b) { Run<RPROC(DepthMask)>(b); }
2853 void ClientWebGLContext::DepthRange(GLclampf zNear, GLclampf zFar) {
2854 const FuncScope funcScope(*this, "depthRange");
2855 if (IsContextLost()) return;
2856 auto& state = State();
2858 state.mDepthRange = {zNear, zFar};
2860 Run<RPROC(DepthRange)>(zNear, zFar);
2863 void ClientWebGLContext::Flush(const bool flushGl) {
2864 const FuncScope funcScope(*this, "flush");
2865 if (IsContextLost()) return;
2867 if (flushGl) {
2868 Run<RPROC(Flush)>();
2871 if (mNotLost->inProcess) return;
2872 const auto& child = mNotLost->outOfProcess;
2873 child->FlushPendingCmds();
2876 void ClientWebGLContext::Finish() {
2877 if (IsContextLost()) return;
2879 const auto& inProcess = mNotLost->inProcess;
2880 if (inProcess) {
2881 inProcess->Finish();
2882 return;
2884 const auto& child = mNotLost->outOfProcess;
2885 child->FlushPendingCmds();
2886 (void)child->SendFinish();
2889 void ClientWebGLContext::FrontFace(GLenum mode) { Run<RPROC(FrontFace)>(mode); }
2891 GLenum ClientWebGLContext::GetError() {
2892 const FuncScope funcScope(*this, "getError");
2893 if (mNextError) {
2894 const auto ret = mNextError;
2895 mNextError = 0;
2896 return ret;
2898 if (IsContextLost()) return 0;
2900 const auto& inProcess = mNotLost->inProcess;
2901 if (inProcess) {
2902 return inProcess->GetError();
2904 const auto& child = mNotLost->outOfProcess;
2905 child->FlushPendingCmds();
2906 GLenum ret = 0;
2907 if (!child->SendGetError(&ret)) {
2908 ret = 0;
2910 return ret;
2913 void ClientWebGLContext::Hint(GLenum target, GLenum mode) {
2914 Run<RPROC(Hint)>(target, mode);
2917 void ClientWebGLContext::LineWidth(GLfloat width) {
2918 Run<RPROC(LineWidth)>(width);
2921 Maybe<webgl::ErrorInfo> SetPixelUnpack(
2922 const bool isWebgl2, webgl::PixelUnpackStateWebgl* const unpacking,
2923 const GLenum pname, const GLint param);
2925 void ClientWebGLContext::PixelStorei(const GLenum pname, const GLint iparam) {
2926 const FuncScope funcScope(*this, "pixelStorei");
2927 if (IsContextLost()) return;
2928 if (!ValidateNonNegative("param", iparam)) return;
2929 const auto param = static_cast<uint32_t>(iparam);
2931 auto& state = State();
2932 auto& packState = state.mPixelPackState;
2933 switch (pname) {
2934 case LOCAL_GL_PACK_ALIGNMENT:
2935 switch (param) {
2936 case 1:
2937 case 2:
2938 case 4:
2939 case 8:
2940 break;
2941 default:
2942 EnqueueError(LOCAL_GL_INVALID_VALUE,
2943 "PACK_ALIGNMENT must be one of [1,2,4,8], was %i.",
2944 iparam);
2945 return;
2947 packState.alignmentInTypeElems = param;
2948 return;
2950 case LOCAL_GL_PACK_ROW_LENGTH:
2951 if (!mIsWebGL2) break;
2952 packState.rowLength = param;
2953 return;
2955 case LOCAL_GL_PACK_SKIP_PIXELS:
2956 if (!mIsWebGL2) break;
2957 packState.skipPixels = param;
2958 return;
2960 case LOCAL_GL_PACK_SKIP_ROWS:
2961 if (!mIsWebGL2) break;
2962 packState.skipRows = param;
2963 return;
2965 case dom::MOZ_debug_Binding::UNPACK_REQUIRE_FASTPATH:
2966 if (!IsSupported(WebGLExtensionID::MOZ_debug)) {
2967 EnqueueError_ArgEnum("pname", pname);
2968 return;
2970 break;
2972 default:
2973 break;
2976 const auto err =
2977 SetPixelUnpack(mIsWebGL2, &state.mPixelUnpackState, pname, iparam);
2978 if (err) {
2979 EnqueueError(*err);
2980 return;
2984 void ClientWebGLContext::PolygonOffset(GLfloat factor, GLfloat units) {
2985 Run<RPROC(PolygonOffset)>(factor, units);
2988 void ClientWebGLContext::SampleCoverage(GLclampf value, WebGLboolean invert) {
2989 Run<RPROC(SampleCoverage)>(value, invert);
2992 void ClientWebGLContext::Scissor(GLint x, GLint y, GLsizei width,
2993 GLsizei height) {
2994 const FuncScope funcScope(*this, "scissor");
2995 if (IsContextLost()) return;
2996 auto& state = State();
2998 if (!ValidateNonNegative("width", width) ||
2999 !ValidateNonNegative("height", height)) {
3000 return;
3003 state.mScissor = {x, y, width, height};
3005 Run<RPROC(Scissor)>(x, y, width, height);
3008 void ClientWebGLContext::StencilFuncSeparate(GLenum face, GLenum func,
3009 GLint ref, GLuint mask) {
3010 Run<RPROC(StencilFuncSeparate)>(face, func, ref, mask);
3013 void ClientWebGLContext::StencilMaskSeparate(GLenum face, GLuint mask) {
3014 Run<RPROC(StencilMaskSeparate)>(face, mask);
3017 void ClientWebGLContext::StencilOpSeparate(GLenum face, GLenum sfail,
3018 GLenum dpfail, GLenum dppass) {
3019 Run<RPROC(StencilOpSeparate)>(face, sfail, dpfail, dppass);
3022 void ClientWebGLContext::Viewport(GLint x, GLint y, GLsizei width,
3023 GLsizei height) {
3024 const FuncScope funcScope(*this, "viewport");
3025 if (IsContextLost()) return;
3026 auto& state = State();
3028 if (!ValidateNonNegative("width", width) ||
3029 !ValidateNonNegative("height", height)) {
3030 return;
3033 state.mViewport = {x, y, width, height};
3035 Run<RPROC(Viewport)>(x, y, width, height);
3038 // ------------------------- Buffer Objects -------------------------
3040 Maybe<const webgl::ErrorInfo> ValidateBindBuffer(
3041 const GLenum target, const webgl::BufferKind curKind) {
3042 if (curKind == webgl::BufferKind::Undefined) return {};
3044 auto requiredKind = webgl::BufferKind::NonIndex;
3045 switch (target) {
3046 case LOCAL_GL_COPY_READ_BUFFER:
3047 case LOCAL_GL_COPY_WRITE_BUFFER:
3048 return {}; // Always ok
3050 case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
3051 requiredKind = webgl::BufferKind::Index;
3052 break;
3054 default:
3055 break;
3058 if (curKind != requiredKind) {
3059 const auto fnKindStr = [&](const webgl::BufferKind kind) {
3060 if (kind == webgl::BufferKind::Index) return "ELEMENT_ARRAY_BUFFER";
3061 return "non-ELEMENT_ARRAY_BUFFER";
3063 const auto info = nsPrintfCString(
3064 "Buffer previously bound to %s cannot be now bound to %s.",
3065 fnKindStr(curKind), fnKindStr(requiredKind));
3066 return Some(
3067 webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION, info.BeginReading()});
3070 return {};
3073 Maybe<webgl::ErrorInfo> CheckBindBufferRange(
3074 const GLenum target, const GLuint index, const bool isBuffer,
3075 const uint64_t offset, const uint64_t size, const webgl::Limits& limits) {
3076 const auto fnSome = [&](const GLenum type, const nsACString& info) {
3077 return Some(webgl::ErrorInfo{type, info.BeginReading()});
3080 switch (target) {
3081 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
3082 if (index >= webgl::kMaxTransformFeedbackSeparateAttribs) {
3083 const auto info = nsPrintfCString(
3084 "`index` (%u) must be less than "
3085 "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS (%u).",
3086 index, webgl::kMaxTransformFeedbackSeparateAttribs);
3087 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3090 if (offset % 4 != 0 || size % 4 != 0) {
3091 const auto info =
3092 nsPrintfCString("`offset` (%" PRIu64 ") and `size` (%" PRIu64
3093 ") must both be aligned to 4 for"
3094 " TRANSFORM_FEEDBACK_BUFFER.",
3095 offset, size);
3096 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3098 break;
3100 case LOCAL_GL_UNIFORM_BUFFER:
3101 if (index >= limits.maxUniformBufferBindings) {
3102 const auto info = nsPrintfCString(
3103 "`index` (%u) must be less than MAX_UNIFORM_BUFFER_BINDINGS (%u).",
3104 index, limits.maxUniformBufferBindings);
3105 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3108 if (offset % limits.uniformBufferOffsetAlignment != 0) {
3109 const auto info =
3110 nsPrintfCString("`offset` (%" PRIu64
3111 ") must be aligned to "
3112 "UNIFORM_BUFFER_OFFSET_ALIGNMENT (%u).",
3113 offset, limits.uniformBufferOffsetAlignment);
3114 return fnSome(LOCAL_GL_INVALID_VALUE, info);
3116 break;
3118 default: {
3119 const auto info =
3120 nsPrintfCString("Unrecognized `target`: 0x%04x", target);
3121 return fnSome(LOCAL_GL_INVALID_ENUM, info);
3125 return {};
3128 // -
3130 void ClientWebGLContext::BindBuffer(const GLenum target,
3131 WebGLBufferJS* const buffer) {
3132 const FuncScope funcScope(*this, "bindBuffer");
3133 if (IsContextLost()) return;
3134 if (buffer && !buffer->ValidateUsable(*this, "buffer")) return;
3136 // -
3137 // Check for INVALID_ENUM
3139 auto& state = State();
3140 auto* slot = &(state.mBoundVao->mIndexBuffer);
3141 if (target != LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
3142 const auto itr = state.mBoundBufferByTarget.find(target);
3143 if (itr == state.mBoundBufferByTarget.end()) {
3144 EnqueueError_ArgEnum("target", target);
3145 return;
3147 slot = &(itr->second);
3150 // -
3152 auto kind = webgl::BufferKind::Undefined;
3153 if (buffer) {
3154 kind = buffer->mKind;
3156 const auto err = ValidateBindBuffer(target, kind);
3157 if (err) {
3158 EnqueueError(err->type, "%s", err->info.c_str());
3159 return;
3162 // -
3163 // Validation complete
3165 if (buffer && buffer->mKind == webgl::BufferKind::Undefined) {
3166 if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
3167 buffer->mKind = webgl::BufferKind::Index;
3168 } else {
3169 buffer->mKind = webgl::BufferKind::NonIndex;
3172 *slot = buffer;
3174 // -
3176 Run<RPROC(BindBuffer)>(target, buffer ? buffer->mId : 0);
3179 // -
3181 void ClientWebGLContext::BindBufferRangeImpl(const GLenum target,
3182 const GLuint index,
3183 WebGLBufferJS* const buffer,
3184 const uint64_t offset,
3185 const uint64_t size) {
3186 if (buffer && !buffer->ValidateUsable(*this, "buffer")) return;
3187 auto& state = State();
3189 // -
3191 const auto& limits = Limits();
3192 auto err =
3193 CheckBindBufferRange(target, index, bool(buffer), offset, size, limits);
3194 if (err) {
3195 EnqueueError(err->type, "%s", err->info.c_str());
3196 return;
3199 // -
3201 auto kind = webgl::BufferKind::Undefined;
3202 if (buffer) {
3203 kind = buffer->mKind;
3205 err = ValidateBindBuffer(target, kind);
3206 if (err) {
3207 EnqueueError(err->type, "%s", err->info.c_str());
3208 return;
3211 if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) {
3212 if (state.mTfActiveAndNotPaused) {
3213 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3214 "Cannot change TRANSFORM_FEEDBACK_BUFFER while "
3215 "TransformFeedback is active and not paused.");
3216 return;
3220 // -
3221 // Validation complete
3223 if (buffer && buffer->mKind == webgl::BufferKind::Undefined) {
3224 buffer->mKind = webgl::BufferKind::NonIndex;
3227 // -
3229 switch (target) {
3230 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
3231 state.mBoundTfo->mAttribBuffers[index] = buffer;
3232 break;
3234 case LOCAL_GL_UNIFORM_BUFFER:
3235 state.mBoundUbos[index] = buffer;
3236 break;
3238 default:
3239 MOZ_CRASH("Bad `target`");
3241 state.mBoundBufferByTarget[target] = buffer;
3243 // -
3245 Run<RPROC(BindBufferRange)>(target, index, buffer ? buffer->mId : 0, offset,
3246 size);
3249 void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset,
3250 const dom::ArrayBufferView& dstData,
3251 GLuint dstElemOffset,
3252 GLuint dstElemCountOverride) {
3253 const FuncScope funcScope(*this, "getBufferSubData");
3254 if (IsContextLost()) return;
3255 const auto notLost =
3256 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
3257 if (!ValidateNonNegative("srcByteOffset", srcByteOffset)) return;
3259 uint8_t* bytes;
3260 size_t byteLen;
3261 if (!ValidateArrayBufferView(dstData, dstElemOffset, dstElemCountOverride,
3262 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
3263 return;
3265 const auto destView = Range<uint8_t>{bytes, byteLen};
3267 const auto& inProcessContext = notLost->inProcess;
3268 if (inProcessContext) {
3269 inProcessContext->GetBufferSubData(target, srcByteOffset, destView);
3270 return;
3273 const auto& child = notLost->outOfProcess;
3274 child->FlushPendingCmds();
3275 mozilla::ipc::Shmem rawShmem;
3276 if (!child->SendGetBufferSubData(target, srcByteOffset, destView.length(),
3277 &rawShmem)) {
3278 return;
3280 const webgl::RaiiShmem shmem{child, rawShmem};
3281 if (!shmem) {
3282 EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "Failed to map in sub data buffer.");
3283 return;
3286 const auto shmemView = shmem.ByteRange();
3287 MOZ_RELEASE_ASSERT(shmemView.length() == 1 + destView.length());
3289 const auto ok = bool(*(shmemView.begin().get()));
3290 const auto srcView =
3291 Range<const uint8_t>{shmemView.begin() + 1, shmemView.end()};
3292 if (ok) {
3293 Memcpy(destView.begin(), srcView.begin(), srcView.length());
3297 ////
3299 void ClientWebGLContext::BufferData(GLenum target, WebGLsizeiptr rawSize,
3300 GLenum usage) {
3301 const FuncScope funcScope(*this, "bufferData");
3302 if (!ValidateNonNegative("size", rawSize)) return;
3304 const auto size = MaybeAs<size_t>(rawSize);
3305 if (!size) {
3306 EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "`size` too large for platform.");
3307 return;
3310 const auto data = RawBuffer<>{*size};
3311 Run<RPROC(BufferData)>(target, data, usage);
3314 void ClientWebGLContext::BufferData(
3315 GLenum target, const dom::Nullable<dom::ArrayBuffer>& maybeSrc,
3316 GLenum usage) {
3317 const FuncScope funcScope(*this, "bufferData");
3318 if (!ValidateNonNull("src", maybeSrc)) return;
3319 const auto& src = maybeSrc.Value();
3321 src.ComputeState();
3322 const auto range = Range<const uint8_t>{src.Data(), src.Length()};
3323 Run<RPROC(BufferData)>(target, RawBuffer<>(range), usage);
3326 void ClientWebGLContext::BufferData(GLenum target,
3327 const dom::ArrayBufferView& src,
3328 GLenum usage, GLuint srcElemOffset,
3329 GLuint srcElemCountOverride) {
3330 const FuncScope funcScope(*this, "bufferData");
3331 uint8_t* bytes;
3332 size_t byteLen;
3333 if (!ValidateArrayBufferView(src, srcElemOffset, srcElemCountOverride,
3334 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
3335 return;
3337 const auto range = Range<const uint8_t>{bytes, byteLen};
3338 Run<RPROC(BufferData)>(target, RawBuffer<>(range), usage);
3341 void ClientWebGLContext::RawBufferData(GLenum target, const uint8_t* srcBytes,
3342 size_t srcLen, GLenum usage) {
3343 const FuncScope funcScope(*this, "bufferData");
3345 const auto srcBuffer =
3346 srcBytes ? RawBuffer<>({srcBytes, srcLen}) : RawBuffer<>(srcLen);
3347 Run<RPROC(BufferData)>(target, srcBuffer, usage);
3350 ////
3352 void ClientWebGLContext::BufferSubData(GLenum target,
3353 WebGLsizeiptr dstByteOffset,
3354 const dom::ArrayBuffer& src) {
3355 const FuncScope funcScope(*this, "bufferSubData");
3356 src.ComputeState();
3357 const auto range = Range<const uint8_t>{src.Data(), src.Length()};
3358 Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(range));
3361 void ClientWebGLContext::BufferSubData(GLenum target,
3362 WebGLsizeiptr dstByteOffset,
3363 const dom::ArrayBufferView& src,
3364 GLuint srcElemOffset,
3365 GLuint srcElemCountOverride) {
3366 const FuncScope funcScope(*this, "bufferSubData");
3367 uint8_t* bytes;
3368 size_t byteLen;
3369 if (!ValidateArrayBufferView(src, srcElemOffset, srcElemCountOverride,
3370 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
3371 return;
3373 const auto range = Range<const uint8_t>{bytes, byteLen};
3374 Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(range));
3377 void ClientWebGLContext::CopyBufferSubData(GLenum readTarget,
3378 GLenum writeTarget,
3379 GLintptr readOffset,
3380 GLintptr writeOffset,
3381 GLsizeiptr size) {
3382 const FuncScope funcScope(*this, "copyBufferSubData");
3383 if (!ValidateNonNegative("readOffset", readOffset) ||
3384 !ValidateNonNegative("writeOffset", writeOffset) ||
3385 !ValidateNonNegative("size", size)) {
3386 return;
3388 Run<RPROC(CopyBufferSubData)>(
3389 readTarget, writeTarget, static_cast<uint64_t>(readOffset),
3390 static_cast<uint64_t>(writeOffset), static_cast<uint64_t>(size));
3393 // -------------------------- Framebuffer Objects --------------------------
3395 void ClientWebGLContext::BindFramebuffer(const GLenum target,
3396 WebGLFramebufferJS* const fb) {
3397 const FuncScope funcScope(*this, "bindFramebuffer");
3398 if (IsContextLost()) return;
3399 if (fb && !fb->ValidateUsable(*this, "fb")) return;
3401 if (!IsFramebufferTarget(mIsWebGL2, target)) {
3402 EnqueueError_ArgEnum("target", target);
3403 return;
3406 // -
3408 auto& state = State();
3410 switch (target) {
3411 case LOCAL_GL_FRAMEBUFFER:
3412 state.mBoundDrawFb = fb;
3413 state.mBoundReadFb = fb;
3414 break;
3416 case LOCAL_GL_DRAW_FRAMEBUFFER:
3417 state.mBoundDrawFb = fb;
3418 break;
3419 case LOCAL_GL_READ_FRAMEBUFFER:
3420 state.mBoundReadFb = fb;
3421 break;
3423 default:
3424 MOZ_CRASH();
3427 // -
3429 if (fb) {
3430 fb->mHasBeenBound = true;
3433 Run<RPROC(BindFramebuffer)>(target, fb ? fb->mId : 0);
3436 // -
3438 void ClientWebGLContext::FramebufferTexture2D(GLenum target, GLenum attachSlot,
3439 GLenum bindImageTarget,
3440 WebGLTextureJS* const tex,
3441 GLint mipLevel) const {
3442 const FuncScope funcScope(*this, "framebufferTexture2D");
3443 if (IsContextLost()) return;
3445 const auto bindTexTarget = ImageToTexTarget(bindImageTarget);
3446 uint32_t zLayer = 0;
3447 switch (bindTexTarget) {
3448 case LOCAL_GL_TEXTURE_2D:
3449 break;
3450 case LOCAL_GL_TEXTURE_CUBE_MAP:
3451 zLayer = bindImageTarget - LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X;
3452 break;
3453 default:
3454 EnqueueError_ArgEnum("imageTarget", bindImageTarget);
3455 return;
3458 if (!mIsWebGL2 &&
3459 !IsExtensionEnabled(WebGLExtensionID::OES_fbo_render_mipmap)) {
3460 if (mipLevel != 0) {
3461 EnqueueError(LOCAL_GL_INVALID_VALUE,
3462 "mipLevel != 0 requires OES_fbo_render_mipmap.");
3463 return;
3467 FramebufferAttach(target, attachSlot, bindImageTarget, nullptr, tex,
3468 static_cast<uint32_t>(mipLevel), zLayer, 0);
3471 Maybe<webgl::ErrorInfo> CheckFramebufferAttach(const GLenum bindImageTarget,
3472 const GLenum curTexTarget,
3473 const uint32_t mipLevel,
3474 const uint32_t zLayerBase,
3475 const uint32_t zLayerCount,
3476 const webgl::Limits& limits) {
3477 if (!curTexTarget) {
3478 return Some(
3479 webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3480 "`tex` not yet bound. Call bindTexture first."});
3483 auto texTarget = curTexTarget;
3484 if (bindImageTarget) {
3485 // FramebufferTexture2D
3486 const auto bindTexTarget = ImageToTexTarget(bindImageTarget);
3487 if (curTexTarget != bindTexTarget) {
3488 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3489 "`tex` cannot be rebound to a new target."});
3492 switch (bindTexTarget) {
3493 case LOCAL_GL_TEXTURE_2D:
3494 case LOCAL_GL_TEXTURE_CUBE_MAP:
3495 break;
3496 default:
3497 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_ENUM,
3498 "`tex` must have been bound to target "
3499 "TEXTURE_2D or TEXTURE_CUBE_MAP."});
3501 texTarget = bindTexTarget;
3502 } else {
3503 // FramebufferTextureLayer/Multiview
3504 switch (curTexTarget) {
3505 case LOCAL_GL_TEXTURE_2D_ARRAY:
3506 case LOCAL_GL_TEXTURE_3D:
3507 break;
3508 default:
3509 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3510 "`tex` must have been bound to target "
3511 "TEXTURE_2D_ARRAY or TEXTURE_3D."});
3514 MOZ_ASSERT(texTarget);
3515 uint32_t maxSize;
3516 uint32_t maxZ;
3517 switch (texTarget) {
3518 case LOCAL_GL_TEXTURE_2D:
3519 maxSize = limits.maxTex2dSize;
3520 maxZ = 1;
3521 break;
3522 case LOCAL_GL_TEXTURE_CUBE_MAP:
3523 maxSize = limits.maxTexCubeSize;
3524 maxZ = 6;
3525 break;
3526 case LOCAL_GL_TEXTURE_2D_ARRAY:
3527 maxSize = limits.maxTex2dSize;
3528 maxZ = limits.maxTexArrayLayers;
3529 break;
3530 case LOCAL_GL_TEXTURE_3D:
3531 maxSize = limits.maxTex3dSize;
3532 maxZ = limits.maxTex3dSize;
3533 break;
3534 default:
3535 MOZ_CRASH();
3537 const auto maxMipLevel = FloorLog2(maxSize);
3538 if (mipLevel > maxMipLevel) {
3539 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE,
3540 "`mipLevel` too large for texture target."});
3542 const auto requiredZLayers = CheckedInt<uint32_t>(zLayerBase) + zLayerCount;
3543 if (!requiredZLayers.isValid() || requiredZLayers.value() > maxZ) {
3544 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE,
3545 "`zLayer` too large for texture target."});
3548 return {};
3551 void ClientWebGLContext::FramebufferAttach(
3552 const GLenum target, const GLenum attachSlot, const GLenum bindImageTarget,
3553 WebGLRenderbufferJS* const rb, WebGLTextureJS* const tex,
3554 const uint32_t mipLevel, const uint32_t zLayerBase,
3555 const uint32_t numViewLayers) const {
3556 if (rb && !rb->ValidateUsable(*this, "rb")) return;
3557 if (tex && !tex->ValidateUsable(*this, "tex")) return;
3558 const auto& state = State();
3559 const auto& limits = Limits();
3561 if (!IsFramebufferTarget(mIsWebGL2, target)) {
3562 EnqueueError_ArgEnum("target", target);
3563 return;
3565 auto fb = state.mBoundDrawFb;
3566 if (target == LOCAL_GL_READ_FRAMEBUFFER) {
3567 fb = state.mBoundReadFb;
3569 if (!fb) {
3570 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No framebuffer bound.");
3571 return;
3574 if (fb->mOpaque) {
3575 EnqueueError(
3576 LOCAL_GL_INVALID_OPERATION,
3577 "An opaque framebuffer's attachments cannot be inspected or changed.");
3578 return;
3581 // -
3582 // Multiview-specific validation skipped by Host.
3584 if (tex && numViewLayers) {
3585 if (tex->mTarget != LOCAL_GL_TEXTURE_2D_ARRAY) {
3586 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3587 "`tex` must have been bound to target TEXTURE_2D_ARRAY.");
3588 return;
3590 if (numViewLayers > limits.maxMultiviewLayers) {
3591 EnqueueError(LOCAL_GL_INVALID_VALUE,
3592 "`numViews` (%u) must be <= MAX_VIEWS (%u).", numViewLayers,
3593 limits.maxMultiviewLayers);
3594 return;
3598 // -
3600 webgl::ObjectId id = 0;
3601 if (tex) {
3602 auto zLayerCount = numViewLayers;
3603 if (!zLayerCount) {
3604 zLayerCount = 1;
3606 const auto err =
3607 CheckFramebufferAttach(bindImageTarget, tex->mTarget, mipLevel,
3608 zLayerBase, zLayerCount, limits);
3609 if (err) {
3610 EnqueueError(err->type, "%s", err->info.c_str());
3611 return;
3613 id = tex->mId;
3614 } else if (rb) {
3615 if (!rb->mHasBeenBound) {
3616 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3617 "`rb` has not yet been bound with BindRenderbuffer.");
3618 return;
3620 id = rb->mId;
3623 // Ready!
3624 // But DEPTH_STENCIL in webgl2 is actually two slots!
3626 const auto fnAttachTo = [&](const GLenum actualAttachSlot) {
3627 const auto slot = fb->GetAttachment(actualAttachSlot);
3628 if (!slot) {
3629 EnqueueError_ArgEnum("attachment", actualAttachSlot);
3630 return;
3633 slot->rb = rb;
3634 slot->tex = tex;
3636 Run<RPROC(FramebufferAttach)>(target, actualAttachSlot, bindImageTarget, id,
3637 mipLevel, zLayerBase, numViewLayers);
3640 if (mIsWebGL2 && attachSlot == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
3641 fnAttachTo(LOCAL_GL_DEPTH_ATTACHMENT);
3642 fnAttachTo(LOCAL_GL_STENCIL_ATTACHMENT);
3643 } else {
3644 fnAttachTo(attachSlot);
3647 if (bindImageTarget) {
3648 if (rb) {
3649 rb->mHasBeenBound = true;
3651 if (tex) {
3652 tex->mTarget = ImageToTexTarget(bindImageTarget);
3657 // -
3659 void ClientWebGLContext::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1,
3660 GLint srcY1, GLint dstX0, GLint dstY0,
3661 GLint dstX1, GLint dstY1,
3662 GLbitfield mask, GLenum filter) {
3663 Run<RPROC(BlitFramebuffer)>(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1,
3664 dstY1, mask, filter);
3666 AfterDrawCall();
3669 void ClientWebGLContext::InvalidateFramebuffer(
3670 GLenum target, const dom::Sequence<GLenum>& attachments,
3671 ErrorResult& unused) {
3672 const auto range = MakeRange(attachments);
3673 const auto& buffer = RawBufferView(range);
3674 Run<RPROC(InvalidateFramebuffer)>(target, buffer);
3676 // Never invalidate the backbuffer, so never needs AfterDrawCall.
3679 void ClientWebGLContext::InvalidateSubFramebuffer(
3680 GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y,
3681 GLsizei width, GLsizei height, ErrorResult& unused) {
3682 const auto range = MakeRange(attachments);
3683 const auto& buffer = RawBufferView(range);
3684 Run<RPROC(InvalidateSubFramebuffer)>(target, buffer, x, y, width, height);
3686 // Never invalidate the backbuffer, so never needs AfterDrawCall.
3689 void ClientWebGLContext::ReadBuffer(GLenum mode) {
3690 Run<RPROC(ReadBuffer)>(mode);
3693 // ----------------------- Renderbuffer objects -----------------------
3695 void ClientWebGLContext::BindRenderbuffer(const GLenum target,
3696 WebGLRenderbufferJS* const rb) {
3697 const FuncScope funcScope(*this, "bindRenderbuffer");
3698 if (IsContextLost()) return;
3699 if (rb && !rb->ValidateUsable(*this, "rb")) return;
3700 auto& state = State();
3702 if (target != LOCAL_GL_RENDERBUFFER) {
3703 EnqueueError_ArgEnum("target", target);
3704 return;
3707 state.mBoundRb = rb;
3708 if (rb) {
3709 rb->mHasBeenBound = true;
3713 void ClientWebGLContext::RenderbufferStorageMultisample(GLenum target,
3714 GLsizei samples,
3715 GLenum internalFormat,
3716 GLsizei width,
3717 GLsizei height) const {
3718 const FuncScope funcScope(*this, "renderbufferStorageMultisample");
3719 if (IsContextLost()) return;
3721 if (target != LOCAL_GL_RENDERBUFFER) {
3722 EnqueueError_ArgEnum("target", target);
3723 return;
3726 const auto& state = State();
3728 const auto& rb = state.mBoundRb;
3729 if (!rb) {
3730 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No renderbuffer bound");
3731 return;
3734 if (!ValidateNonNegative("width", width) ||
3735 !ValidateNonNegative("height", height) ||
3736 !ValidateNonNegative("samples", samples)) {
3737 return;
3740 if (internalFormat == LOCAL_GL_DEPTH_STENCIL && samples > 0) {
3741 // While our backend supports it trivially, the spec forbids it.
3742 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3743 "WebGL 1's DEPTH_STENCIL format may not be multisampled. Use "
3744 "DEPTH24_STENCIL8 when `samples > 0`.");
3745 return;
3748 Run<RPROC(RenderbufferStorageMultisample)>(
3749 rb->mId, static_cast<uint32_t>(samples), internalFormat,
3750 static_cast<uint32_t>(width), static_cast<uint32_t>(height));
3753 // --------------------------- Texture objects ---------------------------
3755 void ClientWebGLContext::ActiveTexture(const GLenum texUnitEnum) {
3756 const FuncScope funcScope(*this, "activeTexture");
3757 if (IsContextLost()) return;
3759 if (texUnitEnum < LOCAL_GL_TEXTURE0) {
3760 EnqueueError(LOCAL_GL_INVALID_VALUE,
3761 "`texture` (0x%04x) must be >= TEXTURE0 (0x%04x).",
3762 texUnitEnum, LOCAL_GL_TEXTURE0);
3763 return;
3766 const auto texUnit = texUnitEnum - LOCAL_GL_TEXTURE0;
3768 auto& state = State();
3769 if (texUnit >= state.mTexUnits.size()) {
3770 EnqueueError(LOCAL_GL_INVALID_VALUE,
3771 "TEXTURE%u must be < MAX_COMBINED_TEXTURE_IMAGE_UNITS (%zu).",
3772 texUnit, state.mTexUnits.size());
3773 return;
3778 state.mActiveTexUnit = texUnit;
3779 Run<RPROC(ActiveTexture)>(texUnit);
3782 static bool IsTexTarget(const GLenum texTarget, const bool webgl2) {
3783 switch (texTarget) {
3784 case LOCAL_GL_TEXTURE_2D:
3785 case LOCAL_GL_TEXTURE_CUBE_MAP:
3786 return true;
3788 case LOCAL_GL_TEXTURE_2D_ARRAY:
3789 case LOCAL_GL_TEXTURE_3D:
3790 return webgl2;
3792 default:
3793 return false;
3797 void ClientWebGLContext::BindTexture(const GLenum texTarget,
3798 WebGLTextureJS* const tex) {
3799 const FuncScope funcScope(*this, "bindTexture");
3800 if (IsContextLost()) return;
3801 if (tex && !tex->ValidateUsable(*this, "tex")) return;
3803 if (!IsTexTarget(texTarget, mIsWebGL2)) {
3804 EnqueueError_ArgEnum("texTarget", texTarget);
3805 return;
3808 if (tex && tex->mTarget) {
3809 if (texTarget != tex->mTarget) {
3810 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3811 "Texture previously bound to %s cannot be bound now to %s.",
3812 EnumString(tex->mTarget).c_str(),
3813 EnumString(texTarget).c_str());
3814 return;
3818 auto& state = State();
3819 auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
3820 texUnit.texByTarget[texTarget] = tex;
3821 if (tex) {
3822 tex->mTarget = texTarget;
3825 Run<RPROC(BindTexture)>(texTarget, tex ? tex->mId : 0);
3828 void ClientWebGLContext::GenerateMipmap(GLenum texTarget) const {
3829 Run<RPROC(GenerateMipmap)>(texTarget);
3832 void ClientWebGLContext::GetTexParameter(
3833 JSContext* cx, GLenum texTarget, GLenum pname,
3834 JS::MutableHandle<JS::Value> retval) const {
3835 retval.set(JS::NullValue());
3836 const FuncScope funcScope(*this, "getTexParameter");
3837 if (IsContextLost()) return;
3838 auto& state = State();
3840 auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
3842 const auto& tex = Find(texUnit.texByTarget, texTarget, nullptr);
3843 if (!tex) {
3844 if (!IsTexTarget(texTarget, mIsWebGL2)) {
3845 EnqueueError_ArgEnum("texTarget", texTarget);
3846 } else {
3847 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No texture bound to %s[%u].",
3848 EnumString(texTarget).c_str(), state.mActiveTexUnit);
3850 return;
3853 const auto maybe = [&]() {
3854 const auto& inProcess = mNotLost->inProcess;
3855 if (inProcess) {
3856 return inProcess->GetTexParameter(tex->mId, pname);
3858 const auto& child = mNotLost->outOfProcess;
3859 child->FlushPendingCmds();
3860 Maybe<double> ret;
3861 if (!child->SendGetTexParameter(tex->mId, pname, &ret)) {
3862 ret.reset();
3864 return ret;
3865 }();
3867 if (maybe) {
3868 switch (pname) {
3869 case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
3870 retval.set(JS::BooleanValue(*maybe));
3871 break;
3873 default:
3874 retval.set(JS::NumberValue(*maybe));
3875 break;
3880 void ClientWebGLContext::TexParameterf(GLenum texTarget, GLenum pname,
3881 GLfloat param) {
3882 Run<RPROC(TexParameter_base)>(texTarget, pname, FloatOrInt(param));
3885 void ClientWebGLContext::TexParameteri(GLenum texTarget, GLenum pname,
3886 GLint param) {
3887 Run<RPROC(TexParameter_base)>(texTarget, pname, FloatOrInt(param));
3890 ////////////////////////////////////
3892 static GLenum JSTypeMatchUnpackTypeError(GLenum unpackType,
3893 js::Scalar::Type jsType) {
3894 bool matches = false;
3895 switch (unpackType) {
3896 case LOCAL_GL_BYTE:
3897 matches = (jsType == js::Scalar::Type::Int8);
3898 break;
3900 case LOCAL_GL_UNSIGNED_BYTE:
3901 matches = (jsType == js::Scalar::Type::Uint8 ||
3902 jsType == js::Scalar::Type::Uint8Clamped);
3903 break;
3905 case LOCAL_GL_SHORT:
3906 matches = (jsType == js::Scalar::Type::Int16);
3907 break;
3909 case LOCAL_GL_UNSIGNED_SHORT:
3910 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
3911 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
3912 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
3913 case LOCAL_GL_HALF_FLOAT:
3914 case LOCAL_GL_HALF_FLOAT_OES:
3915 matches = (jsType == js::Scalar::Type::Uint16);
3916 break;
3918 case LOCAL_GL_INT:
3919 matches = (jsType == js::Scalar::Type::Int32);
3920 break;
3922 case LOCAL_GL_UNSIGNED_INT:
3923 case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
3924 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
3925 case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
3926 case LOCAL_GL_UNSIGNED_INT_24_8:
3927 matches = (jsType == js::Scalar::Type::Uint32);
3928 break;
3930 case LOCAL_GL_FLOAT:
3931 matches = (jsType == js::Scalar::Type::Float32);
3932 break;
3934 case LOCAL_GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
3935 matches = false; // No valid jsType, but we allow uploads with null.
3936 break;
3938 default:
3939 return LOCAL_GL_INVALID_ENUM;
3941 if (!matches) return LOCAL_GL_INVALID_OPERATION;
3942 return 0;
3945 /////////////////////////////////////////////////
3947 static inline uvec2 CastUvec2(const ivec2& val) {
3948 return {static_cast<uint32_t>(val.x), static_cast<uint32_t>(val.y)};
3951 static inline uvec3 CastUvec3(const ivec3& val) {
3952 return {static_cast<uint32_t>(val.x), static_cast<uint32_t>(val.y),
3953 static_cast<uint32_t>(val.z)};
3956 template <typename T>
3957 Range<T> SubRange(const Range<T>& full, const size_t offset,
3958 const size_t length) {
3959 const auto newBegin = full.begin() + offset;
3960 return Range<T>{newBegin, newBegin + length};
3963 static inline size_t SizeOfViewElem(const dom::ArrayBufferView& view) {
3964 const auto& elemType = view.Type();
3965 if (elemType == js::Scalar::MaxTypedArrayViewType) // DataViews.
3966 return 1;
3968 return js::Scalar::byteSize(elemType);
3971 Maybe<Range<const uint8_t>> GetRangeFromView(const dom::ArrayBufferView& view,
3972 GLuint elemOffset,
3973 GLuint elemCountOverride) {
3974 const auto byteRange = MakeRangeAbv(view); // In bytes.
3975 const auto bytesPerElem = SizeOfViewElem(view);
3977 auto elemCount = byteRange.length() / bytesPerElem;
3978 if (elemOffset > elemCount) return {};
3979 elemCount -= elemOffset;
3981 if (elemCountOverride) {
3982 if (elemCountOverride > elemCount) return {};
3983 elemCount = elemCountOverride;
3985 const auto subrange =
3986 SubRange(byteRange, elemOffset * bytesPerElem, elemCount * bytesPerElem);
3987 return Some(subrange);
3990 // -
3992 static bool IsTexTargetForDims(const GLenum texTarget, const bool webgl2,
3993 const uint8_t funcDims) {
3994 if (!IsTexTarget(texTarget, webgl2)) return false;
3995 switch (texTarget) {
3996 case LOCAL_GL_TEXTURE_2D:
3997 case LOCAL_GL_TEXTURE_CUBE_MAP:
3998 return funcDims == 2;
4000 default:
4001 return funcDims == 3;
4005 void ClientWebGLContext::TexStorage(uint8_t funcDims, GLenum texTarget,
4006 GLsizei levels, GLenum internalFormat,
4007 const ivec3& size) const {
4008 const FuncScope funcScope(*this, "texStorage[23]D");
4009 if (IsContextLost()) return;
4010 if (!IsTexTargetForDims(texTarget, mIsWebGL2, funcDims)) {
4011 EnqueueError_ArgEnum("texTarget", texTarget);
4012 return;
4014 Run<RPROC(TexStorage)>(texTarget, static_cast<uint32_t>(levels),
4015 internalFormat, CastUvec3(size));
4018 namespace webgl {
4019 // TODO: Move these definitions into statics here.
4020 Maybe<webgl::TexUnpackBlobDesc> FromImageBitmap(
4021 GLenum target, Maybe<uvec3> size, const dom::ImageBitmap& imageBitmap,
4022 ErrorResult* const out_rv);
4024 Maybe<webgl::TexUnpackBlobDesc> FromOffscreenCanvas(
4025 const ClientWebGLContext&, GLenum target, Maybe<uvec3> size,
4026 const dom::OffscreenCanvas& src, ErrorResult* const out_error);
4028 Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext&,
4029 GLenum target, Maybe<uvec3> size,
4030 const dom::Element& src,
4031 ErrorResult* const out_error);
4032 } // namespace webgl
4034 // -
4036 void webgl::TexUnpackBlobDesc::Shrink(const webgl::PackingInfo& pi) {
4037 if (cpuData) {
4038 if (!size.x || !size.y || !size.z) return;
4040 const auto unpackRes = ExplicitUnpacking(pi, {});
4041 if (!unpackRes.isOk()) {
4042 return;
4044 const auto& unpack = unpackRes.inspect();
4046 const auto bytesUpperBound =
4047 CheckedInt<size_t>(unpack.metrics.bytesPerRowStride) *
4048 unpack.metrics.totalRows;
4049 if (bytesUpperBound.isValid()) {
4050 cpuData->Shrink(bytesUpperBound.value());
4055 // -
4057 void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget,
4058 GLint level, GLenum respecFormat,
4059 const ivec3& offset,
4060 const Maybe<ivec3>& isize, GLint border,
4061 const webgl::PackingInfo& pi,
4062 const TexImageSource& src) const {
4063 const FuncScope funcScope(*this, "tex(Sub)Image[23]D");
4064 if (IsContextLost()) return;
4065 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
4066 EnqueueError_ArgEnum("imageTarget", imageTarget);
4067 return;
4069 if (border != 0) {
4070 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
4071 return;
4074 Maybe<uvec3> size;
4075 if (isize) {
4076 size = Some(CastUvec3(isize.value()));
4079 // -
4081 // Demarcate the region within which GC is disallowed. Typed arrays can move
4082 // their data during a GC, so this will allow the rooting hazard analysis to
4083 // report if a GC is possible while any data pointers extracted from the
4084 // typed array are still live.
4085 dom::Uint8ClampedArray scopedArr;
4086 const auto reset = MakeScopeExit([&] {
4087 scopedArr.Reset(); // (For the hazard analysis) Done with the data.
4090 // -
4091 bool isDataUpload = false;
4092 auto desc = [&]() -> Maybe<webgl::TexUnpackBlobDesc> {
4093 if (src.mPboOffset) {
4094 isDataUpload = true;
4095 const auto offset = static_cast<uint64_t>(*src.mPboOffset);
4096 return Some(webgl::TexUnpackBlobDesc{imageTarget,
4097 size.value(),
4098 gfxAlphaType::NonPremult,
4100 Some(offset)});
4103 if (src.mView) {
4104 isDataUpload = true;
4105 const auto& view = *src.mView;
4106 const auto& jsType = view.Type();
4107 const auto err = JSTypeMatchUnpackTypeError(pi.type, jsType);
4108 switch (err) {
4109 case LOCAL_GL_INVALID_ENUM:
4110 EnqueueError_ArgEnum("unpackType", pi.type);
4111 return {};
4112 case LOCAL_GL_INVALID_OPERATION:
4113 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4114 "ArrayBufferView type %s not compatible with `type` %s.",
4115 name(jsType), EnumString(pi.type).c_str());
4116 return {};
4117 default:
4118 break;
4121 const auto range = GetRangeFromView(view, src.mViewElemOffset,
4122 src.mViewElemLengthOverride);
4123 if (!range) {
4124 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`source` too small.");
4125 return {};
4127 return Some(webgl::TexUnpackBlobDesc{imageTarget,
4128 size.value(),
4129 gfxAlphaType::NonPremult,
4130 Some(RawBuffer<>{*range}),
4131 {}});
4134 if (src.mImageBitmap) {
4135 return webgl::FromImageBitmap(imageTarget, size, *(src.mImageBitmap),
4136 src.mOut_error);
4139 if (src.mImageData) {
4140 const auto& imageData = *src.mImageData;
4141 MOZ_RELEASE_ASSERT(scopedArr.Init(imageData.GetDataObject()));
4142 scopedArr.ComputeState();
4143 const auto dataSize = scopedArr.Length();
4144 const auto data = reinterpret_cast<uint8_t*>(scopedArr.Data());
4145 if (!data) {
4146 // Neutered, e.g. via Transfer
4147 EnqueueError(LOCAL_GL_INVALID_VALUE,
4148 "ImageData.data.buffer is Detached. (Maybe you Transfered "
4149 "it to a Worker?");
4150 return {};
4153 // -
4155 const gfx::IntSize imageSize(imageData.Width(), imageData.Height());
4156 const auto sizeFromDims =
4157 CheckedInt<size_t>(imageSize.width) * imageSize.height * 4;
4158 MOZ_RELEASE_ASSERT(sizeFromDims.isValid() &&
4159 sizeFromDims.value() == dataSize);
4161 const RefPtr<gfx::DataSourceSurface> surf =
4162 gfx::Factory::CreateWrappingDataSourceSurface(
4163 data, imageSize.width * 4, imageSize,
4164 gfx::SurfaceFormat::R8G8B8A8);
4165 MOZ_ASSERT(surf);
4167 // -
4169 const auto imageUSize = *uvec2::FromSize(imageSize);
4170 const auto concreteSize =
4171 size.valueOr(uvec3{imageUSize.x, imageUSize.y, 1});
4173 // WhatWG "HTML Living Standard" (30 October 2015):
4174 // "The getImageData(sx, sy, sw, sh) method [...] Pixels must be returned
4175 // as non-premultiplied alpha values."
4176 return Some(webgl::TexUnpackBlobDesc{imageTarget,
4177 concreteSize,
4178 gfxAlphaType::NonPremult,
4181 Some(imageUSize),
4182 nullptr,
4184 surf});
4187 if (src.mOffscreenCanvas) {
4188 return webgl::FromOffscreenCanvas(
4189 *this, imageTarget, size, *(src.mOffscreenCanvas), src.mOut_error);
4192 if (src.mDomElem) {
4193 return webgl::FromDomElem(*this, imageTarget, size, *(src.mDomElem),
4194 src.mOut_error);
4197 return Some(webgl::TexUnpackBlobDesc{
4198 imageTarget, size.value(), gfxAlphaType::NonPremult, {}, {}});
4199 }();
4200 if (!desc) {
4201 return;
4204 // -
4206 const auto& rawUnpacking = State().mPixelUnpackState;
4208 auto defaultSubrectState = webgl::PixelPackingState{};
4209 defaultSubrectState.alignmentInTypeElems =
4210 rawUnpacking.alignmentInTypeElems;
4211 const bool isSubrect = (rawUnpacking != defaultSubrectState);
4212 if (isDataUpload && isSubrect) {
4213 if (rawUnpacking.flipY || rawUnpacking.premultiplyAlpha) {
4214 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4215 "Non-DOM-Element uploads with alpha-premult"
4216 " or y-flip do not support subrect selection.");
4217 return;
4221 desc->unpacking = rawUnpacking;
4223 if (desc->structuredSrcSize) {
4224 // WebGL 2 spec:
4225 // ### 5.35 Pixel store parameters for uploads from TexImageSource
4226 // UNPACK_ALIGNMENT and UNPACK_ROW_LENGTH are ignored.
4227 const auto& elemSize = *desc->structuredSrcSize;
4228 desc->unpacking.alignmentInTypeElems = 1;
4229 desc->unpacking.rowLength = elemSize.x;
4231 if (!desc->unpacking.rowLength) {
4232 desc->unpacking.rowLength = desc->size.x;
4234 if (!desc->unpacking.imageHeight) {
4235 desc->unpacking.imageHeight = desc->size.y;
4238 // -
4240 mozilla::ipc::Shmem* pShmem = nullptr;
4242 if (desc->sd) {
4243 const auto& sd = *(desc->sd);
4244 const auto sdType = sd.type();
4245 const auto& contextInfo = mNotLost->info;
4247 const auto fallbackReason = [&]() -> Maybe<std::string> {
4248 auto fallbackReason = BlitPreventReason(level, offset, pi, *desc);
4249 if (fallbackReason) return fallbackReason;
4251 const bool canUploadViaSd = contextInfo.uploadableSdTypes[sdType];
4252 if (!canUploadViaSd) {
4253 const nsPrintfCString msg(
4254 "Fast uploads for resource type %i not implemented.", int(sdType));
4255 return Some(ToString(msg));
4258 if (sdType == layers::SurfaceDescriptor::TSurfaceDescriptorBuffer) {
4259 const auto& sdb = sd.get_SurfaceDescriptorBuffer();
4260 const auto& data = sdb.data();
4261 if (data.type() == layers::MemoryOrShmem::TShmem) {
4262 pShmem = &data.get_Shmem();
4263 } else {
4264 return Some(
4265 std::string{"SurfaceDescriptorBuffer data is not Shmem."});
4269 if (sdType == layers::SurfaceDescriptor::TSurfaceDescriptorD3D10) {
4270 const auto& sdD3D = sd.get_SurfaceDescriptorD3D10();
4271 const auto& inProcess = mNotLost->inProcess;
4272 if (sdD3D.gpuProcessTextureId().isSome() && inProcess) {
4273 return Some(
4274 std::string{"gpuProcessTextureId works only in GPU process."});
4278 switch (respecFormat) {
4279 case LOCAL_GL_SRGB:
4280 case LOCAL_GL_SRGB8:
4281 case LOCAL_GL_SRGB_ALPHA:
4282 case LOCAL_GL_SRGB8_ALPHA8: {
4283 const nsPrintfCString msg(
4284 "srgb-encoded formats (like %s) are not supported.",
4285 EnumString(respecFormat).c_str());
4286 return Some(ToString(msg));
4290 if (StaticPrefs::webgl_disable_DOM_blit_uploads()) {
4291 return Some(std::string{"DOM blit uploads are disabled."});
4293 return {};
4294 }();
4296 if (fallbackReason) {
4297 EnqueuePerfWarning("Missed GPU-copy fast-path: %s",
4298 fallbackReason->c_str());
4300 const auto& image = desc->image;
4301 const RefPtr<gfx::SourceSurface> surf = image->GetAsSourceSurface();
4302 if (surf) {
4303 // WARNING: OSX can lose our MakeCurrent here.
4304 desc->dataSurf = surf->GetDataSurface();
4306 if (!desc->dataSurf) {
4307 EnqueueError(LOCAL_GL_OUT_OF_MEMORY,
4308 "Failed to retrieve source bytes for CPU upload.");
4309 return;
4311 desc->sd = Nothing();
4314 desc->image = nullptr;
4316 desc->Shrink(pi);
4318 // -
4320 std::shared_ptr<webgl::RaiiShmem> tempShmem;
4322 const bool doInlineUpload = !desc->sd;
4323 // Why always de-inline SDs here?
4324 // 1. This way we always send SDs down the same handling path, which
4325 // should keep things from breaking if things flip between paths because of
4326 // what we get handed by SurfaceFromElement etc.
4327 // 2. We don't actually always grab strong-refs to the resources in the SDs,
4328 // so we should try to use them sooner rather than later. Yes we should fix
4329 // this, but for now let's give the SDs the best chance of lucking out, eh?
4330 // :)
4331 // 3. It means we don't need to write QueueParamTraits<SurfaceDescriptor>.
4332 if (doInlineUpload) {
4333 // We definitely want e.g. TexImage(PBO) here.
4334 Run<RPROC(TexImage)>(static_cast<uint32_t>(level), respecFormat,
4335 CastUvec3(offset), pi, std::move(*desc));
4336 } else {
4337 // We can't handle shmems like SurfaceDescriptorBuffer inline, so use ipdl.
4338 const auto& inProcess = mNotLost->inProcess;
4339 if (inProcess) {
4340 return inProcess->TexImage(static_cast<uint32_t>(level), respecFormat,
4341 CastUvec3(offset), pi, *desc);
4343 const auto& child = mNotLost->outOfProcess;
4344 child->FlushPendingCmds();
4346 // The shmem we're handling was only shared from RDD to Content, and
4347 // immediately on Content receiving it, it was closed! RIP
4348 // Eventually we'll be able to make shmems that can traverse multiple
4349 // endpoints, but for now we need to make a new Content->WebGLParent shmem
4350 // and memcpy into it. We don't use `desc` elsewhere, so just replace the
4351 // Shmem buried within it with one that's valid for WebGLChild->Parent
4352 // transport.
4353 if (pShmem) {
4354 MOZ_ASSERT(desc->sd);
4355 const auto srcBytes = ShmemRange<uint8_t>(*pShmem);
4356 tempShmem = std::make_shared<webgl::RaiiShmem>();
4358 // We need Unsafe because we want to dictate when to destroy it from the
4359 // client side.
4360 *tempShmem = webgl::RaiiShmem::AllocUnsafe(child, srcBytes.length());
4361 if (!*tempShmem) {
4362 NS_WARNING("AllocShmem failed in TexImage");
4363 return;
4365 const auto dstBytes = ShmemRange<uint8_t>(tempShmem->Shmem());
4366 Memcpy(&dstBytes, srcBytes.begin());
4368 *pShmem = tempShmem->Shmem();
4369 // Not Extract, because we free tempShmem manually below, after the remote
4370 // side has finished executing SendTexImage.
4373 (void)child->SendTexImage(static_cast<uint32_t>(level), respecFormat,
4374 CastUvec3(offset), pi, std::move(*desc));
4376 if (tempShmem) {
4377 const auto eventTarget = GetCurrentSerialEventTarget();
4378 MOZ_ASSERT(eventTarget);
4379 child->SendPing()->Then(eventTarget, __func__, [tempShmem]() {
4380 // Cleans up when (our copy of) sendableShmem goes out of scope.
4386 void ClientWebGLContext::RawTexImage(uint32_t level, GLenum respecFormat,
4387 uvec3 offset, const webgl::PackingInfo& pi,
4388 webgl::TexUnpackBlobDesc&& desc) const {
4389 const FuncScope funcScope(*this, "tex(Sub)Image[23]D");
4390 if (IsContextLost()) return;
4391 if (desc.sd) {
4392 // Shmems are stored in Buffer surface descriptors. We need to ensure first
4393 // that all queued commands are flushed and then send the Shmem over IPDL.
4394 const auto& sd = *(desc.sd);
4395 if (sd.type() == layers::SurfaceDescriptor::TSurfaceDescriptorBuffer &&
4396 sd.get_SurfaceDescriptorBuffer().data().type() ==
4397 layers::MemoryOrShmem::TShmem) {
4398 const auto& inProcess = mNotLost->inProcess;
4399 if (inProcess) {
4400 inProcess->TexImage(level, respecFormat, offset, pi, desc);
4401 } else {
4402 const auto& child = mNotLost->outOfProcess;
4403 child->FlushPendingCmds();
4404 (void)child->SendTexImage(level, respecFormat, offset, pi,
4405 std::move(desc));
4407 } else {
4408 NS_WARNING(
4409 "RawTexImage with SurfaceDescriptor only supports "
4410 "SurfaceDescriptorBuffer with Shmem");
4412 return;
4415 Run<RPROC(TexImage)>(level, respecFormat, offset, pi, desc);
4418 // -
4420 void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims,
4421 GLenum imageTarget, GLint level,
4422 GLenum format, const ivec3& offset,
4423 const ivec3& isize, GLint border,
4424 const TexImageSource& src,
4425 GLsizei pboImageSize) const {
4426 const FuncScope funcScope(*this, "compressedTex(Sub)Image[23]D");
4427 if (IsContextLost()) return;
4428 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
4429 EnqueueError_ArgEnum("imageTarget", imageTarget);
4430 return;
4432 if (border != 0) {
4433 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
4434 return;
4437 RawBuffer<> range;
4438 Maybe<uint64_t> pboOffset;
4439 if (src.mView) {
4440 const auto maybe = GetRangeFromView(*src.mView, src.mViewElemOffset,
4441 src.mViewElemLengthOverride);
4442 if (!maybe) {
4443 EnqueueError(LOCAL_GL_INVALID_VALUE, "`source` too small.");
4444 return;
4446 range = RawBuffer<>{*maybe};
4447 } else if (src.mPboOffset) {
4448 if (!ValidateNonNegative("offset", *src.mPboOffset)) return;
4449 pboOffset = Some(*src.mPboOffset);
4450 } else {
4451 MOZ_CRASH("impossible");
4454 // We don't need to shrink `range` because valid calls require `range` to
4455 // match requirements exactly.
4457 Run<RPROC(CompressedTexImage)>(
4458 sub, imageTarget, static_cast<uint32_t>(level), format, CastUvec3(offset),
4459 CastUvec3(isize), range, static_cast<uint32_t>(pboImageSize), pboOffset);
4462 void ClientWebGLContext::CopyTexImage(uint8_t funcDims, GLenum imageTarget,
4463 GLint level, GLenum respecFormat,
4464 const ivec3& dstOffset,
4465 const ivec2& srcOffset, const ivec2& size,
4466 GLint border) const {
4467 const FuncScope funcScope(*this, "copy(Sub)Image[23]D");
4468 if (IsContextLost()) return;
4469 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
4470 EnqueueError_ArgEnum("imageTarget", imageTarget);
4471 return;
4473 if (border != 0) {
4474 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
4475 return;
4477 Run<RPROC(CopyTexImage)>(imageTarget, static_cast<uint32_t>(level),
4478 respecFormat, CastUvec3(dstOffset), srcOffset,
4479 CastUvec2(size));
4482 // ------------------- Programs and shaders --------------------------------
4484 void ClientWebGLContext::UseProgram(WebGLProgramJS* const prog) {
4485 const FuncScope funcScope(*this, "useProgram");
4486 if (IsContextLost()) return;
4487 if (prog && !prog->ValidateUsable(*this, "prog")) return;
4489 auto& state = State();
4491 if (state.mTfActiveAndNotPaused) {
4492 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4493 "Transform feedback is active and not paused.");
4494 return;
4497 if (prog) {
4498 const auto& res = GetLinkResult(*prog);
4499 if (!res.success) {
4500 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4501 "Program must be linked successfully.");
4502 return;
4506 // -
4508 state.mCurrentProgram = prog;
4509 state.mProgramKeepAlive = prog ? prog->mKeepAliveWeak.lock() : nullptr;
4510 state.mActiveLinkResult = prog ? prog->mResult : nullptr;
4512 Run<RPROC(UseProgram)>(prog ? prog->mId : 0);
4515 void ClientWebGLContext::ValidateProgram(WebGLProgramJS& prog) const {
4516 const FuncScope funcScope(*this, "validateProgram");
4517 if (IsContextLost()) return;
4518 if (!prog.ValidateUsable(*this, "prog")) return;
4520 prog.mLastValidate = [&]() {
4521 const auto& inProcess = mNotLost->inProcess;
4522 if (inProcess) {
4523 return inProcess->ValidateProgram(prog.mId);
4525 const auto& child = mNotLost->outOfProcess;
4526 child->FlushPendingCmds();
4527 bool ret = {};
4528 if (!child->SendValidateProgram(prog.mId, &ret)) {
4529 ret = {};
4531 return ret;
4532 }();
4535 // ------------------------ Uniforms and attributes ------------------------
4537 Maybe<double> ClientWebGLContext::GetVertexAttribPriv(const GLuint index,
4538 const GLenum pname) {
4539 const auto& inProcess = mNotLost->inProcess;
4540 if (inProcess) {
4541 return inProcess->GetVertexAttrib(index, pname);
4543 const auto& child = mNotLost->outOfProcess;
4544 child->FlushPendingCmds();
4545 Maybe<double> ret;
4546 if (!child->SendGetVertexAttrib(index, pname, &ret)) {
4547 ret.reset();
4549 return ret;
4552 void ClientWebGLContext::GetVertexAttrib(JSContext* cx, GLuint index,
4553 GLenum pname,
4554 JS::MutableHandle<JS::Value> retval,
4555 ErrorResult& rv) {
4556 retval.set(JS::NullValue());
4557 const FuncScope funcScope(*this, "getVertexAttrib");
4558 if (IsContextLost()) return;
4559 const auto& state = State();
4561 const auto& genericAttribs = state.mGenericVertexAttribs;
4562 if (index >= genericAttribs.size()) {
4563 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` (%u) >= MAX_VERTEX_ATTRIBS",
4564 index);
4565 return;
4568 switch (pname) {
4569 case LOCAL_GL_CURRENT_VERTEX_ATTRIB: {
4570 JS::Rooted<JSObject*> obj(cx);
4572 const auto& attrib = genericAttribs[index];
4573 switch (attrib.type) {
4574 case webgl::AttribBaseType::Float:
4575 obj = dom::Float32Array::Create(
4576 cx, this, 4, reinterpret_cast<const float*>(attrib.data.data()));
4577 break;
4578 case webgl::AttribBaseType::Int:
4579 obj = dom::Int32Array::Create(
4580 cx, this, 4,
4581 reinterpret_cast<const int32_t*>(attrib.data.data()));
4582 break;
4583 case webgl::AttribBaseType::Uint:
4584 obj = dom::Uint32Array::Create(
4585 cx, this, 4,
4586 reinterpret_cast<const uint32_t*>(attrib.data.data()));
4587 break;
4588 case webgl::AttribBaseType::Boolean:
4589 MOZ_CRASH("impossible");
4592 if (!obj) {
4593 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
4594 return;
4596 retval.set(JS::ObjectValue(*obj));
4597 return;
4600 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: {
4601 const auto& buffers = state.mBoundVao->mAttribBuffers;
4602 const auto& buffer = buffers[index];
4603 (void)ToJSValueOrNull(cx, buffer, retval);
4604 return;
4607 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER:
4608 // Disallowed from JS, but allowed in Host.
4609 EnqueueError_ArgEnum("pname", pname);
4610 return;
4612 default:
4613 break;
4616 const auto maybe = GetVertexAttribPriv(index, pname);
4617 if (maybe) {
4618 switch (pname) {
4619 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED:
4620 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
4621 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_INTEGER:
4622 retval.set(JS::BooleanValue(*maybe));
4623 break;
4625 default:
4626 retval.set(JS::NumberValue(*maybe));
4627 break;
4632 void ClientWebGLContext::UniformData(const GLenum funcElemType,
4633 const WebGLUniformLocationJS* const loc,
4634 bool transpose,
4635 const Range<const uint8_t>& bytes,
4636 GLuint elemOffset,
4637 GLuint elemCountOverride) const {
4638 const FuncScope funcScope(*this, "uniform setter");
4639 if (IsContextLost()) return;
4641 const auto& activeLinkResult = GetActiveLinkResult();
4642 if (!activeLinkResult) {
4643 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No active linked Program.");
4644 return;
4647 // -
4649 auto availCount = bytes.length() / sizeof(float);
4650 if (elemOffset > availCount) {
4651 EnqueueError(LOCAL_GL_INVALID_VALUE, "`elemOffset` too large for `data`.");
4652 return;
4654 availCount -= elemOffset;
4655 if (elemCountOverride) {
4656 if (elemCountOverride > availCount) {
4657 EnqueueError(LOCAL_GL_INVALID_VALUE,
4658 "`elemCountOverride` too large for `data`.");
4659 return;
4661 availCount = elemCountOverride;
4664 // -
4666 const auto channels = ElemTypeComponents(funcElemType);
4667 if (!availCount || availCount % channels != 0) {
4668 EnqueueError(LOCAL_GL_INVALID_VALUE,
4669 "`values` length (%u) must be a positive "
4670 "integer multiple of size of %s.",
4671 availCount, EnumString(funcElemType).c_str());
4672 return;
4675 // -
4677 uint32_t locId = -1;
4678 if (MOZ_LIKELY(loc)) {
4679 locId = loc->mLocation;
4680 if (!loc->ValidateUsable(*this, "location")) return;
4682 // -
4684 const auto& reqLinkInfo = loc->mParent.lock();
4685 if (reqLinkInfo.get() != activeLinkResult) {
4686 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4687 "UniformLocation is not from the current active Program.");
4688 return;
4691 // -
4693 bool funcMatchesLocation = false;
4694 for (const auto allowed : loc->mValidUploadElemTypes) {
4695 funcMatchesLocation |= (funcElemType == allowed);
4697 if (MOZ_UNLIKELY(!funcMatchesLocation)) {
4698 std::string validSetters;
4699 for (const auto allowed : loc->mValidUploadElemTypes) {
4700 validSetters += EnumString(allowed);
4701 validSetters += '/';
4703 validSetters.pop_back(); // Cheekily discard the extra trailing '/'.
4705 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4706 "Uniform's `type` requires uniform setter of type %s.",
4707 validSetters.c_str());
4708 return;
4712 // -
4714 const auto begin =
4715 reinterpret_cast<const webgl::UniformDataVal*>(bytes.begin().get()) +
4716 elemOffset;
4717 const auto range = Range{begin, availCount};
4718 Run<RPROC(UniformData)>(locId, transpose, RawBuffer{range});
4721 // -
4723 void ClientWebGLContext::BindVertexArray(WebGLVertexArrayJS* const vao) {
4724 const FuncScope funcScope(*this, "bindVertexArray");
4725 if (IsContextLost()) return;
4726 if (vao && !vao->ValidateUsable(*this, "vao")) return;
4727 auto& state = State();
4729 if (vao) {
4730 vao->mHasBeenBound = true;
4731 state.mBoundVao = vao;
4732 } else {
4733 state.mBoundVao = state.mDefaultVao;
4736 Run<RPROC(BindVertexArray)>(vao ? vao->mId : 0);
4739 void ClientWebGLContext::EnableVertexAttribArray(GLuint index) {
4740 Run<RPROC(EnableVertexAttribArray)>(index);
4743 void ClientWebGLContext::DisableVertexAttribArray(GLuint index) {
4744 Run<RPROC(DisableVertexAttribArray)>(index);
4747 WebGLsizeiptr ClientWebGLContext::GetVertexAttribOffset(GLuint index,
4748 GLenum pname) {
4749 const FuncScope funcScope(*this, "getVertexAttribOffset");
4750 if (IsContextLost()) return 0;
4752 if (pname != LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER) {
4753 EnqueueError_ArgEnum("pname", pname);
4754 return 0;
4757 const auto maybe = GetVertexAttribPriv(index, pname);
4758 if (!maybe) return 0;
4759 return *maybe;
4762 void ClientWebGLContext::VertexAttrib4Tv(GLuint index, webgl::AttribBaseType t,
4763 const Range<const uint8_t>& src) {
4764 const FuncScope funcScope(*this, "vertexAttrib[1234]u?[fi]{v}");
4765 if (IsContextLost()) return;
4766 auto& state = State();
4768 if (src.length() / sizeof(float) < 4) {
4769 EnqueueError(LOCAL_GL_INVALID_VALUE, "Array must have >=4 elements.");
4770 return;
4773 auto& list = state.mGenericVertexAttribs;
4774 if (index >= list.size()) {
4775 EnqueueError(LOCAL_GL_INVALID_VALUE,
4776 "`index` must be < MAX_VERTEX_ATTRIBS.");
4777 return;
4780 auto& attrib = list[index];
4781 attrib.type = t;
4782 memcpy(attrib.data.data(), src.begin().get(), attrib.data.size());
4784 Run<RPROC(VertexAttrib4T)>(index, attrib);
4787 // -
4789 void ClientWebGLContext::VertexAttribDivisor(GLuint index, GLuint divisor) {
4790 Run<RPROC(VertexAttribDivisor)>(index, divisor);
4793 // -
4795 void ClientWebGLContext::VertexAttribPointerImpl(bool isFuncInt, GLuint index,
4796 GLint rawChannels, GLenum type,
4797 bool normalized,
4798 GLsizei rawByteStrideOrZero,
4799 WebGLintptr rawByteOffset) {
4800 const FuncScope funcScope(*this, "vertexAttribI?Pointer");
4801 if (IsContextLost()) return;
4802 auto& state = State();
4804 const auto channels = MaybeAs<uint8_t>(rawChannels);
4805 if (!channels) {
4806 EnqueueError(LOCAL_GL_INVALID_VALUE,
4807 "Channel count `size` must be within [1,4].");
4808 return;
4811 const auto byteStrideOrZero = MaybeAs<uint8_t>(rawByteStrideOrZero);
4812 if (!byteStrideOrZero) {
4813 EnqueueError(LOCAL_GL_INVALID_VALUE, "`stride` must be within [0,255].");
4814 return;
4817 if (!ValidateNonNegative("byteOffset", rawByteOffset)) return;
4818 const auto byteOffset = static_cast<uint64_t>(rawByteOffset);
4820 // -
4822 const webgl::VertAttribPointerDesc desc{
4823 isFuncInt, *channels, normalized, *byteStrideOrZero, type, byteOffset};
4825 const auto res = CheckVertexAttribPointer(mIsWebGL2, desc);
4826 if (res.isErr()) {
4827 const auto& err = res.inspectErr();
4828 EnqueueError(err.type, "%s", err.info.c_str());
4829 return;
4832 auto& list = state.mBoundVao->mAttribBuffers;
4833 if (index >= list.size()) {
4834 EnqueueError(LOCAL_GL_INVALID_VALUE,
4835 "`index` (%u) must be < MAX_VERTEX_ATTRIBS.", index);
4836 return;
4839 const auto buffer = state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER];
4840 if (!buffer && byteOffset) {
4841 return EnqueueError(LOCAL_GL_INVALID_OPERATION,
4842 "If ARRAY_BUFFER is null, byteOffset must be zero.");
4845 Run<RPROC(VertexAttribPointer)>(index, desc);
4847 list[index] = buffer;
4850 // -------------------------------- Drawing -------------------------------
4852 void ClientWebGLContext::DrawArraysInstanced(GLenum mode, GLint first,
4853 GLsizei count, GLsizei primcount) {
4854 Run<RPROC(DrawArraysInstanced)>(mode, first, count, primcount);
4855 AfterDrawCall();
4858 void ClientWebGLContext::DrawElementsInstanced(GLenum mode, GLsizei count,
4859 GLenum type, WebGLintptr offset,
4860 GLsizei primcount) {
4861 Run<RPROC(DrawElementsInstanced)>(mode, count, type, offset, primcount);
4862 AfterDrawCall();
4865 // ------------------------------ Readback -------------------------------
4866 void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
4867 GLsizei height, GLenum format, GLenum type,
4868 WebGLsizeiptr offset,
4869 dom::CallerType aCallerType,
4870 ErrorResult& out_error) const {
4871 const FuncScope funcScope(*this, "readPixels");
4872 if (!ReadPixels_SharedPrecheck(aCallerType, out_error)) return;
4873 const auto& state = State();
4874 if (!ValidateNonNegative("width", width)) return;
4875 if (!ValidateNonNegative("height", height)) return;
4876 if (!ValidateNonNegative("offset", offset)) return;
4878 const auto desc = webgl::ReadPixelsDesc{{x, y},
4879 *uvec2::From(width, height),
4880 {format, type},
4881 state.mPixelPackState};
4882 Run<RPROC(ReadPixelsPbo)>(desc, static_cast<uint64_t>(offset));
4885 void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
4886 GLsizei height, GLenum format, GLenum type,
4887 const dom::ArrayBufferView& dstData,
4888 GLuint dstElemOffset,
4889 dom::CallerType aCallerType,
4890 ErrorResult& out_error) const {
4891 const FuncScope funcScope(*this, "readPixels");
4892 if (!ReadPixels_SharedPrecheck(aCallerType, out_error)) return;
4893 const auto& state = State();
4894 if (!ValidateNonNegative("width", width)) return;
4895 if (!ValidateNonNegative("height", height)) return;
4897 ////
4899 js::Scalar::Type reqScalarType;
4900 if (!GetJSScalarFromGLType(type, &reqScalarType)) {
4901 nsCString name;
4902 WebGLContext::EnumName(type, &name);
4903 EnqueueError(LOCAL_GL_INVALID_ENUM, "type: invalid enum value %s",
4904 name.BeginReading());
4905 return;
4908 auto viewElemType = dstData.Type();
4909 if (viewElemType == js::Scalar::Uint8Clamped) {
4910 viewElemType = js::Scalar::Uint8;
4912 if (viewElemType != reqScalarType) {
4913 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4914 "`pixels` type does not match `type`.");
4915 return;
4918 uint8_t* bytes;
4919 size_t byteLen;
4920 if (!ValidateArrayBufferView(dstData, dstElemOffset, 0,
4921 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
4922 return;
4925 const auto desc = webgl::ReadPixelsDesc{{x, y},
4926 *uvec2::From(width, height),
4927 {format, type},
4928 state.mPixelPackState};
4929 const auto range = Range<uint8_t>(bytes, byteLen);
4930 if (!DoReadPixels(desc, range)) {
4931 return;
4935 bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
4936 const Range<uint8_t> dest) const {
4937 const auto notLost =
4938 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
4939 if (!notLost) return false;
4940 const auto& inProcess = notLost->inProcess;
4941 if (inProcess) {
4942 inProcess->ReadPixelsInto(desc, dest);
4943 return true;
4945 const auto& child = notLost->outOfProcess;
4946 child->FlushPendingCmds();
4947 webgl::ReadPixelsResultIpc res = {};
4948 if (!child->SendReadPixels(desc, dest.length(), &res)) {
4949 res = {};
4951 if (!res.byteStride || !res.shmem) return false;
4952 const auto& byteStride = res.byteStride;
4953 const auto& subrect = res.subrect;
4954 const webgl::RaiiShmem shmem{child, res.shmem.ref()};
4955 if (!shmem) {
4956 EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "Failed to map in back buffer.");
4957 return false;
4960 const auto& shmemBytes = shmem.ByteRange();
4962 const auto pii = webgl::PackingInfoInfo::For(desc.pi);
4963 if (!pii) {
4964 gfxCriticalError() << "ReadPixels: Bad " << desc.pi;
4965 return false;
4967 const auto bpp = pii->BytesPerPixel();
4969 const auto& packing = desc.packState;
4970 auto packRect = *uvec2::From(subrect.x, subrect.y);
4971 packRect.x += packing.skipPixels;
4972 packRect.y += packing.skipRows;
4974 const auto xByteSize = bpp * static_cast<uint32_t>(subrect.width);
4975 const ptrdiff_t byteOffset = packRect.y * byteStride + packRect.x * bpp;
4977 auto srcItr = shmemBytes.begin() + byteOffset;
4978 auto destItr = dest.begin() + byteOffset;
4980 for (const auto i : IntegerRange(subrect.height)) {
4981 if (i) {
4982 // Don't trigger an assert on the last loop by pushing a RangedPtr past
4983 // its bounds.
4984 srcItr += byteStride;
4985 destItr += byteStride;
4986 MOZ_RELEASE_ASSERT(srcItr + xByteSize <= shmemBytes.end());
4987 MOZ_RELEASE_ASSERT(destItr + xByteSize <= dest.end());
4989 Memcpy(destItr, srcItr, xByteSize);
4992 return true;
4995 bool ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
4996 const mozilla::ipc::Shmem& shmem) const {
4997 const auto notLost =
4998 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
4999 if (!notLost) return false;
5000 const auto& inProcess = notLost->inProcess;
5001 if (inProcess) {
5002 const auto& shmemBytes = shmem.Range<uint8_t>();
5003 inProcess->ReadPixelsInto(desc, shmemBytes);
5004 return true;
5006 const auto& child = notLost->outOfProcess;
5007 child->FlushPendingCmds();
5008 webgl::ReadPixelsResultIpc res = {};
5009 // We assume the input is an unsafe shmem which won't be consumed by this
5010 // request. Since SendReadPixels expects a Shmem rvalue, we must create a copy
5011 // to provide it that can be consumed instead of the original descriptor.
5012 mozilla::ipc::Shmem dest = shmem;
5013 if (!child->SendReadPixels(desc, dest, &res)) {
5014 res = {};
5016 return res.byteStride > 0;
5019 bool ClientWebGLContext::ReadPixels_SharedPrecheck(
5020 dom::CallerType aCallerType, ErrorResult& out_error) const {
5021 if (IsContextLost()) return false;
5023 if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
5024 aCallerType != dom::CallerType::System) {
5025 JsWarning("readPixels: Not allowed");
5026 out_error.Throw(NS_ERROR_DOM_SECURITY_ERR);
5027 return false;
5030 return true;
5033 // --------------------------------- GL Query ---------------------------------
5035 static inline GLenum QuerySlotTarget(const GLenum specificTarget) {
5036 if (specificTarget == LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
5037 return LOCAL_GL_ANY_SAMPLES_PASSED;
5039 return specificTarget;
5042 void ClientWebGLContext::GetQuery(JSContext* cx, GLenum specificTarget,
5043 GLenum pname,
5044 JS::MutableHandle<JS::Value> retval) const {
5045 retval.set(JS::NullValue());
5046 const FuncScope funcScope(*this, "getQuery");
5047 if (IsContextLost()) return;
5048 const auto& limits = Limits();
5049 auto& state = State();
5051 if (IsExtensionEnabled(WebGLExtensionID::EXT_disjoint_timer_query)) {
5052 if (pname == LOCAL_GL_QUERY_COUNTER_BITS) {
5053 switch (specificTarget) {
5054 case LOCAL_GL_TIME_ELAPSED_EXT:
5055 retval.set(JS::NumberValue(limits.queryCounterBitsTimeElapsed));
5056 return;
5058 case LOCAL_GL_TIMESTAMP_EXT:
5059 retval.set(JS::NumberValue(limits.queryCounterBitsTimestamp));
5060 return;
5062 default:
5063 EnqueueError_ArgEnum("target", specificTarget);
5064 return;
5069 if (pname != LOCAL_GL_CURRENT_QUERY) {
5070 EnqueueError_ArgEnum("pname", pname);
5071 return;
5074 const auto slotTarget = QuerySlotTarget(specificTarget);
5075 const auto& slot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
5076 if (!slot) {
5077 EnqueueError_ArgEnum("target", specificTarget);
5078 return;
5081 auto query = *slot;
5082 if (query && query->mTarget != specificTarget) {
5083 query = nullptr;
5086 (void)ToJSValueOrNull(cx, query, retval);
5089 void ClientWebGLContext::GetQueryParameter(
5090 JSContext*, WebGLQueryJS& query, const GLenum pname,
5091 JS::MutableHandle<JS::Value> retval) const {
5092 retval.set(JS::NullValue());
5093 const FuncScope funcScope(*this, "getQueryParameter");
5094 if (IsContextLost()) return;
5095 if (!query.ValidateUsable(*this, "query")) return;
5097 auto maybe = [&]() {
5098 const auto& inProcess = mNotLost->inProcess;
5099 if (inProcess) {
5100 return inProcess->GetQueryParameter(query.mId, pname);
5102 const auto& child = mNotLost->outOfProcess;
5103 child->FlushPendingCmds();
5104 Maybe<double> ret;
5105 if (!child->SendGetQueryParameter(query.mId, pname, &ret)) {
5106 ret.reset();
5108 return ret;
5109 }();
5110 if (!maybe) return;
5112 // We must usually wait for an event loop before the query can be available.
5113 const bool canBeAvailable =
5114 (query.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
5115 if (!canBeAvailable) {
5116 if (pname != LOCAL_GL_QUERY_RESULT_AVAILABLE) {
5117 return;
5119 maybe = Some(0.0);
5122 switch (pname) {
5123 case LOCAL_GL_QUERY_RESULT_AVAILABLE:
5124 retval.set(JS::BooleanValue(*maybe));
5125 break;
5127 default:
5128 retval.set(JS::NumberValue(*maybe));
5129 break;
5133 void ClientWebGLContext::BeginQuery(const GLenum specificTarget,
5134 WebGLQueryJS& query) {
5135 const FuncScope funcScope(*this, "beginQuery");
5136 if (IsContextLost()) return;
5137 if (!query.ValidateUsable(*this, "query")) return;
5138 auto& state = State();
5140 const auto slotTarget = QuerySlotTarget(specificTarget);
5141 const auto& slot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
5142 if (!slot) {
5143 EnqueueError_ArgEnum("target", specificTarget);
5144 return;
5147 if (*slot) {
5148 auto enumStr = EnumString(slotTarget);
5149 if (slotTarget == LOCAL_GL_ANY_SAMPLES_PASSED) {
5150 enumStr += "/ANY_SAMPLES_PASSED_CONSERVATIVE";
5152 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5153 "A Query is already active for %s.", enumStr.c_str());
5154 return;
5157 if (query.mTarget && query.mTarget != specificTarget) {
5158 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5159 "`query` cannot be changed to a different target.");
5160 return;
5163 *slot = &query;
5164 query.mTarget = specificTarget;
5166 Run<RPROC(BeginQuery)>(specificTarget, query.mId);
5169 void ClientWebGLContext::EndQuery(const GLenum specificTarget) {
5170 const FuncScope funcScope(*this, "endQuery");
5171 if (IsContextLost()) return;
5172 auto& state = State();
5174 const auto slotTarget = QuerySlotTarget(specificTarget);
5175 const auto& maybeSlot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
5176 if (!maybeSlot) {
5177 EnqueueError_ArgEnum("target", specificTarget);
5178 return;
5180 auto& slot = *maybeSlot;
5181 if (!slot || slot->mTarget != specificTarget) {
5182 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No Query is active for %s.",
5183 EnumString(specificTarget).c_str());
5184 return;
5186 const auto query = slot;
5187 slot = nullptr;
5189 Run<RPROC(EndQuery)>(specificTarget);
5191 auto& availRunnable = EnsureAvailabilityRunnable();
5192 availRunnable.mQueries.push_back(query.get());
5193 query->mCanBeAvailable = false;
5196 void ClientWebGLContext::QueryCounter(WebGLQueryJS& query,
5197 const GLenum target) const {
5198 const FuncScope funcScope(*this, "queryCounter");
5199 if (IsContextLost()) return;
5200 if (!query.ValidateUsable(*this, "query")) return;
5202 if (target != LOCAL_GL_TIMESTAMP) {
5203 EnqueueError(LOCAL_GL_INVALID_ENUM, "`target` must be TIMESTAMP.");
5204 return;
5207 if (query.mTarget && query.mTarget != target) {
5208 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5209 "`query` cannot be changed to a different target.");
5210 return;
5212 query.mTarget = target;
5214 Run<RPROC(QueryCounter)>(query.mId);
5216 auto& availRunnable = EnsureAvailabilityRunnable();
5217 availRunnable.mQueries.push_back(&query);
5218 query.mCanBeAvailable = false;
5221 // -------------------------------- Sampler -------------------------------
5222 void ClientWebGLContext::GetSamplerParameter(
5223 JSContext* cx, const WebGLSamplerJS& sampler, const GLenum pname,
5224 JS::MutableHandle<JS::Value> retval) const {
5225 retval.set(JS::NullValue());
5226 const FuncScope funcScope(*this, "getSamplerParameter");
5227 if (IsContextLost()) return;
5228 if (!sampler.ValidateUsable(*this, "sampler")) return;
5230 const auto maybe = [&]() {
5231 const auto& inProcess = mNotLost->inProcess;
5232 if (inProcess) {
5233 return inProcess->GetSamplerParameter(sampler.mId, pname);
5235 const auto& child = mNotLost->outOfProcess;
5236 child->FlushPendingCmds();
5237 Maybe<double> ret;
5238 if (!child->SendGetSamplerParameter(sampler.mId, pname, &ret)) {
5239 ret.reset();
5241 return ret;
5242 }();
5243 if (maybe) {
5244 retval.set(JS::NumberValue(*maybe));
5248 void ClientWebGLContext::BindSampler(const GLuint unit,
5249 WebGLSamplerJS* const sampler) {
5250 const FuncScope funcScope(*this, "bindSampler");
5251 if (IsContextLost()) return;
5252 if (sampler && !sampler->ValidateUsable(*this, "sampler")) return;
5253 auto& state = State();
5255 auto& texUnits = state.mTexUnits;
5256 if (unit >= texUnits.size()) {
5257 EnqueueError(LOCAL_GL_INVALID_VALUE, "`unit` (%u) larger than %zu.", unit,
5258 texUnits.size());
5259 return;
5262 // -
5264 texUnits[unit].sampler = sampler;
5266 Run<RPROC(BindSampler)>(unit, sampler ? sampler->mId : 0);
5269 void ClientWebGLContext::SamplerParameteri(WebGLSamplerJS& sampler,
5270 const GLenum pname,
5271 const GLint param) const {
5272 const FuncScope funcScope(*this, "samplerParameteri");
5273 if (IsContextLost()) return;
5274 if (!sampler.ValidateUsable(*this, "sampler")) return;
5276 Run<RPROC(SamplerParameteri)>(sampler.mId, pname, param);
5279 void ClientWebGLContext::SamplerParameterf(WebGLSamplerJS& sampler,
5280 const GLenum pname,
5281 const GLfloat param) const {
5282 const FuncScope funcScope(*this, "samplerParameterf");
5283 if (IsContextLost()) return;
5284 if (!sampler.ValidateUsable(*this, "sampler")) return;
5286 Run<RPROC(SamplerParameterf)>(sampler.mId, pname, param);
5289 // ------------------------------- GL Sync ---------------------------------
5291 void ClientWebGLContext::GetSyncParameter(
5292 JSContext* const cx, WebGLSyncJS& sync, const GLenum pname,
5293 JS::MutableHandle<JS::Value> retval) const {
5294 retval.set(JS::NullValue());
5295 const FuncScope funcScope(*this, "getSyncParameter");
5296 if (IsContextLost()) return;
5297 if (!sync.ValidateUsable(*this, "sync")) return;
5299 retval.set([&]() -> JS::Value {
5300 switch (pname) {
5301 case LOCAL_GL_OBJECT_TYPE:
5302 return JS::NumberValue(LOCAL_GL_SYNC_FENCE);
5303 case LOCAL_GL_SYNC_CONDITION:
5304 return JS::NumberValue(LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE);
5305 case LOCAL_GL_SYNC_FLAGS:
5306 return JS::NumberValue(0);
5307 case LOCAL_GL_SYNC_STATUS: {
5308 if (!sync.mSignaled) {
5309 const auto res = ClientWaitSync(sync, 0, 0);
5310 sync.mSignaled = (res == LOCAL_GL_ALREADY_SIGNALED ||
5311 res == LOCAL_GL_CONDITION_SATISFIED);
5313 return JS::NumberValue(sync.mSignaled ? LOCAL_GL_SIGNALED
5314 : LOCAL_GL_UNSIGNALED);
5316 default:
5317 EnqueueError_ArgEnum("pname", pname);
5318 return JS::NullValue();
5320 }());
5323 GLenum ClientWebGLContext::ClientWaitSync(WebGLSyncJS& sync,
5324 const GLbitfield flags,
5325 const GLuint64 timeout) const {
5326 const FuncScope funcScope(*this, "clientWaitSync");
5327 if (IsContextLost()) return LOCAL_GL_WAIT_FAILED;
5328 if (!sync.ValidateUsable(*this, "sync")) return LOCAL_GL_WAIT_FAILED;
5330 if (flags != 0 && flags != LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT) {
5331 EnqueueError(LOCAL_GL_INVALID_VALUE,
5332 "`flags` must be SYNC_FLUSH_COMMANDS_BIT or 0.");
5333 return LOCAL_GL_WAIT_FAILED;
5336 const auto ret = [&]() {
5337 const auto& inProcess = mNotLost->inProcess;
5338 if (inProcess) {
5339 return inProcess->ClientWaitSync(sync.mId, flags, timeout);
5341 const auto& child = mNotLost->outOfProcess;
5342 child->FlushPendingCmds();
5343 GLenum ret = {};
5344 if (!child->SendClientWaitSync(sync.mId, flags, timeout, &ret)) {
5345 ret = {};
5347 return ret;
5348 }();
5350 switch (ret) {
5351 case LOCAL_GL_CONDITION_SATISFIED:
5352 case LOCAL_GL_ALREADY_SIGNALED:
5353 sync.mSignaled = true;
5354 break;
5357 // -
5359 const bool canBeAvailable =
5360 (sync.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
5361 if (!canBeAvailable) {
5362 if (!sync.mHasWarnedNotAvailable) {
5363 EnqueueWarning(
5364 "ClientWaitSync must return TIMEOUT_EXPIRED until control has"
5365 " returned to the user agent's main loop. (only warns once)");
5366 sync.mHasWarnedNotAvailable = true;
5368 return LOCAL_GL_TIMEOUT_EXPIRED;
5371 return ret;
5374 void ClientWebGLContext::WaitSync(const WebGLSyncJS& sync,
5375 const GLbitfield flags,
5376 const GLint64 timeout) const {
5377 const FuncScope funcScope(*this, "waitSync");
5378 if (IsContextLost()) return;
5379 if (!sync.ValidateUsable(*this, "sync")) return;
5381 if (flags != 0) {
5382 EnqueueError(LOCAL_GL_INVALID_VALUE, "`flags` must be 0.");
5383 return;
5385 if (timeout != -1) {
5386 EnqueueError(LOCAL_GL_INVALID_VALUE, "`timeout` must be TIMEOUT_IGNORED.");
5387 return;
5390 JsWarning("waitSync is a no-op.");
5393 // -------------------------- Transform Feedback ---------------------------
5395 void ClientWebGLContext::BindTransformFeedback(
5396 const GLenum target, WebGLTransformFeedbackJS* const tf) {
5397 const FuncScope funcScope(*this, "bindTransformFeedback");
5398 if (IsContextLost()) return;
5399 if (tf && !tf->ValidateUsable(*this, "tf")) return;
5400 auto& state = State();
5402 if (target != LOCAL_GL_TRANSFORM_FEEDBACK) {
5403 EnqueueError(LOCAL_GL_INVALID_ENUM, "`target` must be TRANSFORM_FEEDBACK.");
5404 return;
5406 if (state.mTfActiveAndNotPaused) {
5407 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5408 "Current Transform Feedback object is active and not paused.");
5409 return;
5412 if (tf) {
5413 tf->mHasBeenBound = true;
5414 state.mBoundTfo = tf;
5415 } else {
5416 state.mBoundTfo = state.mDefaultTfo;
5419 Run<RPROC(BindTransformFeedback)>(tf ? tf->mId : 0);
5422 void ClientWebGLContext::BeginTransformFeedback(const GLenum primMode) {
5423 const FuncScope funcScope(*this, "beginTransformFeedback");
5424 if (IsContextLost()) return;
5425 auto& state = State();
5426 auto& tfo = *(state.mBoundTfo);
5428 if (tfo.mActiveOrPaused) {
5429 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5430 "Transform Feedback is already active or paused.");
5431 return;
5433 MOZ_ASSERT(!state.mTfActiveAndNotPaused);
5435 auto& prog = state.mCurrentProgram;
5436 if (!prog) {
5437 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No program in use.");
5438 return;
5440 const auto& linkResult = GetLinkResult(*prog);
5441 if (!linkResult.success) {
5442 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5443 "Program is not successfully linked.");
5444 return;
5447 auto tfBufferCount = linkResult.active.activeTfVaryings.size();
5448 if (tfBufferCount &&
5449 linkResult.tfBufferMode == LOCAL_GL_INTERLEAVED_ATTRIBS) {
5450 tfBufferCount = 1;
5452 if (!tfBufferCount) {
5453 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5454 "Program does not use Transform Feedback.");
5455 return;
5458 const auto& buffers = tfo.mAttribBuffers;
5459 for (const auto i : IntegerRange(tfBufferCount)) {
5460 if (!buffers[i]) {
5461 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5462 "Transform Feedback buffer %u is null.", i);
5463 return;
5467 switch (primMode) {
5468 case LOCAL_GL_POINTS:
5469 case LOCAL_GL_LINES:
5470 case LOCAL_GL_TRIANGLES:
5471 break;
5472 default:
5473 EnqueueError(LOCAL_GL_INVALID_ENUM,
5474 "`primitiveMode` must be POINTS, LINES< or TRIANGLES.");
5475 return;
5478 // -
5480 tfo.mActiveOrPaused = true;
5481 tfo.mActiveProgram = prog;
5482 tfo.mActiveProgramKeepAlive = prog->mKeepAliveWeak.lock();
5483 prog->mActiveTfos.insert(&tfo);
5484 state.mTfActiveAndNotPaused = true;
5486 Run<RPROC(BeginTransformFeedback)>(primMode);
5489 void ClientWebGLContext::EndTransformFeedback() {
5490 const FuncScope funcScope(*this, "endTransformFeedback");
5491 if (IsContextLost()) return;
5492 auto& state = State();
5493 auto& tfo = *(state.mBoundTfo);
5495 if (!tfo.mActiveOrPaused) {
5496 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5497 "Transform Feedback is not active or paused.");
5498 return;
5501 tfo.mActiveOrPaused = false;
5502 tfo.mActiveProgram->mActiveTfos.erase(&tfo);
5503 tfo.mActiveProgram = nullptr;
5504 tfo.mActiveProgramKeepAlive = nullptr;
5505 state.mTfActiveAndNotPaused = false;
5506 Run<RPROC(EndTransformFeedback)>();
5509 void ClientWebGLContext::PauseTransformFeedback() {
5510 const FuncScope funcScope(*this, "pauseTransformFeedback");
5511 if (IsContextLost()) return;
5512 auto& state = State();
5513 auto& tfo = *(state.mBoundTfo);
5515 if (!tfo.mActiveOrPaused) {
5516 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5517 "Transform Feedback is not active.");
5518 return;
5520 if (!state.mTfActiveAndNotPaused) {
5521 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5522 "Transform Feedback is already paused.");
5523 return;
5526 state.mTfActiveAndNotPaused = false;
5527 Run<RPROC(PauseTransformFeedback)>();
5530 void ClientWebGLContext::ResumeTransformFeedback() {
5531 const FuncScope funcScope(*this, "resumeTransformFeedback");
5532 if (IsContextLost()) return;
5533 auto& state = State();
5534 auto& tfo = *(state.mBoundTfo);
5536 if (!tfo.mActiveOrPaused) {
5537 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5538 "Transform Feedback is not active and paused.");
5539 return;
5541 if (state.mTfActiveAndNotPaused) {
5542 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5543 "Transform Feedback is not paused.");
5544 return;
5546 if (state.mCurrentProgram != tfo.mActiveProgram) {
5547 EnqueueError(
5548 LOCAL_GL_INVALID_OPERATION,
5549 "Cannot Resume Transform Feedback with a program link result different"
5550 " from when Begin was called.");
5551 return;
5554 state.mTfActiveAndNotPaused = true;
5555 Run<RPROC(ResumeTransformFeedback)>();
5558 void ClientWebGLContext::SetFramebufferIsInOpaqueRAF(WebGLFramebufferJS* fb,
5559 bool value) {
5560 fb->mInOpaqueRAF = value;
5561 Run<RPROC(SetFramebufferIsInOpaqueRAF)>(fb->mId, value);
5564 // ---------------------------- Misc Extensions ----------------------------
5565 void ClientWebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers) {
5566 const auto range = MakeRange(buffers);
5567 const auto vec = std::vector<GLenum>(range.begin().get(), range.end().get());
5568 Run<RPROC(DrawBuffers)>(vec);
5571 void ClientWebGLContext::EnqueueErrorImpl(const GLenum error,
5572 const nsACString& text) const {
5573 if (!mNotLost) return; // Ignored if context is lost.
5574 Run<RPROC(GenerateError)>(error, ToString(text));
5577 void ClientWebGLContext::RequestExtension(const WebGLExtensionID ext) const {
5578 Run<RPROC(RequestExtension)>(ext);
5581 // -
5583 static bool IsExtensionForbiddenForCaller(const WebGLExtensionID ext,
5584 const dom::CallerType callerType) {
5585 if (callerType == dom::CallerType::System) return false;
5587 if (StaticPrefs::webgl_enable_privileged_extensions()) return false;
5589 const bool resistFingerprinting =
5590 nsContentUtils::ShouldResistFingerprinting();
5591 switch (ext) {
5592 case WebGLExtensionID::MOZ_debug:
5593 return true;
5595 case WebGLExtensionID::WEBGL_debug_renderer_info:
5596 return resistFingerprinting ||
5597 !StaticPrefs::webgl_enable_debug_renderer_info();
5599 case WebGLExtensionID::WEBGL_debug_shaders:
5600 return resistFingerprinting;
5602 default:
5603 return false;
5607 bool ClientWebGLContext::IsSupported(const WebGLExtensionID ext,
5608 const dom::CallerType callerType) const {
5609 if (IsExtensionForbiddenForCaller(ext, callerType)) return false;
5610 const auto& limits = Limits();
5611 return limits.supportedExtensions[ext];
5614 void ClientWebGLContext::GetSupportedExtensions(
5615 dom::Nullable<nsTArray<nsString>>& retval,
5616 const dom::CallerType callerType) const {
5617 retval.SetNull();
5618 if (!mNotLost) return;
5620 auto& retarr = retval.SetValue();
5621 for (const auto i : MakeEnumeratedRange(WebGLExtensionID::Max)) {
5622 if (!IsSupported(i, callerType)) continue;
5624 const auto& extStr = GetExtensionName(i);
5625 retarr.AppendElement(NS_ConvertUTF8toUTF16(extStr));
5629 // -
5631 void ClientWebGLContext::GetSupportedProfilesASTC(
5632 dom::Nullable<nsTArray<nsString>>& retval) const {
5633 retval.SetNull();
5634 if (!mNotLost) return;
5635 const auto& limits = Limits();
5637 auto& retarr = retval.SetValue();
5638 retarr.AppendElement(u"ldr"_ns);
5639 if (limits.astcHdr) {
5640 retarr.AppendElement(u"hdr"_ns);
5644 // -
5646 bool ClientWebGLContext::ShouldResistFingerprinting() const {
5647 if (mCanvasElement) {
5648 // If we're constructed from a canvas element
5649 return nsContentUtils::ShouldResistFingerprinting(GetOwnerDoc());
5651 if (mOffscreenCanvas) {
5652 // If we're constructed from an offscreen canvas
5653 return mOffscreenCanvas->ShouldResistFingerprinting();
5655 // Last resort, just check the global preference
5656 return nsContentUtils::ShouldResistFingerprinting();
5659 uint32_t ClientWebGLContext::GetPrincipalHashValue() const {
5660 if (mCanvasElement) {
5661 return mCanvasElement->NodePrincipal()->GetHashValue();
5663 if (mOffscreenCanvas) {
5664 nsIGlobalObject* global = mOffscreenCanvas->GetOwnerGlobal();
5665 if (global) {
5666 return global->GetPrincipalHashValue();
5669 return 0;
5672 // ---------------------------
5674 void ClientWebGLContext::EnqueueError_ArgEnum(const char* const argName,
5675 const GLenum val) const {
5676 EnqueueError(LOCAL_GL_INVALID_ENUM, "Bad `%s`: 0x%04x", argName, val);
5679 // -
5680 // WebGLProgramJS
5682 void ClientWebGLContext::AttachShader(WebGLProgramJS& prog,
5683 WebGLShaderJS& shader) const {
5684 const FuncScope funcScope(*this, "attachShader");
5685 if (IsContextLost()) return;
5686 if (!prog.ValidateUsable(*this, "program")) return;
5687 if (!shader.ValidateUsable(*this, "shader")) return;
5689 auto& slot = *MaybeFind(prog.mNextLink_Shaders, shader.mType);
5690 if (slot.shader) {
5691 if (&shader == slot.shader) {
5692 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`shader` is already attached.");
5693 } else {
5694 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5695 "Only one of each type of"
5696 " shader may be attached to a program.");
5698 return;
5700 slot = {&shader, shader.mKeepAliveWeak.lock()};
5702 Run<RPROC(AttachShader)>(prog.mId, shader.mId);
5705 void ClientWebGLContext::BindAttribLocation(WebGLProgramJS& prog,
5706 const GLuint location,
5707 const nsAString& name) const {
5708 const FuncScope funcScope(*this, "detachShader");
5709 if (IsContextLost()) return;
5710 if (!prog.ValidateUsable(*this, "program")) return;
5712 const auto& nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
5713 Run<RPROC(BindAttribLocation)>(prog.mId, location, nameU8);
5716 void ClientWebGLContext::DetachShader(WebGLProgramJS& prog,
5717 const WebGLShaderJS& shader) const {
5718 const FuncScope funcScope(*this, "detachShader");
5719 if (IsContextLost()) return;
5720 if (!prog.ValidateUsable(*this, "program")) return;
5721 if (!shader.ValidateUsable(*this, "shader")) return;
5723 auto& slot = *MaybeFind(prog.mNextLink_Shaders, shader.mType);
5725 if (slot.shader != &shader) {
5726 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`shader` is not attached.");
5727 return;
5729 slot = {};
5731 Run<RPROC(DetachShader)>(prog.mId, shader.mId);
5734 void ClientWebGLContext::GetAttachedShaders(
5735 const WebGLProgramJS& prog,
5736 dom::Nullable<nsTArray<RefPtr<WebGLShaderJS>>>& retval) const {
5737 const FuncScope funcScope(*this, "getAttachedShaders");
5738 if (IsContextLost()) return;
5739 if (!prog.ValidateUsable(*this, "program")) return;
5741 auto& arr = retval.SetValue();
5742 for (const auto& pair : prog.mNextLink_Shaders) {
5743 const auto& attachment = pair.second;
5744 if (!attachment.shader) continue;
5745 arr.AppendElement(attachment.shader);
5749 void ClientWebGLContext::LinkProgram(WebGLProgramJS& prog) const {
5750 const FuncScope funcScope(*this, "linkProgram");
5751 if (IsContextLost()) return;
5752 if (!prog.ValidateUsable(*this, "program")) return;
5754 if (!prog.mActiveTfos.empty()) {
5755 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5756 "Program still in use by active or paused"
5757 " Transform Feedback objects.");
5758 return;
5761 prog.mResult = std::make_shared<webgl::LinkResult>();
5762 prog.mUniformLocByName = Nothing();
5763 prog.mUniformBlockBindings = {};
5764 Run<RPROC(LinkProgram)>(prog.mId);
5767 void ClientWebGLContext::TransformFeedbackVaryings(
5768 WebGLProgramJS& prog, const dom::Sequence<nsString>& varyings,
5769 const GLenum bufferMode) const {
5770 const FuncScope funcScope(*this, "transformFeedbackVaryings");
5771 if (IsContextLost()) return;
5772 if (!prog.ValidateUsable(*this, "program")) return;
5774 std::vector<std::string> varyingsU8;
5775 varyingsU8.reserve(varyings.Length());
5776 for (const auto& cur : varyings) {
5777 const auto curU8 = ToString(NS_ConvertUTF16toUTF8(cur));
5778 varyingsU8.push_back(curU8);
5781 Run<RPROC(TransformFeedbackVaryings)>(prog.mId, varyingsU8, bufferMode);
5784 void ClientWebGLContext::UniformBlockBinding(WebGLProgramJS& prog,
5785 const GLuint blockIndex,
5786 const GLuint blockBinding) const {
5787 const FuncScope funcScope(*this, "uniformBlockBinding");
5788 if (IsContextLost()) return;
5789 if (!prog.ValidateUsable(*this, "program")) return;
5790 const auto& state = State();
5792 (void)GetLinkResult(prog);
5793 auto& list = prog.mUniformBlockBindings;
5794 if (blockIndex >= list.size()) {
5795 EnqueueError(
5796 LOCAL_GL_INVALID_VALUE,
5797 "`blockIndex` (%u) must be less than ACTIVE_UNIFORM_BLOCKS (%zu).",
5798 blockIndex, list.size());
5799 return;
5801 if (blockBinding >= state.mBoundUbos.size()) {
5802 EnqueueError(LOCAL_GL_INVALID_VALUE,
5803 "`blockBinding` (%u) must be less than "
5804 "MAX_UNIFORM_BUFFER_BINDINGS (%zu).",
5805 blockBinding, state.mBoundUbos.size());
5806 return;
5809 list[blockIndex] = blockBinding;
5810 Run<RPROC(UniformBlockBinding)>(prog.mId, blockIndex, blockBinding);
5813 // WebGLProgramJS link result reflection
5815 already_AddRefed<WebGLActiveInfoJS> ClientWebGLContext::GetActiveAttrib(
5816 const WebGLProgramJS& prog, const GLuint index) {
5817 const FuncScope funcScope(*this, "getActiveAttrib");
5818 if (IsContextLost()) return nullptr;
5819 if (!prog.ValidateUsable(*this, "program")) return nullptr;
5821 const auto& res = GetLinkResult(prog);
5822 const auto& list = res.active.activeAttribs;
5823 if (index >= list.size()) {
5824 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5825 return nullptr;
5828 const auto& info = list[index];
5829 return AsAddRefed(new WebGLActiveInfoJS(info));
5832 already_AddRefed<WebGLActiveInfoJS> ClientWebGLContext::GetActiveUniform(
5833 const WebGLProgramJS& prog, const GLuint index) {
5834 const FuncScope funcScope(*this, "getActiveUniform");
5835 if (IsContextLost()) return nullptr;
5836 if (!prog.ValidateUsable(*this, "program")) return nullptr;
5838 const auto& res = GetLinkResult(prog);
5839 const auto& list = res.active.activeUniforms;
5840 if (index >= list.size()) {
5841 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5842 return nullptr;
5845 const auto& info = list[index];
5846 return AsAddRefed(new WebGLActiveInfoJS(info));
5849 void ClientWebGLContext::GetActiveUniformBlockName(const WebGLProgramJS& prog,
5850 const GLuint index,
5851 nsAString& retval) const {
5852 retval.SetIsVoid(true);
5853 const FuncScope funcScope(*this, "getActiveUniformBlockName");
5854 if (IsContextLost()) return;
5855 if (!prog.ValidateUsable(*this, "program")) return;
5857 const auto& res = GetLinkResult(prog);
5858 if (!res.success) {
5859 EnqueueError(LOCAL_GL_INVALID_OPERATION, "Program has not been linked.");
5860 return;
5863 const auto& list = res.active.activeUniformBlocks;
5864 if (index >= list.size()) {
5865 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5866 return;
5869 const auto& block = list[index];
5870 CopyUTF8toUTF16(block.name, retval);
5873 void ClientWebGLContext::GetActiveUniformBlockParameter(
5874 JSContext* const cx, const WebGLProgramJS& prog, const GLuint index,
5875 const GLenum pname, JS::MutableHandle<JS::Value> retval, ErrorResult& rv) {
5876 retval.set(JS::NullValue());
5877 const FuncScope funcScope(*this, "getActiveUniformBlockParameter");
5878 if (IsContextLost()) return;
5879 if (!prog.ValidateUsable(*this, "program")) return;
5881 const auto& res = GetLinkResult(prog);
5882 const auto& list = res.active.activeUniformBlocks;
5883 if (index >= list.size()) {
5884 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5885 return;
5887 const auto& block = list[index];
5889 retval.set([&]() -> JS::Value {
5890 switch (pname) {
5891 case LOCAL_GL_UNIFORM_BLOCK_BINDING:
5892 return JS::NumberValue(prog.mUniformBlockBindings[index]);
5894 case LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE:
5895 return JS::NumberValue(block.dataSize);
5897 case LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS:
5898 return JS::NumberValue(block.activeUniformIndices.size());
5900 case LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: {
5901 const auto& indices = block.activeUniformIndices;
5902 JS::Rooted<JSObject*> obj(
5904 dom::Uint32Array::Create(cx, this, indices.size(), indices.data()));
5905 if (!obj) {
5906 rv = NS_ERROR_OUT_OF_MEMORY;
5908 return JS::ObjectOrNullValue(obj);
5911 case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER:
5912 return JS::BooleanValue(block.referencedByVertexShader);
5914 case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER:
5915 return JS::BooleanValue(block.referencedByFragmentShader);
5917 default:
5918 EnqueueError_ArgEnum("pname", pname);
5919 return JS::NullValue();
5921 }());
5924 void ClientWebGLContext::GetActiveUniforms(
5925 JSContext* const cx, const WebGLProgramJS& prog,
5926 const dom::Sequence<GLuint>& uniformIndices, const GLenum pname,
5927 JS::MutableHandle<JS::Value> retval) const {
5928 retval.set(JS::NullValue());
5929 const FuncScope funcScope(*this, "getActiveUniforms");
5930 if (IsContextLost()) return;
5931 if (!prog.ValidateUsable(*this, "program")) return;
5933 const auto& res = GetLinkResult(prog);
5934 const auto& list = res.active.activeUniforms;
5936 const auto count = uniformIndices.Length();
5937 JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, count));
5938 if (!array) return; // Just bail.
5940 for (const auto i : IntegerRange(count)) {
5941 const auto index = uniformIndices[i];
5942 if (index >= list.size()) {
5943 EnqueueError(LOCAL_GL_INVALID_VALUE,
5944 "`uniformIndices[%u]`: `%u` too large.", i, index);
5945 return;
5947 const auto& uniform = list[index];
5949 JS::Rooted<JS::Value> value(cx);
5950 switch (pname) {
5951 case LOCAL_GL_UNIFORM_TYPE:
5952 value = JS::NumberValue(uniform.elemType);
5953 break;
5955 case LOCAL_GL_UNIFORM_SIZE:
5956 value = JS::NumberValue(uniform.elemCount);
5957 break;
5959 case LOCAL_GL_UNIFORM_BLOCK_INDEX:
5960 value = JS::NumberValue(uniform.block_index);
5961 break;
5963 case LOCAL_GL_UNIFORM_OFFSET:
5964 value = JS::NumberValue(uniform.block_offset);
5965 break;
5967 case LOCAL_GL_UNIFORM_ARRAY_STRIDE:
5968 value = JS::NumberValue(uniform.block_arrayStride);
5969 break;
5971 case LOCAL_GL_UNIFORM_MATRIX_STRIDE:
5972 value = JS::NumberValue(uniform.block_matrixStride);
5973 break;
5975 case LOCAL_GL_UNIFORM_IS_ROW_MAJOR:
5976 value = JS::BooleanValue(uniform.block_isRowMajor);
5977 break;
5979 default:
5980 EnqueueError_ArgEnum("pname", pname);
5981 return;
5983 if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE)) return;
5986 retval.setObject(*array);
5989 already_AddRefed<WebGLActiveInfoJS>
5990 ClientWebGLContext::GetTransformFeedbackVarying(const WebGLProgramJS& prog,
5991 const GLuint index) {
5992 const FuncScope funcScope(*this, "getTransformFeedbackVarying");
5993 if (IsContextLost()) return nullptr;
5994 if (!prog.ValidateUsable(*this, "program")) return nullptr;
5996 const auto& res = GetLinkResult(prog);
5997 const auto& list = res.active.activeTfVaryings;
5998 if (index >= list.size()) {
5999 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
6000 return nullptr;
6003 const auto& info = list[index];
6004 return AsAddRefed(new WebGLActiveInfoJS(info));
6007 GLint ClientWebGLContext::GetAttribLocation(const WebGLProgramJS& prog,
6008 const nsAString& name) const {
6009 const FuncScope funcScope(*this, "getAttribLocation");
6010 if (IsContextLost()) return -1;
6011 if (!prog.ValidateUsable(*this, "program")) return -1;
6013 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
6014 const auto& res = GetLinkResult(prog);
6015 for (const auto& cur : res.active.activeAttribs) {
6016 if (cur.name == nameU8) return cur.location;
6019 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
6020 if (err) {
6021 EnqueueError(err->type, "%s", err->info.c_str());
6023 return -1;
6026 GLint ClientWebGLContext::GetFragDataLocation(const WebGLProgramJS& prog,
6027 const nsAString& name) const {
6028 const FuncScope funcScope(*this, "getFragDataLocation");
6029 if (IsContextLost()) return -1;
6030 if (!prog.ValidateUsable(*this, "program")) return -1;
6032 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
6034 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
6035 if (err) {
6036 EnqueueError(*err);
6037 return -1;
6040 return [&]() {
6041 const auto& inProcess = mNotLost->inProcess;
6042 if (inProcess) {
6043 return inProcess->GetFragDataLocation(prog.mId, nameU8);
6045 const auto& child = mNotLost->outOfProcess;
6046 child->FlushPendingCmds();
6047 GLint ret = {};
6048 if (!child->SendGetFragDataLocation(prog.mId, nameU8, &ret)) {
6049 ret = {};
6051 return ret;
6052 }();
6055 GLuint ClientWebGLContext::GetUniformBlockIndex(
6056 const WebGLProgramJS& prog, const nsAString& blockName) const {
6057 const FuncScope funcScope(*this, "getUniformBlockIndex");
6058 if (IsContextLost()) return LOCAL_GL_INVALID_INDEX;
6059 if (!prog.ValidateUsable(*this, "program")) return LOCAL_GL_INVALID_INDEX;
6061 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(blockName));
6063 const auto& res = GetLinkResult(prog);
6064 const auto& list = res.active.activeUniformBlocks;
6065 for (const auto i : IntegerRange(list.size())) {
6066 const auto& cur = list[i];
6067 if (cur.name == nameU8) {
6068 return i;
6071 return LOCAL_GL_INVALID_INDEX;
6074 void ClientWebGLContext::GetUniformIndices(
6075 const WebGLProgramJS& prog, const dom::Sequence<nsString>& uniformNames,
6076 dom::Nullable<nsTArray<GLuint>>& retval) const {
6077 const FuncScope funcScope(*this, "getUniformIndices");
6078 if (IsContextLost()) return;
6079 if (!prog.ValidateUsable(*this, "program")) return;
6081 const auto& res = GetLinkResult(prog);
6082 auto ret = nsTArray<GLuint>(uniformNames.Length());
6084 for (const auto& queriedNameU16 : uniformNames) {
6085 const auto queriedName = ToString(NS_ConvertUTF16toUTF8(queriedNameU16));
6086 const auto impliedProperArrayQueriedName = queriedName + "[0]";
6088 GLuint activeId = LOCAL_GL_INVALID_INDEX;
6089 for (const auto i : IntegerRange(res.active.activeUniforms.size())) {
6090 // O(N^2) ok for small N.
6091 const auto& activeInfoForI = res.active.activeUniforms[i];
6092 if (queriedName == activeInfoForI.name ||
6093 impliedProperArrayQueriedName == activeInfoForI.name) {
6094 activeId = i;
6095 break;
6098 ret.AppendElement(activeId);
6101 retval.SetValue(std::move(ret));
6104 already_AddRefed<WebGLUniformLocationJS> ClientWebGLContext::GetUniformLocation(
6105 const WebGLProgramJS& prog, const nsAString& name) const {
6106 const FuncScope funcScope(*this, "getUniformLocation");
6107 if (IsContextLost()) return nullptr;
6108 if (!prog.ValidateUsable(*this, "program")) return nullptr;
6110 const auto& res = GetLinkResult(prog);
6112 if (!prog.mUniformLocByName) {
6113 // Cache a map from name->location.
6114 // Since the only way to set uniforms requires calling GetUniformLocation,
6115 // we expect apps to query most active uniforms once for each scalar or
6116 // array. NB: Uniform array setters do have overflow semantics, even though
6117 // uniform locations aren't guaranteed contiguous, but GetUniformLocation
6118 // must still be called once per array.
6119 prog.mUniformLocByName.emplace();
6121 for (const auto& activeUniform : res.active.activeUniforms) {
6122 if (activeUniform.block_index != -1) continue;
6124 auto locName = activeUniform.name;
6125 const auto indexed = webgl::ParseIndexed(locName);
6126 if (indexed) {
6127 locName = indexed->name;
6130 const auto err = CheckGLSLVariableName(mIsWebGL2, locName);
6131 if (err) continue;
6133 const auto baseLength = locName.size();
6134 for (const auto& pair : activeUniform.locByIndex) {
6135 if (indexed) {
6136 locName.erase(baseLength); // Erase previous "[N]".
6137 locName += '[';
6138 locName += std::to_string(pair.first);
6139 locName += ']';
6141 const auto locInfo =
6142 WebGLProgramJS::UniformLocInfo{pair.second, activeUniform.elemType};
6143 prog.mUniformLocByName->insert({locName, locInfo});
6147 const auto& locByName = *(prog.mUniformLocByName);
6149 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
6150 auto loc = MaybeFind(locByName, nameU8);
6151 if (!loc) {
6152 loc = MaybeFind(locByName, nameU8 + "[0]");
6154 if (!loc) {
6155 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
6156 if (err) {
6157 EnqueueError(err->type, "%s", err->info.c_str());
6159 return nullptr;
6162 return AsAddRefed(new WebGLUniformLocationJS(*this, prog.mResult,
6163 loc->location, loc->elemType));
6166 std::array<uint16_t, 3> ValidUploadElemTypes(const GLenum elemType) {
6167 std::vector<GLenum> ret;
6168 switch (elemType) {
6169 case LOCAL_GL_BOOL:
6170 ret = {LOCAL_GL_FLOAT, LOCAL_GL_INT, LOCAL_GL_UNSIGNED_INT};
6171 break;
6172 case LOCAL_GL_BOOL_VEC2:
6173 ret = {LOCAL_GL_FLOAT_VEC2, LOCAL_GL_INT_VEC2,
6174 LOCAL_GL_UNSIGNED_INT_VEC2};
6175 break;
6176 case LOCAL_GL_BOOL_VEC3:
6177 ret = {LOCAL_GL_FLOAT_VEC3, LOCAL_GL_INT_VEC3,
6178 LOCAL_GL_UNSIGNED_INT_VEC3};
6179 break;
6180 case LOCAL_GL_BOOL_VEC4:
6181 ret = {LOCAL_GL_FLOAT_VEC4, LOCAL_GL_INT_VEC4,
6182 LOCAL_GL_UNSIGNED_INT_VEC4};
6183 break;
6185 case LOCAL_GL_SAMPLER_2D:
6186 case LOCAL_GL_SAMPLER_3D:
6187 case LOCAL_GL_SAMPLER_CUBE:
6188 case LOCAL_GL_SAMPLER_2D_SHADOW:
6189 case LOCAL_GL_SAMPLER_2D_ARRAY:
6190 case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
6191 case LOCAL_GL_SAMPLER_CUBE_SHADOW:
6192 case LOCAL_GL_INT_SAMPLER_2D:
6193 case LOCAL_GL_INT_SAMPLER_3D:
6194 case LOCAL_GL_INT_SAMPLER_CUBE:
6195 case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
6196 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
6197 case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
6198 case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
6199 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
6200 ret = {LOCAL_GL_INT};
6201 break;
6203 default:
6204 ret = {elemType};
6205 break;
6208 std::array<uint16_t, 3> arr = {};
6209 MOZ_ASSERT(arr[2] == 0);
6210 for (const auto i : IntegerRange(ret.size())) {
6211 arr[i] = AssertedCast<uint16_t>(ret[i]);
6213 return arr;
6216 void ClientWebGLContext::GetProgramInfoLog(const WebGLProgramJS& prog,
6217 nsAString& retval) const {
6218 retval.SetIsVoid(true);
6219 const FuncScope funcScope(*this, "getProgramInfoLog");
6220 if (IsContextLost()) return;
6221 if (!prog.ValidateUsable(*this, "program")) return;
6223 const auto& res = GetLinkResult(prog);
6224 CopyUTF8toUTF16(res.log, retval);
6227 void ClientWebGLContext::GetProgramParameter(
6228 JSContext* const js, const WebGLProgramJS& prog, const GLenum pname,
6229 JS::MutableHandle<JS::Value> retval) const {
6230 retval.set(JS::NullValue());
6231 const FuncScope funcScope(*this, "getProgramParameter");
6232 if (IsContextLost()) return;
6233 if (!prog.ValidateUsable(*this, "program")) return;
6235 retval.set([&]() -> JS::Value {
6236 switch (pname) {
6237 case LOCAL_GL_DELETE_STATUS:
6238 // "Is flagged for deletion?"
6239 return JS::BooleanValue(!prog.mKeepAlive);
6240 case LOCAL_GL_VALIDATE_STATUS:
6241 return JS::BooleanValue(prog.mLastValidate);
6242 case LOCAL_GL_ATTACHED_SHADERS: {
6243 size_t shaders = 0;
6244 for (const auto& pair : prog.mNextLink_Shaders) {
6245 const auto& slot = pair.second;
6246 if (slot.shader) {
6247 shaders += 1;
6250 return JS::NumberValue(shaders);
6252 default:
6253 break;
6256 const auto& res = GetLinkResult(prog);
6258 switch (pname) {
6259 case LOCAL_GL_LINK_STATUS:
6260 return JS::BooleanValue(res.success);
6262 case LOCAL_GL_ACTIVE_ATTRIBUTES:
6263 return JS::NumberValue(res.active.activeAttribs.size());
6265 case LOCAL_GL_ACTIVE_UNIFORMS:
6266 return JS::NumberValue(res.active.activeUniforms.size());
6268 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
6269 if (!mIsWebGL2) break;
6270 return JS::NumberValue(res.tfBufferMode);
6272 case LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS:
6273 if (!mIsWebGL2) break;
6274 return JS::NumberValue(res.active.activeTfVaryings.size());
6276 case LOCAL_GL_ACTIVE_UNIFORM_BLOCKS:
6277 if (!mIsWebGL2) break;
6278 return JS::NumberValue(res.active.activeUniformBlocks.size());
6280 default:
6281 break;
6283 EnqueueError_ArgEnum("pname", pname);
6284 return JS::NullValue();
6285 }());
6288 // -
6289 // WebGLShaderJS
6291 void ClientWebGLContext::CompileShader(WebGLShaderJS& shader) const {
6292 const FuncScope funcScope(*this, "compileShader");
6293 if (IsContextLost()) return;
6294 if (!shader.ValidateUsable(*this, "shader")) return;
6296 shader.mResult = {};
6297 Run<RPROC(CompileShader)>(shader.mId);
6300 void ClientWebGLContext::GetShaderInfoLog(const WebGLShaderJS& shader,
6301 nsAString& retval) const {
6302 retval.SetIsVoid(true);
6303 const FuncScope funcScope(*this, "getShaderInfoLog");
6304 if (IsContextLost()) return;
6305 if (!shader.ValidateUsable(*this, "shader")) return;
6307 const auto& result = GetCompileResult(shader);
6308 CopyUTF8toUTF16(result.log, retval);
6311 void ClientWebGLContext::GetShaderParameter(
6312 JSContext* const cx, const WebGLShaderJS& shader, const GLenum pname,
6313 JS::MutableHandle<JS::Value> retval) const {
6314 retval.set(JS::NullValue());
6315 const FuncScope funcScope(*this, "getShaderParameter");
6316 if (IsContextLost()) return;
6317 if (!shader.ValidateUsable(*this, "shader")) return;
6319 retval.set([&]() -> JS::Value {
6320 switch (pname) {
6321 case LOCAL_GL_SHADER_TYPE:
6322 return JS::NumberValue(shader.mType);
6324 case LOCAL_GL_DELETE_STATUS: // "Is flagged for deletion?"
6325 return JS::BooleanValue(!shader.mKeepAlive);
6327 case LOCAL_GL_COMPILE_STATUS: {
6328 const auto& result = GetCompileResult(shader);
6329 return JS::BooleanValue(result.success);
6332 default:
6333 EnqueueError_ArgEnum("pname", pname);
6334 return JS::NullValue();
6336 }());
6339 void ClientWebGLContext::GetShaderSource(const WebGLShaderJS& shader,
6340 nsAString& retval) const {
6341 retval.SetIsVoid(true);
6342 const FuncScope funcScope(*this, "getShaderSource");
6343 if (IsContextLost()) return;
6344 if (!shader.ValidateUsable(*this, "shader")) return;
6346 CopyUTF8toUTF16(shader.mSource, retval);
6349 void ClientWebGLContext::GetTranslatedShaderSource(const WebGLShaderJS& shader,
6350 nsAString& retval) const {
6351 retval.SetIsVoid(true);
6352 const FuncScope funcScope(*this, "getTranslatedShaderSource");
6353 if (IsContextLost()) return;
6354 if (!shader.ValidateUsable(*this, "shader")) return;
6356 const auto& result = GetCompileResult(shader);
6357 CopyUTF8toUTF16(result.translatedSource, retval);
6360 void ClientWebGLContext::ShaderSource(WebGLShaderJS& shader,
6361 const nsAString& sourceU16) const {
6362 const FuncScope funcScope(*this, "shaderSource");
6363 if (IsContextLost()) return;
6364 if (!shader.ValidateUsable(*this, "shader")) return;
6366 shader.mSource = ToString(NS_ConvertUTF16toUTF8(sourceU16));
6367 Run<RPROC(ShaderSource)>(shader.mId, shader.mSource);
6370 // -
6372 const webgl::CompileResult& ClientWebGLContext::GetCompileResult(
6373 const WebGLShaderJS& shader) const {
6374 if (shader.mResult.pending) {
6375 shader.mResult = [&]() {
6376 const auto& inProcess = mNotLost->inProcess;
6377 if (inProcess) {
6378 return inProcess->GetCompileResult(shader.mId);
6380 const auto& child = mNotLost->outOfProcess;
6381 child->FlushPendingCmds();
6382 webgl::CompileResult ret = {};
6383 if (!child->SendGetCompileResult(shader.mId, &ret)) {
6384 ret = {};
6386 return ret;
6387 }();
6389 return shader.mResult;
6392 const webgl::LinkResult& ClientWebGLContext::GetLinkResult(
6393 const WebGLProgramJS& prog) const {
6394 if (prog.mResult->pending) {
6395 const auto notLost =
6396 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
6397 if (!notLost) return *(prog.mResult);
6399 *(prog.mResult) = [&]() {
6400 const auto& inProcess = mNotLost->inProcess;
6401 if (inProcess) {
6402 return inProcess->GetLinkResult(prog.mId);
6404 const auto& child = mNotLost->outOfProcess;
6405 child->FlushPendingCmds();
6406 webgl::LinkResult ret;
6407 if (!child->SendGetLinkResult(prog.mId, &ret)) {
6408 ret = {};
6410 return ret;
6411 }();
6413 prog.mUniformBlockBindings.resize(
6414 prog.mResult->active.activeUniformBlocks.size());
6416 auto& state = State();
6417 if (state.mCurrentProgram == &prog && prog.mResult->success) {
6418 state.mActiveLinkResult = prog.mResult;
6421 return *(prog.mResult);
6424 #undef RPROC
6426 // ---------------------------
6428 bool ClientWebGLContext::ValidateArrayBufferView(
6429 const dom::ArrayBufferView& view, GLuint elemOffset,
6430 GLuint elemCountOverride, const GLenum errorEnum, uint8_t** const out_bytes,
6431 size_t* const out_byteLen) const {
6432 view.ComputeState();
6433 uint8_t* const bytes = view.Data();
6434 const size_t byteLen = view.Length();
6436 const auto& elemSize = SizeOfViewElem(view);
6438 size_t elemCount = byteLen / elemSize;
6439 if (elemOffset > elemCount) {
6440 EnqueueError(errorEnum, "Invalid offset into ArrayBufferView.");
6441 return false;
6443 elemCount -= elemOffset;
6445 if (elemCountOverride) {
6446 if (elemCountOverride > elemCount) {
6447 EnqueueError(errorEnum, "Invalid sub-length for ArrayBufferView.");
6448 return false;
6450 elemCount = elemCountOverride;
6453 *out_bytes = bytes + (elemOffset * elemSize);
6454 *out_byteLen = elemCount * elemSize;
6455 return true;
6458 // ---------------------------
6460 webgl::ObjectJS::ObjectJS(const ClientWebGLContext& webgl)
6461 : mGeneration(webgl.mNotLost), mId(webgl.mNotLost->state.NextId()) {}
6463 // -
6465 WebGLFramebufferJS::WebGLFramebufferJS(const ClientWebGLContext& webgl,
6466 bool opaque)
6467 : webgl::ObjectJS(webgl), mOpaque(opaque) {
6468 (void)mAttachments[LOCAL_GL_DEPTH_ATTACHMENT];
6469 (void)mAttachments[LOCAL_GL_STENCIL_ATTACHMENT];
6470 if (!webgl.mIsWebGL2) {
6471 (void)mAttachments[LOCAL_GL_DEPTH_STENCIL_ATTACHMENT];
6474 EnsureColorAttachments();
6477 void WebGLFramebufferJS::EnsureColorAttachments() {
6478 const auto& webgl = Context();
6479 const auto& limits = webgl->Limits();
6480 auto maxColorDrawBuffers = limits.maxColorDrawBuffers;
6481 if (!webgl->mIsWebGL2 &&
6482 !webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers)) {
6483 maxColorDrawBuffers = 1;
6485 for (const auto i : IntegerRange(maxColorDrawBuffers)) {
6486 (void)mAttachments[LOCAL_GL_COLOR_ATTACHMENT0 + i];
6490 WebGLProgramJS::WebGLProgramJS(const ClientWebGLContext& webgl)
6491 : webgl::ObjectJS(webgl),
6492 mKeepAlive(std::make_shared<webgl::ProgramKeepAlive>(*this)),
6493 mKeepAliveWeak(mKeepAlive) {
6494 (void)mNextLink_Shaders[LOCAL_GL_VERTEX_SHADER];
6495 (void)mNextLink_Shaders[LOCAL_GL_FRAGMENT_SHADER];
6497 mResult = std::make_shared<webgl::LinkResult>();
6500 WebGLShaderJS::WebGLShaderJS(const ClientWebGLContext& webgl, const GLenum type)
6501 : webgl::ObjectJS(webgl),
6502 mType(type),
6503 mKeepAlive(std::make_shared<webgl::ShaderKeepAlive>(*this)),
6504 mKeepAliveWeak(mKeepAlive) {}
6506 WebGLTransformFeedbackJS::WebGLTransformFeedbackJS(
6507 const ClientWebGLContext& webgl)
6508 : webgl::ObjectJS(webgl),
6509 mAttribBuffers(webgl::kMaxTransformFeedbackSeparateAttribs) {}
6511 WebGLVertexArrayJS::WebGLVertexArrayJS(const ClientWebGLContext& webgl)
6512 : webgl::ObjectJS(webgl), mAttribBuffers(webgl.Limits().maxVertexAttribs) {}
6514 // -
6516 #define _(WebGLType) \
6517 JSObject* WebGLType##JS::WrapObject(JSContext* const cx, \
6518 JS::Handle<JSObject*> givenProto) { \
6519 return dom::WebGLType##_Binding::Wrap(cx, this, givenProto); \
6522 _(WebGLBuffer)
6523 _(WebGLFramebuffer)
6524 _(WebGLProgram)
6525 _(WebGLQuery)
6526 _(WebGLRenderbuffer)
6527 _(WebGLSampler)
6528 _(WebGLShader)
6529 _(WebGLSync)
6530 _(WebGLTexture)
6531 _(WebGLTransformFeedback)
6532 _(WebGLUniformLocation)
6533 //_(WebGLVertexArray) // The webidl is `WebGLVertexArrayObject` :(
6535 #undef _
6537 JSObject* WebGLVertexArrayJS::WrapObject(JSContext* const cx,
6538 JS::Handle<JSObject*> givenProto) {
6539 return dom::WebGLVertexArrayObject_Binding::Wrap(cx, this, givenProto);
6542 bool WebGLActiveInfoJS::WrapObject(JSContext* const cx,
6543 JS::Handle<JSObject*> givenProto,
6544 JS::MutableHandle<JSObject*> reflector) {
6545 return dom::WebGLActiveInfo_Binding::Wrap(cx, this, givenProto, reflector);
6548 bool WebGLShaderPrecisionFormatJS::WrapObject(
6549 JSContext* const cx, JS::Handle<JSObject*> givenProto,
6550 JS::MutableHandle<JSObject*> reflector) {
6551 return dom::WebGLShaderPrecisionFormat_Binding::Wrap(cx, this, givenProto,
6552 reflector);
6555 // ---------------------
6557 // Todo: Move this to RefPtr.h.
6558 template <typename T>
6559 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
6560 const RefPtr<T>& field, const char* name,
6561 uint32_t flags) {
6562 ImplCycleCollectionTraverse(callback, const_cast<RefPtr<T>&>(field), name,
6563 flags);
6566 // -
6568 template <typename T>
6569 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
6570 const std::vector<RefPtr<T>>& field,
6571 const char* name, uint32_t flags) {
6572 for (const auto& cur : field) {
6573 ImplCycleCollectionTraverse(callback, cur, name, flags);
6577 template <typename T>
6578 void ImplCycleCollectionUnlink(std::vector<RefPtr<T>>& field) {
6579 field = {};
6582 // -
6584 template <typename T, size_t N>
6585 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
6586 const std::array<RefPtr<T>, N>& field,
6587 const char* name, uint32_t flags) {
6588 for (const auto& cur : field) {
6589 ImplCycleCollectionTraverse(callback, cur, name, flags);
6593 template <typename T, size_t N>
6594 void ImplCycleCollectionUnlink(std::array<RefPtr<T>, N>& field) {
6595 field = {};
6598 // -
6600 template <typename T>
6601 void ImplCycleCollectionTraverse(
6602 nsCycleCollectionTraversalCallback& callback,
6603 const std::unordered_map<GLenum, RefPtr<T>>& field, const char* name,
6604 uint32_t flags) {
6605 for (const auto& pair : field) {
6606 ImplCycleCollectionTraverse(callback, pair.second, name, flags);
6610 template <typename T>
6611 void ImplCycleCollectionUnlink(std::unordered_map<GLenum, RefPtr<T>>& field) {
6612 field = {};
6615 // -
6617 void ImplCycleCollectionTraverse(
6618 nsCycleCollectionTraversalCallback& callback,
6619 const std::unordered_map<GLenum, WebGLFramebufferJS::Attachment>& field,
6620 const char* name, uint32_t flags) {
6621 for (const auto& pair : field) {
6622 const auto& attach = pair.second;
6623 ImplCycleCollectionTraverse(callback, attach.rb, name, flags);
6624 ImplCycleCollectionTraverse(callback, attach.tex, name, flags);
6628 void ImplCycleCollectionUnlink(
6629 std::unordered_map<GLenum, WebGLFramebufferJS::Attachment>& field) {
6630 field = {};
6633 // -
6635 void ImplCycleCollectionTraverse(
6636 nsCycleCollectionTraversalCallback& callback,
6637 const std::unordered_map<GLenum, WebGLProgramJS::Attachment>& field,
6638 const char* name, uint32_t flags) {
6639 for (const auto& pair : field) {
6640 const auto& attach = pair.second;
6641 ImplCycleCollectionTraverse(callback, attach.shader, name, flags);
6645 void ImplCycleCollectionUnlink(
6646 std::unordered_map<GLenum, WebGLProgramJS::Attachment>& field) {
6647 field = {};
6650 // -
6652 void ImplCycleCollectionUnlink(
6653 const RefPtr<ClientWebGLExtensionLoseContext>& field) {
6654 const_cast<RefPtr<ClientWebGLExtensionLoseContext>&>(field) = nullptr;
6656 void ImplCycleCollectionUnlink(const RefPtr<WebGLProgramJS>& field) {
6657 const_cast<RefPtr<WebGLProgramJS>&>(field) = nullptr;
6659 void ImplCycleCollectionUnlink(const RefPtr<WebGLShaderJS>& field) {
6660 const_cast<RefPtr<WebGLShaderJS>&>(field) = nullptr;
6663 // ----------------------
6665 void ImplCycleCollectionTraverse(
6666 nsCycleCollectionTraversalCallback& callback,
6667 const std::shared_ptr<webgl::NotLostData>& field, const char* name,
6668 uint32_t flags) {
6669 if (!field) return;
6671 ImplCycleCollectionTraverse(callback, field->extensions,
6672 "NotLostData.extensions", flags);
6674 const auto& state = field->state;
6676 ImplCycleCollectionTraverse(callback, state.mDefaultTfo, "state.mDefaultTfo",
6677 flags);
6678 ImplCycleCollectionTraverse(callback, state.mDefaultVao, "state.mDefaultVao",
6679 flags);
6681 ImplCycleCollectionTraverse(callback, state.mCurrentProgram,
6682 "state.mCurrentProgram", flags);
6684 ImplCycleCollectionTraverse(callback, state.mBoundBufferByTarget,
6685 "state.mBoundBufferByTarget", flags);
6686 ImplCycleCollectionTraverse(callback, state.mBoundUbos, "state.mBoundUbos",
6687 flags);
6688 ImplCycleCollectionTraverse(callback, state.mBoundDrawFb,
6689 "state.mBoundDrawFb", flags);
6690 ImplCycleCollectionTraverse(callback, state.mBoundReadFb,
6691 "state.mBoundReadFb", flags);
6692 ImplCycleCollectionTraverse(callback, state.mBoundRb, "state.mBoundRb",
6693 flags);
6694 ImplCycleCollectionTraverse(callback, state.mBoundTfo, "state.mBoundTfo",
6695 flags);
6696 ImplCycleCollectionTraverse(callback, state.mBoundVao, "state.mBoundVao",
6697 flags);
6698 ImplCycleCollectionTraverse(callback, state.mCurrentQueryByTarget,
6699 "state.state.mCurrentQueryByTarget", flags);
6701 for (const auto& texUnit : state.mTexUnits) {
6702 ImplCycleCollectionTraverse(callback, texUnit.sampler,
6703 "state.mTexUnits[].sampler", flags);
6704 ImplCycleCollectionTraverse(callback, texUnit.texByTarget,
6705 "state.mTexUnits[].texByTarget", flags);
6709 void ImplCycleCollectionUnlink(std::shared_ptr<webgl::NotLostData>& field) {
6710 if (!field) return;
6711 const auto keepAlive = field;
6712 keepAlive->extensions = {};
6713 keepAlive->state = {};
6714 field = nullptr;
6717 // -----------------------------------------------------
6719 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLBufferJS)
6720 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLFramebufferJS, mAttachments)
6721 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLProgramJS, mNextLink_Shaders)
6722 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLQueryJS)
6723 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLRenderbufferJS)
6724 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLSamplerJS)
6725 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLShaderJS)
6726 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLSyncJS)
6727 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLTextureJS)
6728 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLTransformFeedbackJS, mAttribBuffers,
6729 mActiveProgram)
6730 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLUniformLocationJS)
6731 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLVertexArrayJS, mIndexBuffer,
6732 mAttribBuffers)
6734 // -
6736 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ClientWebGLContext)
6737 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
6738 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
6739 NS_INTERFACE_MAP_ENTRY(nsISupports)
6740 NS_INTERFACE_MAP_END
6742 NS_IMPL_CYCLE_COLLECTING_ADDREF(ClientWebGLContext)
6743 NS_IMPL_CYCLE_COLLECTING_RELEASE(ClientWebGLContext)
6745 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(
6746 ClientWebGLContext, mExtLoseContext, mNotLost,
6747 // Don't forget nsICanvasRenderingContextInternal:
6748 mCanvasElement, mOffscreenCanvas)
6750 // -----------------------------
6752 #define _(X) \
6753 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGL##X##JS, AddRef) \
6754 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGL##X##JS, Release)
6756 _(Buffer)
6757 _(Framebuffer)
6758 _(Program)
6759 _(Query)
6760 _(Renderbuffer)
6761 _(Sampler)
6762 _(Shader)
6763 _(Sync)
6764 _(Texture)
6765 _(TransformFeedback)
6766 _(UniformLocation)
6767 _(VertexArray)
6769 #undef _
6771 } // namespace mozilla