Bug 1700051: part 26) Correct typo in comment of `mozInlineSpellWordUtil::BuildSoftTe...
[gecko.git] / dom / canvas / ClientWebGLContext.cpp
blob15752fb28449b167257fa5f21c5292821c795220
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 "ClientWebGLExtensions.h"
9 #include "Layers.h"
10 #include "gfxCrashReporterUtils.h"
11 #include "HostWebGLContext.h"
12 #include "js/ScalarType.h" // js::Scalar::Type
13 #include "mozilla/dom/Document.h"
14 #include "mozilla/dom/SanitizeRenderer.h"
15 #include "mozilla/dom/ToJSValue.h"
16 #include "mozilla/dom/WebGLContextEvent.h"
17 #include "mozilla/dom/WorkerCommon.h"
18 #include "mozilla/EnumeratedRange.h"
19 #include "mozilla/gfx/gfxVars.h"
20 #include "mozilla/ipc/Shmem.h"
21 #include "mozilla/layers/CompositorBridgeChild.h"
22 #include "mozilla/layers/ImageBridgeChild.h"
23 #include "mozilla/layers/LayerTransactionChild.h"
24 #include "mozilla/layers/OOPCanvasRenderer.h"
25 #include "mozilla/layers/TextureClientSharedSurface.h"
26 #include "mozilla/layers/WebRenderUserData.h"
27 #include "mozilla/layers/WebRenderCanvasRenderer.h"
28 #include "mozilla/Preferences.h"
29 #include "mozilla/ScopeExit.h"
30 #include "mozilla/StaticPrefs_webgl.h"
31 #include "nsContentUtils.h"
32 #include "nsDisplayList.h"
33 #include "TexUnpackBlob.h"
34 #include "WebGLMethodDispatcher.h"
35 #include "WebGLChild.h"
36 #include "WebGLValidateStrings.h"
38 namespace mozilla {
40 webgl::NotLostData::NotLostData(ClientWebGLContext& _context)
41 : context(_context) {}
43 webgl::NotLostData::~NotLostData() {
44 if (outOfProcess) {
45 Unused << dom::WebGLChild::Send__delete__(outOfProcess.get());
49 // -
51 bool webgl::ObjectJS::ValidateForContext(
52 const ClientWebGLContext& targetContext, const char* const argName) const {
53 if (!IsForContext(targetContext)) {
54 targetContext.EnqueueError(
55 LOCAL_GL_INVALID_OPERATION,
56 "`%s` is from a different (or lost) WebGL context.", argName);
57 return false;
59 return true;
62 void webgl::ObjectJS::WarnInvalidUse(const ClientWebGLContext& targetContext,
63 const char* const argName) const {
64 if (!ValidateForContext(targetContext, argName)) return;
66 const auto errEnum = ErrorOnDeleted();
67 targetContext.EnqueueError(errEnum, "Object `%s` is already deleted.",
68 argName);
71 static bool GetJSScalarFromGLType(GLenum type,
72 js::Scalar::Type* const out_scalarType) {
73 switch (type) {
74 case LOCAL_GL_BYTE:
75 *out_scalarType = js::Scalar::Int8;
76 return true;
78 case LOCAL_GL_UNSIGNED_BYTE:
79 *out_scalarType = js::Scalar::Uint8;
80 return true;
82 case LOCAL_GL_SHORT:
83 *out_scalarType = js::Scalar::Int16;
84 return true;
86 case LOCAL_GL_HALF_FLOAT:
87 case LOCAL_GL_HALF_FLOAT_OES:
88 case LOCAL_GL_UNSIGNED_SHORT:
89 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
90 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
91 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
92 *out_scalarType = js::Scalar::Uint16;
93 return true;
95 case LOCAL_GL_UNSIGNED_INT:
96 case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
97 case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
98 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
99 case LOCAL_GL_UNSIGNED_INT_24_8:
100 *out_scalarType = js::Scalar::Uint32;
101 return true;
102 case LOCAL_GL_INT:
103 *out_scalarType = js::Scalar::Int32;
104 return true;
106 case LOCAL_GL_FLOAT:
107 *out_scalarType = js::Scalar::Float32;
108 return true;
110 default:
111 return false;
115 ClientWebGLContext::ClientWebGLContext(const bool webgl2)
116 : mIsWebGL2(webgl2),
117 mExtLoseContext(new ClientWebGLExtensionLoseContext(*this)) {}
119 ClientWebGLContext::~ClientWebGLContext() { RemovePostRefreshObserver(); }
121 void ClientWebGLContext::JsWarning(const std::string& utf8) const {
122 if (!mCanvasElement) {
123 return;
125 dom::AutoJSAPI api;
126 if (!api.Init(mCanvasElement->OwnerDoc()->GetScopeObject())) {
127 return;
129 const auto& cx = api.cx();
130 JS::WarnUTF8(cx, "%s", utf8.c_str());
133 void AutoJsWarning(const std::string& utf8) {
134 const AutoJSContext cx;
135 JS::WarnUTF8(cx, "%s", utf8.c_str());
138 // ---------
140 bool ClientWebGLContext::DispatchEvent(const nsAString& eventName) const {
141 const auto kCanBubble = CanBubble::eYes;
142 const auto kIsCancelable = Cancelable::eYes;
143 bool useDefaultHandler = true;
145 if (mCanvasElement) {
146 nsContentUtils::DispatchTrustedEvent(
147 mCanvasElement->OwnerDoc(), static_cast<nsIContent*>(mCanvasElement),
148 eventName, kCanBubble, kIsCancelable, &useDefaultHandler);
149 } else if (mOffscreenCanvas) {
150 // OffscreenCanvas case
151 RefPtr<dom::Event> event =
152 new dom::Event(mOffscreenCanvas, nullptr, nullptr);
153 event->InitEvent(eventName, kCanBubble, kIsCancelable);
154 event->SetTrusted(true);
155 useDefaultHandler = mOffscreenCanvas->DispatchEvent(
156 *event, dom::CallerType::System, IgnoreErrors());
158 return useDefaultHandler;
161 // -
163 void ClientWebGLContext::EmulateLoseContext() const {
164 const FuncScope funcScope(*this, "loseContext");
165 if (mLossStatus != webgl::LossStatus::Ready) {
166 JsWarning("loseContext: Already lost.");
167 if (!mNextError) {
168 mNextError = LOCAL_GL_INVALID_OPERATION;
170 return;
172 OnContextLoss(webgl::ContextLossReason::Manual);
175 void ClientWebGLContext::OnContextLoss(
176 const webgl::ContextLossReason reason) const {
177 MOZ_ASSERT(NS_IsMainThread());
178 JsWarning("WebGL context was lost.");
180 if (mNotLost) {
181 for (const auto& ext : mNotLost->extensions) {
182 if (!ext) continue;
183 ext->mContext = nullptr; // Detach.
185 mNotLost = {}; // Lost now!
186 mNextError = LOCAL_GL_CONTEXT_LOST_WEBGL;
189 switch (reason) {
190 case webgl::ContextLossReason::Guilty:
191 mLossStatus = webgl::LossStatus::LostForever;
192 break;
194 case webgl::ContextLossReason::None:
195 mLossStatus = webgl::LossStatus::Lost;
196 break;
198 case webgl::ContextLossReason::Manual:
199 mLossStatus = webgl::LossStatus::LostManually;
200 break;
203 const auto weak = WeakPtr<const ClientWebGLContext>(this);
204 const auto fnRun = [weak]() {
205 const auto strong = RefPtr<const ClientWebGLContext>(weak);
206 if (!strong) return;
207 strong->Event_webglcontextlost();
209 already_AddRefed<mozilla::Runnable> runnable =
210 NS_NewRunnableFunction("enqueue Event_webglcontextlost", fnRun);
211 NS_DispatchToCurrentThread(std::move(runnable));
214 void ClientWebGLContext::Event_webglcontextlost() const {
215 const bool useDefaultHandler = DispatchEvent(u"webglcontextlost"_ns);
216 if (useDefaultHandler) {
217 mLossStatus = webgl::LossStatus::LostForever;
220 if (mLossStatus == webgl::LossStatus::Lost) {
221 RestoreContext(webgl::LossStatus::Lost);
225 void ClientWebGLContext::RestoreContext(
226 const webgl::LossStatus requiredStatus) const {
227 if (requiredStatus != mLossStatus) {
228 JsWarning(
229 "restoreContext: Only valid iff context lost with loseContext().");
230 if (!mNextError) {
231 mNextError = LOCAL_GL_INVALID_OPERATION;
233 return;
235 MOZ_RELEASE_ASSERT(mLossStatus == webgl::LossStatus::Lost ||
236 mLossStatus == webgl::LossStatus::LostManually);
238 if (mAwaitingRestore) return;
239 mAwaitingRestore = true;
241 const auto weak = WeakPtr<const ClientWebGLContext>(this);
242 const auto fnRun = [weak]() {
243 const auto strong = RefPtr<const ClientWebGLContext>(weak);
244 if (!strong) return;
245 strong->Event_webglcontextrestored();
247 already_AddRefed<mozilla::Runnable> runnable =
248 NS_NewRunnableFunction("enqueue Event_webglcontextrestored", fnRun);
249 NS_DispatchToCurrentThread(std::move(runnable));
252 void ClientWebGLContext::Event_webglcontextrestored() const {
253 mAwaitingRestore = false;
254 mLossStatus = webgl::LossStatus::Ready;
255 mNextError = 0;
257 const uvec2 requestSize = {mCanvasElement->Width(), mCanvasElement->Height()};
258 const auto mutThis = const_cast<ClientWebGLContext*>(
259 this); // TODO: Make context loss non-mutable.
260 if (!mutThis->CreateHostContext(requestSize)) {
261 mLossStatus = webgl::LossStatus::LostForever;
262 return;
265 (void)DispatchEvent(u"webglcontextrestored"_ns);
268 // ---------
270 void ClientWebGLContext::ThrowEvent_WebGLContextCreationError(
271 const std::string& text) const {
272 nsCString msg;
273 msg.AppendPrintf("Failed to create WebGL context: %s", text.c_str());
274 JsWarning(msg.BeginReading());
276 RefPtr<dom::EventTarget> target = mCanvasElement;
277 if (!target && mOffscreenCanvas) {
278 target = mOffscreenCanvas;
279 } else if (!target) {
280 return;
283 const auto kEventName = u"webglcontextcreationerror"_ns;
285 dom::WebGLContextEventInit eventInit;
286 // eventInit.mCancelable = true; // The spec says this, but it's silly.
287 eventInit.mStatusMessage = NS_ConvertASCIItoUTF16(text.c_str());
289 const RefPtr<dom::WebGLContextEvent> event =
290 dom::WebGLContextEvent::Constructor(target, kEventName, eventInit);
291 event->SetTrusted(true);
293 target->DispatchEvent(*event);
296 // -
298 // If we are running WebGL in this process then call the HostWebGLContext
299 // method directly. Otherwise, dispatch over IPC.
300 template <typename MethodType, MethodType method, typename... Args>
301 void ClientWebGLContext::Run(Args&&... args) const {
302 const auto notLost =
303 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
304 if (IsContextLost()) return;
306 const auto& inProcess = notLost->inProcess;
307 if (inProcess) {
308 return (inProcess.get()->*method)(std::forward<Args>(args)...);
311 const auto& child = notLost->outOfProcess;
313 const auto id = IdByMethod<MethodType, method>();
315 const auto size = webgl::SerializedSize(id, args...);
316 const auto maybeDest = child->AllocPendingCmdBytes(size);
317 if (!maybeDest) {
318 JsWarning("Failed to allocate internal command buffer.");
319 OnContextLoss(webgl::ContextLossReason::None);
320 return;
322 const auto& destBytes = *maybeDest;
323 webgl::Serialize(destBytes, id, args...);
326 // -------------------------------------------------------------------------
327 // Client-side helper methods. Dispatch to a Host method.
328 // -------------------------------------------------------------------------
330 #define RPROC(_METHOD) \
331 decltype(&HostWebGLContext::_METHOD), &HostWebGLContext::_METHOD
333 // ------------------------- Composition, etc -------------------------
335 void ClientWebGLContext::OnBeforePaintTransaction() {
336 const RefPtr<layers::ImageBridgeChild> imageBridge =
337 layers::ImageBridgeChild::GetSingleton();
339 const auto texType = layers::TexTypeForWebgl(imageBridge);
340 Present(nullptr, texType);
343 void ClientWebGLContext::EndComposition() {
344 // Mark ourselves as no longer invalidated.
345 MarkContextClean();
348 // -
350 void ClientWebGLContext::Present(WebGLFramebufferJS* const xrFb,
351 const layers::TextureType type,
352 const bool webvr) {
353 if (!mIsCanvasDirty && !xrFb) return;
354 if (!xrFb) {
355 mIsCanvasDirty = false;
358 Run<RPROC(Present)>(xrFb ? xrFb->mId : 0, type, webvr);
361 Maybe<layers::SurfaceDescriptor> ClientWebGLContext::GetFrontBuffer(
362 WebGLFramebufferJS* const fb, bool vr) {
363 const auto notLost = mNotLost;
364 if (IsContextLost()) return {};
366 const auto& inProcess = mNotLost->inProcess;
367 if (inProcess) {
368 return inProcess->GetFrontBuffer(fb ? fb->mId : 0, vr);
371 const auto& child = mNotLost->outOfProcess;
372 child->FlushPendingCmds();
373 Maybe<layers::SurfaceDescriptor> ret;
374 if (!child->SendGetFrontBuffer(fb ? fb->mId : 0, vr, &ret)) return {};
375 return ret;
378 void ClientWebGLContext::ClearVRSwapChain() { Run<RPROC(ClearVRSwapChain)>(); }
380 // -
382 already_AddRefed<layers::Layer> ClientWebGLContext::GetCanvasLayer(
383 nsDisplayListBuilder* builder, Layer* oldLayer, LayerManager* manager) {
384 if (!mResetLayer && oldLayer) {
385 RefPtr<layers::Layer> ret = oldLayer;
386 return ret.forget();
389 RefPtr<CanvasLayer> canvasLayer = manager->CreateCanvasLayer();
390 if (!canvasLayer) {
391 NS_WARNING("CreateCanvasLayer returned null!");
392 return nullptr;
395 const auto canvasRenderer = canvasLayer->CreateOrGetCanvasRenderer();
396 if (!InitializeCanvasRenderer(builder, canvasRenderer)) return nullptr;
398 uint32_t flags = 0;
399 if (GetIsOpaque()) {
400 flags |= Layer::CONTENT_OPAQUE;
402 canvasLayer->SetContentFlags(flags);
404 mResetLayer = false;
405 return canvasLayer.forget();
408 bool ClientWebGLContext::UpdateWebRenderCanvasData(
409 nsDisplayListBuilder* aBuilder, WebRenderCanvasData* aCanvasData) {
410 CanvasRenderer* renderer = aCanvasData->GetCanvasRenderer();
412 if (!mResetLayer && renderer) {
413 return true;
416 renderer = aCanvasData->CreateCanvasRenderer();
417 if (!InitializeCanvasRenderer(aBuilder, renderer)) {
418 // Clear CanvasRenderer of WebRenderCanvasData
419 aCanvasData->ClearCanvasRenderer();
420 return false;
423 MOZ_ASSERT(renderer);
424 mResetLayer = false;
425 return true;
428 bool ClientWebGLContext::InitializeCanvasRenderer(
429 nsDisplayListBuilder* aBuilder, CanvasRenderer* aRenderer) {
430 const FuncScope funcScope(*this, "<InitializeCanvasRenderer>");
431 if (IsContextLost()) return false;
433 layers::CanvasRendererData data;
434 data.mContext = mSharedPtrPtr;
435 data.mOriginPos = gl::OriginPos::BottomLeft;
437 const auto& options = *mInitialOptions;
438 const auto& size = DrawingBufferSize();
439 data.mIsOpaque = !options.alpha;
440 data.mIsAlphaPremult = !options.alpha || options.premultipliedAlpha;
441 data.mSize = {size.x, size.y};
443 if (aBuilder->IsPaintingToWindow() && mCanvasElement) {
444 data.mDoPaintCallbacks = true;
447 aRenderer->Initialize(data);
448 aRenderer->SetDirty();
449 return true;
452 layers::LayersBackend ClientWebGLContext::GetCompositorBackendType() const {
453 if (mCanvasElement) {
454 return mCanvasElement->GetCompositorBackendType();
455 } else if (mOffscreenCanvas) {
456 return mOffscreenCanvas->GetCompositorBackendType();
459 return layers::LayersBackend::LAYERS_NONE;
462 mozilla::dom::Document* ClientWebGLContext::GetOwnerDoc() const {
463 MOZ_ASSERT(mCanvasElement);
464 if (!mCanvasElement) {
465 return nullptr;
467 return mCanvasElement->OwnerDoc();
470 void ClientWebGLContext::Commit() {
471 if (mOffscreenCanvas) {
472 mOffscreenCanvas->CommitFrameToCompositor();
476 void ClientWebGLContext::GetCanvas(
477 dom::Nullable<dom::OwningHTMLCanvasElementOrOffscreenCanvas>& retval) {
478 if (mCanvasElement) {
479 MOZ_RELEASE_ASSERT(!mOffscreenCanvas, "GFX: Canvas is offscreen.");
481 if (mCanvasElement->IsInNativeAnonymousSubtree()) {
482 retval.SetNull();
483 } else {
484 retval.SetValue().SetAsHTMLCanvasElement() = mCanvasElement;
486 } else if (mOffscreenCanvas) {
487 retval.SetValue().SetAsOffscreenCanvas() = mOffscreenCanvas;
488 } else {
489 retval.SetNull();
493 void ClientWebGLContext::GetContextAttributes(
494 dom::Nullable<dom::WebGLContextAttributes>& retval) {
495 retval.SetNull();
496 const FuncScope funcScope(*this, "getContextAttributes");
497 if (IsContextLost()) return;
499 dom::WebGLContextAttributes& result = retval.SetValue();
501 const auto& options = mNotLost->info.options;
503 result.mAlpha.Construct(options.alpha);
504 result.mDepth = options.depth;
505 result.mStencil = options.stencil;
506 result.mAntialias.Construct(options.antialias);
507 result.mPremultipliedAlpha = options.premultipliedAlpha;
508 result.mPreserveDrawingBuffer = options.preserveDrawingBuffer;
509 result.mFailIfMajorPerformanceCaveat = options.failIfMajorPerformanceCaveat;
510 result.mPowerPreference = options.powerPreference;
513 // -----------------------
515 NS_IMETHODIMP
516 ClientWebGLContext::SetDimensions(const int32_t signedWidth,
517 const int32_t signedHeight) {
518 const FuncScope funcScope(*this, "<SetDimensions>");
519 MOZ_ASSERT(mInitialOptions);
521 if (mLossStatus != webgl::LossStatus::Ready) {
522 // Attempted resize of a lost context.
523 return NS_OK;
526 uvec2 size = {static_cast<uint32_t>(signedWidth),
527 static_cast<uint32_t>(signedHeight)};
528 if (!size.x) {
529 size.x = 1;
531 if (!size.y) {
532 size.y = 1;
534 const auto prevRequestedSize = mRequestedSize;
535 mRequestedSize = size;
537 mResetLayer = true; // Always treat this as resize.
539 if (mNotLost) {
540 auto& state = State();
542 auto curSize = prevRequestedSize;
543 if (state.mDrawingBufferSize) {
544 curSize = *state.mDrawingBufferSize;
546 if (size == curSize) return NS_OK; // MUST skip no-op resize
548 state.mDrawingBufferSize = Nothing();
549 Run<RPROC(Resize)>(size);
551 MarkCanvasDirty();
552 return NS_OK;
555 // -
556 // Context (re-)creation
558 if (!CreateHostContext(size)) {
559 return NS_ERROR_FAILURE;
561 return NS_OK;
564 static bool IsWebglOutOfProcessEnabled() {
565 bool useOop = StaticPrefs::webgl_out_of_process();
567 if (!gfx::gfxVars::AllowWebglOop()) {
568 useOop = false;
570 if (StaticPrefs::webgl_out_of_process_force()) {
571 useOop = true;
573 return useOop;
576 static inline bool StartsWith(const std::string& haystack,
577 const std::string& needle) {
578 return haystack.find(needle) == 0;
581 bool ClientWebGLContext::CreateHostContext(const uvec2& requestedSize) {
582 const auto pNotLost = std::make_shared<webgl::NotLostData>(*this);
583 auto& notLost = *pNotLost;
585 auto res = [&]() -> Result<Ok, std::string> {
586 auto options = *mInitialOptions;
587 if (StaticPrefs::webgl_disable_fail_if_major_performance_caveat()) {
588 options.failIfMajorPerformanceCaveat = false;
591 if (options.failIfMajorPerformanceCaveat) {
592 const auto backend = GetCompositorBackendType();
593 bool isCompositorSlow = false;
594 isCompositorSlow |= (backend == layers::LayersBackend::LAYERS_BASIC);
595 isCompositorSlow |= (backend == layers::LayersBackend::LAYERS_WR &&
596 gfx::gfxVars::UseSoftwareWebRender());
598 if (isCompositorSlow) {
599 return Err(
600 "failIfMajorPerformanceCaveat: Compositor is not"
601 " hardware-accelerated.");
605 const bool resistFingerprinting = ShouldResistFingerprinting();
607 const auto& principal = GetCanvas()->NodePrincipal();
608 const auto principalKey = principal->GetHashValue();
609 const auto initDesc = webgl::InitContextDesc{
610 mIsWebGL2, resistFingerprinting, requestedSize, options, principalKey};
612 // -
614 auto useOop = IsWebglOutOfProcessEnabled();
615 if (XRE_IsParentProcess()) {
616 useOop = false;
619 if (!useOop) {
620 notLost.inProcess =
621 HostWebGLContext::Create({this, nullptr}, initDesc, &notLost.info);
622 return Ok();
625 // -
627 ScopedGfxFeatureReporter reporter("IpcWebGL");
629 auto* const cbc = layers::CompositorBridgeChild::Get();
630 MOZ_ASSERT(cbc);
631 if (!cbc) {
632 return Err("!CompositorBridgeChild::Get()");
635 RefPtr<dom::WebGLChild> outOfProcess = new dom::WebGLChild(*this);
636 outOfProcess =
637 static_cast<dom::WebGLChild*>(cbc->SendPWebGLConstructor(outOfProcess));
638 if (!outOfProcess) {
639 return Err("SendPWebGLConstructor failed");
642 if (!outOfProcess->SendInitialize(initDesc, &notLost.info)) {
643 return Err("WebGL actor Initialize failed");
646 notLost.outOfProcess = outOfProcess;
647 reporter.SetSuccessful();
648 return Ok();
649 }();
650 if (!res.isOk()) {
651 auto str = res.unwrapErr();
652 if (StartsWith(str, "failIfMajorPerformanceCaveat")) {
653 str +=
654 " (about:config override available:"
655 " webgl.disable-fail-if-major-performance-caveat)";
657 notLost.info.error = str;
659 if (!notLost.info.error.empty()) {
660 ThrowEvent_WebGLContextCreationError(notLost.info.error);
661 return false;
663 mNotLost = pNotLost;
664 MarkCanvasDirty();
666 // Init state
667 const auto& limits = Limits();
668 auto& state = State();
669 state.mDefaultTfo = new WebGLTransformFeedbackJS(*this);
670 state.mDefaultVao = new WebGLVertexArrayJS(*this);
672 state.mBoundTfo = state.mDefaultTfo;
673 state.mBoundVao = state.mDefaultVao;
675 (void)state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER];
677 state.mTexUnits.resize(limits.maxTexUnits);
678 state.mBoundUbos.resize(limits.maxUniformBufferBindings);
681 webgl::TypedQuad initVal;
682 const float fData[4] = {0, 0, 0, 1};
683 memcpy(initVal.data, fData, sizeof(initVal.data));
684 state.mGenericVertexAttribs.resize(limits.maxVertexAttribs, initVal);
687 const auto& size = DrawingBufferSize();
688 state.mViewport = {0, 0, static_cast<int32_t>(size.x),
689 static_cast<int32_t>(size.y)};
690 state.mScissor = state.mViewport;
692 if (mIsWebGL2) {
693 // Insert keys to enable slots:
694 (void)state.mBoundBufferByTarget[LOCAL_GL_COPY_READ_BUFFER];
695 (void)state.mBoundBufferByTarget[LOCAL_GL_COPY_WRITE_BUFFER];
696 (void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_PACK_BUFFER];
697 (void)state.mBoundBufferByTarget[LOCAL_GL_PIXEL_UNPACK_BUFFER];
698 (void)state.mBoundBufferByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER];
699 (void)state.mBoundBufferByTarget[LOCAL_GL_UNIFORM_BUFFER];
701 (void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED];
702 //(void)state.mCurrentQueryByTarget[LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE];
703 //// Same slot as ANY_SAMPLES_PASSED.
704 (void)state
705 .mCurrentQueryByTarget[LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN];
708 return true;
711 // -------
713 uvec2 ClientWebGLContext::DrawingBufferSize() {
714 if (IsContextLost()) return {};
715 const auto notLost =
716 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
717 auto& state = State();
718 auto& size = state.mDrawingBufferSize;
720 if (!size) {
721 const auto& inProcess = mNotLost->inProcess;
722 if (inProcess) {
723 size = Some(inProcess->DrawingBufferSize());
724 } else {
725 const auto& child = mNotLost->outOfProcess;
726 child->FlushPendingCmds();
727 uvec2 actual = {};
728 if (!child->SendDrawingBufferSize(&actual)) return {};
729 size = Some(actual);
733 return *size;
736 void ClientWebGLContext::OnMemoryPressure() {
737 if (IsContextLost()) return;
739 const auto& inProcess = mNotLost->inProcess;
740 if (inProcess) {
741 return inProcess->OnMemoryPressure();
743 const auto& child = mNotLost->outOfProcess;
744 (void)child->SendOnMemoryPressure();
747 NS_IMETHODIMP
748 ClientWebGLContext::SetContextOptions(JSContext* cx,
749 JS::Handle<JS::Value> options,
750 ErrorResult& aRvForDictionaryInit) {
751 if (mInitialOptions && options.isNullOrUndefined()) return NS_OK;
753 dom::WebGLContextAttributes attributes;
754 if (!attributes.Init(cx, options)) {
755 aRvForDictionaryInit.Throw(NS_ERROR_UNEXPECTED);
756 return NS_ERROR_UNEXPECTED;
759 WebGLContextOptions newOpts;
761 newOpts.stencil = attributes.mStencil;
762 newOpts.depth = attributes.mDepth;
763 newOpts.premultipliedAlpha = attributes.mPremultipliedAlpha;
764 newOpts.preserveDrawingBuffer = attributes.mPreserveDrawingBuffer;
765 newOpts.failIfMajorPerformanceCaveat =
766 attributes.mFailIfMajorPerformanceCaveat;
767 newOpts.xrCompatible = attributes.mXrCompatible;
768 newOpts.powerPreference = attributes.mPowerPreference;
769 newOpts.enableDebugRendererInfo =
770 StaticPrefs::webgl_enable_debug_renderer_info();
771 MOZ_ASSERT(mCanvasElement || mOffscreenCanvas);
772 newOpts.shouldResistFingerprinting =
773 mCanvasElement ?
774 // If we're constructed from a canvas element
775 nsContentUtils::ShouldResistFingerprinting(GetOwnerDoc())
777 // If we're constructed from an offscreen canvas
778 nsContentUtils::ShouldResistFingerprinting(
779 mOffscreenCanvas->GetOwnerGlobal()->PrincipalOrNull());
781 if (attributes.mAlpha.WasPassed()) {
782 newOpts.alpha = attributes.mAlpha.Value();
784 if (attributes.mAntialias.WasPassed()) {
785 newOpts.antialias = attributes.mAntialias.Value();
788 // Don't do antialiasing if we've disabled MSAA.
789 if (!StaticPrefs::webgl_msaa_samples()) {
790 newOpts.antialias = false;
793 if (mInitialOptions && *mInitialOptions != newOpts) {
794 // Err if the options asked for aren't the same as what they were
795 // originally.
796 return NS_ERROR_FAILURE;
799 mXRCompatible = attributes.mXrCompatible;
801 mInitialOptions.emplace(newOpts);
802 return NS_OK;
805 void ClientWebGLContext::DidRefresh() { Run<RPROC(DidRefresh)>(); }
807 already_AddRefed<gfx::SourceSurface> ClientWebGLContext::GetSurfaceSnapshot(
808 gfxAlphaType* const out_alphaType) {
809 const FuncScope funcScope(*this, "<GetSurfaceSnapshot>");
810 if (IsContextLost()) return nullptr;
811 const auto notLost =
812 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
814 auto ret = BackBufferSnapshot();
815 if (!ret) return nullptr;
817 // -
819 const auto& options = mNotLost->info.options;
821 auto srcAlphaType = gfxAlphaType::Opaque;
822 if (options.alpha) {
823 if (options.premultipliedAlpha) {
824 srcAlphaType = gfxAlphaType::Premult;
825 } else {
826 srcAlphaType = gfxAlphaType::NonPremult;
830 if (out_alphaType) {
831 *out_alphaType = srcAlphaType;
832 } else {
833 // Expects Opaque or Premult
834 if (srcAlphaType == gfxAlphaType::NonPremult) {
835 const auto nonPremultSurf = ret;
836 const auto& size = nonPremultSurf->GetSize();
837 const auto format = nonPremultSurf->GetFormat();
838 ret = gfx::Factory::CreateDataSourceSurface(size, format, /*zero=*/false);
839 gfxUtils::PremultiplyDataSurface(nonPremultSurf, ret);
843 return ret.forget();
846 RefPtr<gfx::SourceSurface> ClientWebGLContext::GetFrontBufferSnapshot(
847 const bool requireAlphaPremult) {
848 const FuncScope funcScope(*this, "<GetSurfaceSnapshot>");
849 if (IsContextLost()) return nullptr;
850 const auto notLost =
851 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
853 const auto& options = mNotLost->info.options;
855 const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
856 : gfx::SurfaceFormat::B8G8R8X8;
858 const auto fnNewSurf = [&](const uvec2 size) {
859 const auto stride = size.x * 4;
860 return RefPtr<gfx::DataSourceSurface>(
861 gfx::Factory::CreateDataSourceSurfaceWithStride({size.x, size.y},
862 surfFormat, stride,
863 /*zero=*/true));
866 auto snapshot = [&]() -> RefPtr<gfx::DataSourceSurface> {
867 const auto& inProcess = mNotLost->inProcess;
868 if (inProcess) {
869 const auto surfSize = inProcess->GetFrontBufferSize();
870 const auto stride = surfSize.x * 4;
871 const auto byteSize = stride * surfSize.y;
872 const auto surf = fnNewSurf(surfSize);
873 if (!surf) return nullptr;
875 const gfx::DataSourceSurface::ScopedMap map(
876 surf, gfx::DataSourceSurface::READ_WRITE);
877 if (!map.IsMapped()) {
878 MOZ_ASSERT(false);
879 return nullptr;
881 MOZ_RELEASE_ASSERT(map.GetStride() == static_cast<int64_t>(stride));
882 auto range = Range<uint8_t>{map.GetData(), byteSize};
883 if (!inProcess->FrontBufferSnapshotInto(range)) return nullptr;
885 return surf;
887 const auto& child = mNotLost->outOfProcess;
888 child->FlushPendingCmds();
889 webgl::FrontBufferSnapshotIpc res;
890 if (!child->SendGetFrontBufferSnapshot(&res)) {
891 res = {};
893 const auto& surfSize = res.surfSize;
894 const webgl::RaiiShmem shmem{child, res.shmem};
895 const auto& shmemBytes = shmem.ByteRange();
896 if (!surfSize.x) return nullptr; // Zero means failure.
898 const auto stride = surfSize.x * 4;
899 const auto byteSize = stride * surfSize.y;
901 const auto surf = fnNewSurf(surfSize);
902 if (!surf) return nullptr;
905 const gfx::DataSourceSurface::ScopedMap map(
906 surf, gfx::DataSourceSurface::READ_WRITE);
907 if (!map.IsMapped()) {
908 MOZ_ASSERT(false);
909 return nullptr;
911 MOZ_RELEASE_ASSERT(map.GetStride() == static_cast<int64_t>(stride));
912 MOZ_RELEASE_ASSERT(shmemBytes.length() == byteSize);
913 memcpy(map.GetData(), shmemBytes.begin().get(), byteSize);
915 return surf;
916 }();
917 if (!snapshot) return nullptr;
919 if (requireAlphaPremult && options.alpha && !options.premultipliedAlpha) {
920 const auto nonPremultSurf = snapshot;
921 const auto& size = nonPremultSurf->GetSize();
922 const auto format = nonPremultSurf->GetFormat();
923 snapshot =
924 gfx::Factory::CreateDataSourceSurface(size, format, /*zero=*/false);
925 if (!snapshot) {
926 gfxCriticalNote << "CreateDataSourceSurface failed for size " << size;
928 gfxUtils::PremultiplyDataSurface(nonPremultSurf, snapshot);
931 return snapshot;
934 RefPtr<gfx::DataSourceSurface> ClientWebGLContext::BackBufferSnapshot() {
935 if (IsContextLost()) return nullptr;
936 const auto notLost =
937 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
939 const auto& options = mNotLost->info.options;
940 const auto& state = State();
942 const auto drawFbWas = state.mBoundDrawFb;
943 const auto readFbWas = state.mBoundReadFb;
944 const auto pboWas =
945 Find(state.mBoundBufferByTarget, LOCAL_GL_PIXEL_PACK_BUFFER);
947 const auto size = DrawingBufferSize();
949 // -
951 BindFramebuffer(LOCAL_GL_FRAMEBUFFER, nullptr);
952 if (pboWas) {
953 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, nullptr);
956 auto reset = MakeScopeExit([&] {
957 if (drawFbWas == readFbWas) {
958 BindFramebuffer(LOCAL_GL_FRAMEBUFFER, drawFbWas);
959 } else {
960 BindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, drawFbWas);
961 BindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, readFbWas);
963 if (pboWas) {
964 BindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, pboWas);
968 const auto surfFormat = options.alpha ? gfx::SurfaceFormat::B8G8R8A8
969 : gfx::SurfaceFormat::B8G8R8X8;
970 const auto stride = size.x * 4;
971 RefPtr<gfx::DataSourceSurface> surf =
972 gfx::Factory::CreateDataSourceSurfaceWithStride(
973 {size.x, size.y}, surfFormat, stride, /*zero=*/true);
974 MOZ_ASSERT(surf);
975 if (NS_WARN_IF(!surf)) return nullptr;
978 const gfx::DataSourceSurface::ScopedMap map(
979 surf, gfx::DataSourceSurface::READ_WRITE);
980 if (!map.IsMapped()) {
981 MOZ_ASSERT(false);
982 return nullptr;
984 MOZ_ASSERT(static_cast<uint32_t>(map.GetStride()) == stride);
986 const auto desc = webgl::ReadPixelsDesc{{0, 0}, size};
987 const auto range = Range<uint8_t>(map.GetData(), stride * size.y);
988 DoReadPixels(desc, range);
990 const auto begin = range.begin().get();
992 std::vector<uint8_t> temp;
993 temp.resize(stride);
994 for (const auto i : IntegerRange(size.y / 2)) {
995 const auto top = begin + stride * i;
996 const auto bottom = begin + stride * (size.y - 1 - i);
997 memcpy(temp.data(), top, stride);
998 memcpy(top, bottom, stride);
999 gfxUtils::ConvertBGRAtoRGBA(top, stride);
1001 memcpy(bottom, temp.data(), stride);
1002 gfxUtils::ConvertBGRAtoRGBA(bottom, stride);
1005 if (size.y % 2) {
1006 const auto middle = begin + stride * (size.y / 2);
1007 gfxUtils::ConvertBGRAtoRGBA(middle, stride);
1011 return surf;
1014 UniquePtr<uint8_t[]> ClientWebGLContext::GetImageBuffer(int32_t* out_format) {
1015 *out_format = 0;
1017 // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
1018 gfxAlphaType any;
1019 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
1020 if (!snapshot) return nullptr;
1022 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
1024 const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;
1025 return gfxUtils::GetImageBuffer(dataSurface, premultAlpha, out_format);
1028 NS_IMETHODIMP
1029 ClientWebGLContext::GetInputStream(const char* mimeType,
1030 const nsAString& encoderOptions,
1031 nsIInputStream** out_stream) {
1032 // Use GetSurfaceSnapshot() to make sure that appropriate y-flip gets applied
1033 gfxAlphaType any;
1034 RefPtr<gfx::SourceSurface> snapshot = GetSurfaceSnapshot(&any);
1035 if (!snapshot) return NS_ERROR_FAILURE;
1037 RefPtr<gfx::DataSourceSurface> dataSurface = snapshot->GetDataSurface();
1038 const auto& premultAlpha = mNotLost->info.options.premultipliedAlpha;
1039 return gfxUtils::GetInputStream(dataSurface, premultAlpha, mimeType,
1040 encoderOptions, out_stream);
1043 // ------------------------- Client WebGL Objects -------------------------
1044 // ------------------------- Create/Destroy/Is -------------------------
1046 template <typename T>
1047 static already_AddRefed<T> AsAddRefed(T* ptr) {
1048 RefPtr<T> rp = ptr;
1049 return rp.forget();
1052 template <typename T>
1053 static RefPtr<T> AsRefPtr(T* ptr) {
1054 return {ptr};
1057 already_AddRefed<WebGLBufferJS> ClientWebGLContext::CreateBuffer() const {
1058 const FuncScope funcScope(*this, "createBuffer");
1059 if (IsContextLost()) return nullptr;
1061 auto ret = AsRefPtr(new WebGLBufferJS(*this));
1062 Run<RPROC(CreateBuffer)>(ret->mId);
1063 return ret.forget();
1066 already_AddRefed<WebGLFramebufferJS> ClientWebGLContext::CreateFramebuffer()
1067 const {
1068 const FuncScope funcScope(*this, "createFramebuffer");
1069 if (IsContextLost()) return nullptr;
1071 auto ret = AsRefPtr(new WebGLFramebufferJS(*this));
1072 Run<RPROC(CreateFramebuffer)>(ret->mId);
1073 return ret.forget();
1076 already_AddRefed<WebGLFramebufferJS>
1077 ClientWebGLContext::CreateOpaqueFramebuffer(
1078 const webgl::OpaqueFramebufferOptions& options) const {
1079 const FuncScope funcScope(*this, "createOpaqueFramebuffer");
1080 if (IsContextLost()) return nullptr;
1082 auto ret = AsRefPtr(new WebGLFramebufferJS(*this, true));
1084 const auto& inProcess = mNotLost->inProcess;
1085 if (inProcess) {
1086 if (!inProcess->CreateOpaqueFramebuffer(ret->mId, options)) {
1087 ret = nullptr;
1089 return ret.forget();
1091 const auto& child = mNotLost->outOfProcess;
1092 child->FlushPendingCmds();
1093 bool ok = false;
1094 if (!child->SendCreateOpaqueFramebuffer(ret->mId, options, &ok))
1095 return nullptr;
1096 if (!ok) return nullptr;
1097 return ret.forget();
1100 already_AddRefed<WebGLProgramJS> ClientWebGLContext::CreateProgram() const {
1101 const FuncScope funcScope(*this, "createProgram");
1102 if (IsContextLost()) return nullptr;
1104 auto ret = AsRefPtr(new WebGLProgramJS(*this));
1105 Run<RPROC(CreateProgram)>(ret->mId);
1106 return ret.forget();
1109 already_AddRefed<WebGLQueryJS> ClientWebGLContext::CreateQuery() const {
1110 const FuncScope funcScope(*this, "createQuery");
1111 if (IsContextLost()) return nullptr;
1113 auto ret = AsRefPtr(new WebGLQueryJS(*this));
1114 Run<RPROC(CreateQuery)>(ret->mId);
1115 return ret.forget();
1118 already_AddRefed<WebGLRenderbufferJS> ClientWebGLContext::CreateRenderbuffer()
1119 const {
1120 const FuncScope funcScope(*this, "createRenderbuffer");
1121 if (IsContextLost()) return nullptr;
1123 auto ret = AsRefPtr(new WebGLRenderbufferJS(*this));
1124 Run<RPROC(CreateRenderbuffer)>(ret->mId);
1125 return ret.forget();
1128 already_AddRefed<WebGLSamplerJS> ClientWebGLContext::CreateSampler() const {
1129 const FuncScope funcScope(*this, "createSampler");
1130 if (IsContextLost()) return nullptr;
1132 auto ret = AsRefPtr(new WebGLSamplerJS(*this));
1133 Run<RPROC(CreateSampler)>(ret->mId);
1134 return ret.forget();
1137 already_AddRefed<WebGLShaderJS> ClientWebGLContext::CreateShader(
1138 const GLenum type) const {
1139 const FuncScope funcScope(*this, "createShader");
1140 if (IsContextLost()) return nullptr;
1142 switch (type) {
1143 case LOCAL_GL_VERTEX_SHADER:
1144 case LOCAL_GL_FRAGMENT_SHADER:
1145 break;
1146 default:
1147 EnqueueError_ArgEnum("type", type);
1148 return nullptr;
1151 auto ret = AsRefPtr(new WebGLShaderJS(*this, type));
1152 Run<RPROC(CreateShader)>(ret->mId, ret->mType);
1153 return ret.forget();
1156 already_AddRefed<WebGLSyncJS> ClientWebGLContext::FenceSync(
1157 const GLenum condition, const GLbitfield flags) const {
1158 const FuncScope funcScope(*this, "fenceSync");
1159 if (IsContextLost()) return nullptr;
1161 if (condition != LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE) {
1162 EnqueueError_ArgEnum("condition", condition);
1163 return nullptr;
1166 if (flags) {
1167 EnqueueError(LOCAL_GL_INVALID_VALUE, "`flags` must be 0.");
1168 return nullptr;
1171 auto ret = AsRefPtr(new WebGLSyncJS(*this));
1172 Run<RPROC(CreateSync)>(ret->mId);
1174 auto& availRunnable = EnsureAvailabilityRunnable();
1175 availRunnable.mSyncs.push_back(ret.get());
1176 ret->mCanBeAvailable = false;
1178 return ret.forget();
1181 already_AddRefed<WebGLTextureJS> ClientWebGLContext::CreateTexture() const {
1182 const FuncScope funcScope(*this, "createTexture");
1183 if (IsContextLost()) return nullptr;
1185 auto ret = AsRefPtr(new WebGLTextureJS(*this));
1186 Run<RPROC(CreateTexture)>(ret->mId);
1187 return ret.forget();
1190 already_AddRefed<WebGLTransformFeedbackJS>
1191 ClientWebGLContext::CreateTransformFeedback() const {
1192 const FuncScope funcScope(*this, "createTransformFeedback");
1193 if (IsContextLost()) return nullptr;
1195 auto ret = AsRefPtr(new WebGLTransformFeedbackJS(*this));
1196 Run<RPROC(CreateTransformFeedback)>(ret->mId);
1197 return ret.forget();
1200 already_AddRefed<WebGLVertexArrayJS> ClientWebGLContext::CreateVertexArray()
1201 const {
1202 const FuncScope funcScope(*this, "createVertexArray");
1203 if (IsContextLost()) return nullptr;
1205 auto ret = AsRefPtr(new WebGLVertexArrayJS(*this));
1206 Run<RPROC(CreateVertexArray)>(ret->mId);
1207 return ret.forget();
1210 // -
1212 static bool ValidateOrSkipForDelete(const ClientWebGLContext& context,
1213 const webgl::ObjectJS* const obj) {
1214 if (!obj) return false;
1215 if (!obj->ValidateForContext(context, "obj")) return false;
1216 if (obj->IsDeleted()) return false;
1217 return true;
1220 void ClientWebGLContext::DeleteBuffer(WebGLBufferJS* const obj) {
1221 const FuncScope funcScope(*this, "deleteBuffer");
1222 if (IsContextLost()) return;
1223 if (!ValidateOrSkipForDelete(*this, obj)) return;
1224 auto& state = State();
1226 // Unbind from all bind points and bound containers
1228 // UBOs
1229 for (const auto i : IntegerRange(state.mBoundUbos.size())) {
1230 if (state.mBoundUbos[i] == obj) {
1231 BindBufferBase(LOCAL_GL_UNIFORM_BUFFER, i, nullptr);
1235 // TFO only if not active
1236 if (!state.mBoundTfo->mActiveOrPaused) {
1237 const auto& buffers = state.mBoundTfo->mAttribBuffers;
1238 for (const auto i : IntegerRange(buffers.size())) {
1239 if (buffers[i] == obj) {
1240 BindBufferBase(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER, i, nullptr);
1245 // Generic/global bind points
1246 for (const auto& pair : state.mBoundBufferByTarget) {
1247 if (pair.second == obj) {
1248 BindBuffer(pair.first, nullptr);
1252 // VAO attachments
1253 if (state.mBoundVao->mIndexBuffer == obj) {
1254 BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, nullptr);
1257 const auto& vaoBuffers = state.mBoundVao->mAttribBuffers;
1258 Maybe<WebGLBufferJS*> toRestore;
1259 for (const auto i : IntegerRange(vaoBuffers.size())) {
1260 if (vaoBuffers[i] == obj) {
1261 if (!toRestore) {
1262 toRestore =
1263 Some(state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER].get());
1264 if (*toRestore) {
1265 BindBuffer(LOCAL_GL_ARRAY_BUFFER, nullptr);
1268 VertexAttribPointer(i, 4, LOCAL_GL_FLOAT, false, 0, 0);
1271 if (toRestore && *toRestore) {
1272 BindBuffer(LOCAL_GL_ARRAY_BUFFER, *toRestore);
1275 // -
1277 obj->mDeleteRequested = true;
1278 Run<RPROC(DeleteBuffer)>(obj->mId);
1281 void ClientWebGLContext::DeleteFramebuffer(WebGLFramebufferJS* const obj,
1282 bool canDeleteOpaque) {
1283 const FuncScope funcScope(*this, "deleteFramebuffer");
1284 if (IsContextLost()) return;
1285 if (!ValidateOrSkipForDelete(*this, obj)) return;
1286 if (!canDeleteOpaque && obj->mOpaque) {
1287 EnqueueError(
1288 LOCAL_GL_INVALID_OPERATION,
1289 "An opaque framebuffer's attachments cannot be inspected or changed.");
1290 return;
1292 const auto& state = State();
1294 // Unbind
1295 const auto fnDetach = [&](const GLenum target,
1296 const WebGLFramebufferJS* const fb) {
1297 if (obj == fb) {
1298 BindFramebuffer(target, nullptr);
1301 if (state.mBoundDrawFb == state.mBoundReadFb) {
1302 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1303 } else {
1304 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1305 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1308 obj->mDeleteRequested = true;
1309 Run<RPROC(DeleteFramebuffer)>(obj->mId);
1312 void ClientWebGLContext::DeleteProgram(WebGLProgramJS* const obj) const {
1313 const FuncScope funcScope(*this, "deleteProgram");
1314 if (IsContextLost()) return;
1315 if (!ValidateOrSkipForDelete(*this, obj)) return;
1317 // Don't unbind
1319 obj->mKeepAlive = nullptr;
1322 webgl::ProgramKeepAlive::~ProgramKeepAlive() {
1323 if (!mParent) return;
1324 const auto& context = mParent->Context();
1325 if (!context) return;
1326 context->DoDeleteProgram(*mParent);
1329 void ClientWebGLContext::DoDeleteProgram(WebGLProgramJS& obj) const {
1330 obj.mNextLink_Shaders = {};
1331 Run<RPROC(DeleteProgram)>(obj.mId);
1334 static GLenum QuerySlotTarget(const GLenum specificTarget);
1336 void ClientWebGLContext::DeleteQuery(WebGLQueryJS* const obj) {
1337 const FuncScope funcScope(*this, "deleteQuery");
1338 if (IsContextLost()) return;
1339 if (!ValidateOrSkipForDelete(*this, obj)) return;
1340 const auto& state = State();
1342 // Unbind if current
1344 if (obj->mTarget) {
1345 const auto slotTarget = QuerySlotTarget(obj->mTarget);
1346 const auto& curForTarget =
1347 *MaybeFind(state.mCurrentQueryByTarget, slotTarget);
1349 if (curForTarget == obj) {
1350 EndQuery(obj->mTarget);
1354 obj->mDeleteRequested = true;
1355 Run<RPROC(DeleteQuery)>(obj->mId);
1358 void ClientWebGLContext::DeleteRenderbuffer(WebGLRenderbufferJS* const obj) {
1359 const FuncScope funcScope(*this, "deleteRenderbuffer");
1360 if (IsContextLost()) return;
1361 if (!ValidateOrSkipForDelete(*this, obj)) return;
1362 const auto& state = State();
1364 // Unbind
1365 if (state.mBoundRb == obj) {
1366 BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nullptr);
1369 // Unbind from bound FBs
1370 const auto fnDetach = [&](const GLenum target,
1371 const WebGLFramebufferJS* const fb) {
1372 if (!fb) return;
1373 for (const auto& pair : fb->mAttachments) {
1374 if (pair.second.rb == obj) {
1375 FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
1376 nullptr);
1380 if (state.mBoundDrawFb == state.mBoundReadFb) {
1381 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1382 } else {
1383 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1384 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1387 obj->mDeleteRequested = true;
1388 Run<RPROC(DeleteRenderbuffer)>(obj->mId);
1391 void ClientWebGLContext::DeleteSampler(WebGLSamplerJS* const obj) {
1392 const FuncScope funcScope(*this, "deleteSampler");
1393 if (IsContextLost()) return;
1394 if (!ValidateOrSkipForDelete(*this, obj)) return;
1395 const auto& state = State();
1397 // Unbind
1398 for (const auto i : IntegerRange(state.mTexUnits.size())) {
1399 if (state.mTexUnits[i].sampler == obj) {
1400 BindSampler(i, nullptr);
1404 obj->mDeleteRequested = true;
1405 Run<RPROC(DeleteSampler)>(obj->mId);
1408 void ClientWebGLContext::DeleteShader(WebGLShaderJS* const obj) const {
1409 const FuncScope funcScope(*this, "deleteShader");
1410 if (IsContextLost()) return;
1411 if (!ValidateOrSkipForDelete(*this, obj)) return;
1413 // Don't unbind
1415 obj->mKeepAlive = nullptr;
1418 webgl::ShaderKeepAlive::~ShaderKeepAlive() {
1419 if (!mParent) return;
1420 const auto& context = mParent->Context();
1421 if (!context) return;
1422 context->DoDeleteShader(*mParent);
1425 void ClientWebGLContext::DoDeleteShader(const WebGLShaderJS& obj) const {
1426 Run<RPROC(DeleteShader)>(obj.mId);
1429 void ClientWebGLContext::DeleteSync(WebGLSyncJS* const obj) const {
1430 const FuncScope funcScope(*this, "deleteSync");
1431 if (IsContextLost()) return;
1432 if (!ValidateOrSkipForDelete(*this, obj)) return;
1434 // Nothing to unbind
1436 obj->mDeleteRequested = true;
1437 Run<RPROC(DeleteSync)>(obj->mId);
1440 void ClientWebGLContext::DeleteTexture(WebGLTextureJS* const obj) {
1441 const FuncScope funcScope(*this, "deleteTexture");
1442 if (IsContextLost()) return;
1443 if (!ValidateOrSkipForDelete(*this, obj)) return;
1444 auto& state = State();
1446 // Unbind
1447 const auto& target = obj->mTarget;
1448 if (target) {
1449 // Unbind from tex units
1450 Maybe<uint32_t> restoreTexUnit;
1451 for (const auto i : IntegerRange(state.mTexUnits.size())) {
1452 if (state.mTexUnits[i].texByTarget[target] == obj) {
1453 if (!restoreTexUnit) {
1454 restoreTexUnit = Some(state.mActiveTexUnit);
1456 ActiveTexture(LOCAL_GL_TEXTURE0 + i);
1457 BindTexture(target, nullptr);
1460 if (restoreTexUnit) {
1461 ActiveTexture(LOCAL_GL_TEXTURE0 + *restoreTexUnit);
1464 // Unbind from bound FBs
1465 const auto fnDetach = [&](const GLenum target,
1466 const WebGLFramebufferJS* const fb) {
1467 if (!fb) return;
1468 for (const auto& pair : fb->mAttachments) {
1469 if (pair.second.tex == obj) {
1470 FramebufferRenderbuffer(target, pair.first, LOCAL_GL_RENDERBUFFER,
1471 nullptr);
1475 if (state.mBoundDrawFb == state.mBoundReadFb) {
1476 fnDetach(LOCAL_GL_FRAMEBUFFER, state.mBoundDrawFb.get());
1477 } else {
1478 fnDetach(LOCAL_GL_DRAW_FRAMEBUFFER, state.mBoundDrawFb.get());
1479 fnDetach(LOCAL_GL_READ_FRAMEBUFFER, state.mBoundReadFb.get());
1483 obj->mDeleteRequested = true;
1484 Run<RPROC(DeleteTexture)>(obj->mId);
1487 void ClientWebGLContext::DeleteTransformFeedback(
1488 WebGLTransformFeedbackJS* const obj) {
1489 const FuncScope funcScope(*this, "deleteTransformFeedback");
1490 if (IsContextLost()) return;
1491 if (!ValidateOrSkipForDelete(*this, obj)) return;
1492 const auto& state = State();
1494 if (obj->mActiveOrPaused) {
1495 EnqueueError(LOCAL_GL_INVALID_OPERATION,
1496 "Transform Feedback object still active or paused.");
1497 return;
1500 // Unbind
1501 if (state.mBoundTfo == obj) {
1502 BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
1505 obj->mDeleteRequested = true;
1506 Run<RPROC(DeleteTransformFeedback)>(obj->mId);
1509 void ClientWebGLContext::DeleteVertexArray(WebGLVertexArrayJS* const obj) {
1510 const FuncScope funcScope(*this, "deleteVertexArray");
1511 if (IsContextLost()) return;
1512 if (!ValidateOrSkipForDelete(*this, obj)) return;
1513 const auto& state = State();
1515 // Unbind
1516 if (state.mBoundVao == obj) {
1517 BindVertexArray(nullptr);
1520 obj->mDeleteRequested = true;
1521 Run<RPROC(DeleteVertexArray)>(obj->mId);
1524 // -
1526 bool ClientWebGLContext::IsBuffer(const WebGLBufferJS* const obj) const {
1527 const FuncScope funcScope(*this, "isBuffer");
1528 if (IsContextLost()) return false;
1530 return obj && obj->IsUsable(*this) &&
1531 obj->mKind != webgl::BufferKind::Undefined;
1534 bool ClientWebGLContext::IsFramebuffer(
1535 const WebGLFramebufferJS* const obj) const {
1536 const FuncScope funcScope(*this, "isFramebuffer");
1537 if (IsContextLost()) return false;
1539 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1542 bool ClientWebGLContext::IsProgram(const WebGLProgramJS* const obj) const {
1543 const FuncScope funcScope(*this, "isProgram");
1544 if (IsContextLost()) return false;
1546 return obj && obj->IsUsable(*this);
1549 bool ClientWebGLContext::IsQuery(const WebGLQueryJS* const obj) const {
1550 const FuncScope funcScope(*this, "isQuery");
1551 if (IsContextLost()) return false;
1553 return obj && obj->IsUsable(*this) && obj->mTarget;
1556 bool ClientWebGLContext::IsRenderbuffer(
1557 const WebGLRenderbufferJS* const obj) const {
1558 const FuncScope funcScope(*this, "isRenderbuffer");
1559 if (IsContextLost()) return false;
1561 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1564 bool ClientWebGLContext::IsSampler(const WebGLSamplerJS* const obj) const {
1565 const FuncScope funcScope(*this, "isSampler");
1566 if (IsContextLost()) return false;
1568 return obj && obj->IsUsable(*this);
1571 bool ClientWebGLContext::IsShader(const WebGLShaderJS* const obj) const {
1572 const FuncScope funcScope(*this, "isShader");
1573 if (IsContextLost()) return false;
1575 return obj && obj->IsUsable(*this);
1578 bool ClientWebGLContext::IsSync(const WebGLSyncJS* const obj) const {
1579 const FuncScope funcScope(*this, "isSync");
1580 if (IsContextLost()) return false;
1582 return obj && obj->IsUsable(*this);
1585 bool ClientWebGLContext::IsTexture(const WebGLTextureJS* const obj) const {
1586 const FuncScope funcScope(*this, "isTexture");
1587 if (IsContextLost()) return false;
1589 return obj && obj->IsUsable(*this) && obj->mTarget;
1592 bool ClientWebGLContext::IsTransformFeedback(
1593 const WebGLTransformFeedbackJS* const obj) const {
1594 const FuncScope funcScope(*this, "isTransformFeedback");
1595 if (IsContextLost()) return false;
1597 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1600 bool ClientWebGLContext::IsVertexArray(
1601 const WebGLVertexArrayJS* const obj) const {
1602 const FuncScope funcScope(*this, "isVertexArray");
1603 if (IsContextLost()) return false;
1605 return obj && obj->IsUsable(*this) && obj->mHasBeenBound;
1608 // ------------------------- GL State -------------------------
1610 void ClientWebGLContext::Disable(GLenum cap) const { Run<RPROC(Disable)>(cap); }
1612 void ClientWebGLContext::Enable(GLenum cap) const { Run<RPROC(Enable)>(cap); }
1614 bool ClientWebGLContext::IsEnabled(GLenum cap) const {
1615 const FuncScope funcScope(*this, "isEnabled");
1616 const auto notLost = mNotLost;
1617 if (IsContextLost()) return false;
1619 const auto& inProcess = notLost->inProcess;
1620 if (inProcess) {
1621 return inProcess->IsEnabled(cap);
1623 const auto& child = notLost->outOfProcess;
1624 child->FlushPendingCmds();
1625 bool ret = {};
1626 if (!child->SendIsEnabled(cap, &ret)) return false;
1627 return ret;
1630 void ClientWebGLContext::GetInternalformatParameter(
1631 JSContext* cx, GLenum target, GLenum internalformat, GLenum pname,
1632 JS::MutableHandle<JS::Value> retval, ErrorResult& rv) {
1633 const FuncScope funcScope(*this, "getInternalformatParameter");
1634 retval.set(JS::NullValue());
1635 const auto notLost =
1636 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
1637 if (IsContextLost()) return;
1639 const auto& inProcessContext = notLost->inProcess;
1640 Maybe<std::vector<int32_t>> maybe;
1641 if (inProcessContext) {
1642 maybe = inProcessContext->GetInternalformatParameter(target, internalformat,
1643 pname);
1644 } else {
1645 const auto& child = notLost->outOfProcess;
1646 child->FlushPendingCmds();
1647 if (!child->SendGetInternalformatParameter(target, internalformat, pname,
1648 &maybe)) {
1649 return;
1653 if (!maybe) {
1654 return;
1656 // zero-length array indicates out-of-memory
1657 JSObject* obj =
1658 dom::Int32Array::Create(cx, this, maybe->size(), maybe->data());
1659 if (!obj) {
1660 rv = NS_ERROR_OUT_OF_MEMORY;
1662 retval.setObjectOrNull(obj);
1665 static JS::Value StringValue(JSContext* cx, const std::string& str,
1666 ErrorResult& er) {
1667 JSString* jsStr = JS_NewStringCopyN(cx, str.data(), str.size());
1668 if (!jsStr) {
1669 er.Throw(NS_ERROR_OUT_OF_MEMORY);
1670 return JS::NullValue();
1673 return JS::StringValue(jsStr);
1676 template <typename T>
1677 bool ToJSValueOrNull(JSContext* const cx, const RefPtr<T>& ptr,
1678 JS::MutableHandle<JS::Value> retval) {
1679 if (!ptr) {
1680 retval.set(JS::NullValue());
1681 return true;
1683 return dom::ToJSValue(cx, ptr, retval);
1686 template <typename T, typename U, typename S>
1687 static JS::Value CreateAs(JSContext* cx, nsWrapperCache* creator, const S& src,
1688 ErrorResult& rv) {
1689 const auto obj =
1690 T::Create(cx, creator, src.size(), reinterpret_cast<U>(src.data()));
1691 if (!obj) {
1692 rv = NS_ERROR_OUT_OF_MEMORY;
1694 return JS::ObjectOrNullValue(obj);
1697 template <typename T, typename S>
1698 static JS::Value Create(JSContext* cx, nsWrapperCache* creator, const S& src,
1699 ErrorResult& rv) {
1700 return CreateAs<T, decltype(&src[0]), S>(cx, creator, src, rv);
1703 Maybe<double> ClientWebGLContext::GetNumber(const GLenum pname) {
1704 MOZ_ASSERT(!IsContextLost());
1706 const auto& inProcess = mNotLost->inProcess;
1707 if (inProcess) {
1708 return inProcess->GetNumber(pname);
1711 const auto& child = mNotLost->outOfProcess;
1712 child->FlushPendingCmds();
1714 Maybe<double> ret;
1715 if (!child->SendGetNumber(pname, &ret)) {
1716 ret.reset();
1718 return ret;
1721 Maybe<std::string> ClientWebGLContext::GetString(const GLenum pname) {
1722 MOZ_ASSERT(!IsContextLost());
1724 const auto& inProcess = mNotLost->inProcess;
1725 if (inProcess) {
1726 return inProcess->GetString(pname);
1729 const auto& child = mNotLost->outOfProcess;
1730 child->FlushPendingCmds();
1732 Maybe<std::string> ret;
1733 if (!child->SendGetString(pname, &ret)) {
1734 ret.reset();
1736 return ret;
1739 void ClientWebGLContext::GetParameter(JSContext* cx, GLenum pname,
1740 JS::MutableHandle<JS::Value> retval,
1741 ErrorResult& rv, const bool debug) {
1742 retval.set(JS::NullValue());
1743 const FuncScope funcScope(*this, "getParameter");
1744 if (IsContextLost()) return;
1745 const auto& limits = Limits();
1746 const auto& state = State();
1748 // -
1750 const auto fnSetRetval_Buffer = [&](const GLenum target) {
1751 const auto buffer = *MaybeFind(state.mBoundBufferByTarget, target);
1752 (void)ToJSValueOrNull(cx, buffer, retval);
1754 const auto fnSetRetval_Tex = [&](const GLenum texTarget) {
1755 const auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
1756 const auto tex = Find(texUnit.texByTarget, texTarget, nullptr);
1757 (void)ToJSValueOrNull(cx, tex, retval);
1760 switch (pname) {
1761 case LOCAL_GL_ARRAY_BUFFER_BINDING:
1762 fnSetRetval_Buffer(LOCAL_GL_ARRAY_BUFFER);
1763 return;
1765 case LOCAL_GL_CURRENT_PROGRAM:
1766 (void)ToJSValueOrNull(cx, state.mCurrentProgram, retval);
1767 return;
1769 case LOCAL_GL_ELEMENT_ARRAY_BUFFER_BINDING:
1770 (void)ToJSValueOrNull(cx, state.mBoundVao->mIndexBuffer, retval);
1771 return;
1773 case LOCAL_GL_FRAMEBUFFER_BINDING:
1774 (void)ToJSValueOrNull(cx, state.mBoundDrawFb, retval);
1775 return;
1777 case LOCAL_GL_RENDERBUFFER_BINDING:
1778 (void)ToJSValueOrNull(cx, state.mBoundRb, retval);
1779 return;
1781 case LOCAL_GL_TEXTURE_BINDING_2D:
1782 fnSetRetval_Tex(LOCAL_GL_TEXTURE_2D);
1783 return;
1785 case LOCAL_GL_TEXTURE_BINDING_CUBE_MAP:
1786 fnSetRetval_Tex(LOCAL_GL_TEXTURE_CUBE_MAP);
1787 return;
1789 case LOCAL_GL_VERTEX_ARRAY_BINDING: {
1790 if (!mIsWebGL2 &&
1791 !IsExtensionEnabled(WebGLExtensionID::OES_vertex_array_object))
1792 break;
1794 auto ret = state.mBoundVao;
1795 if (ret == state.mDefaultVao) {
1796 ret = nullptr;
1798 (void)ToJSValueOrNull(cx, ret, retval);
1799 return;
1802 case LOCAL_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS:
1803 retval.set(JS::NumberValue(limits.maxTexUnits));
1804 return;
1805 case LOCAL_GL_MAX_TEXTURE_SIZE:
1806 retval.set(JS::NumberValue(limits.maxTex2dSize));
1807 return;
1808 case LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE:
1809 retval.set(JS::NumberValue(limits.maxTexCubeSize));
1810 return;
1811 case LOCAL_GL_MAX_VERTEX_ATTRIBS:
1812 retval.set(JS::NumberValue(limits.maxVertexAttribs));
1813 return;
1815 case LOCAL_GL_MAX_VIEWS_OVR:
1816 if (IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) {
1817 retval.set(JS::NumberValue(limits.maxMultiviewLayers));
1818 return;
1820 break;
1822 case LOCAL_GL_PACK_ALIGNMENT:
1823 retval.set(JS::NumberValue(state.mPixelPackState.alignment));
1824 return;
1825 case LOCAL_GL_UNPACK_ALIGNMENT:
1826 retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackAlignment));
1827 return;
1829 case dom::WebGLRenderingContext_Binding::UNPACK_FLIP_Y_WEBGL:
1830 retval.set(JS::BooleanValue(state.mPixelUnpackState.mFlipY));
1831 return;
1832 case dom::WebGLRenderingContext_Binding::UNPACK_PREMULTIPLY_ALPHA_WEBGL:
1833 retval.set(JS::BooleanValue(state.mPixelUnpackState.mPremultiplyAlpha));
1834 return;
1835 case dom::WebGLRenderingContext_Binding::UNPACK_COLORSPACE_CONVERSION_WEBGL:
1836 retval.set(
1837 JS::NumberValue(state.mPixelUnpackState.mColorspaceConversion));
1838 return;
1840 // -
1841 // Array returns
1843 // 2 floats
1844 case LOCAL_GL_DEPTH_RANGE:
1845 retval.set(Create<dom::Float32Array>(cx, this, state.mDepthRange, rv));
1846 return;
1848 case LOCAL_GL_ALIASED_POINT_SIZE_RANGE:
1849 retval.set(
1850 Create<dom::Float32Array>(cx, this, limits.pointSizeRange, rv));
1851 return;
1853 case LOCAL_GL_ALIASED_LINE_WIDTH_RANGE:
1854 retval.set(
1855 Create<dom::Float32Array>(cx, this, limits.lineWidthRange, rv));
1856 return;
1858 // 4 floats
1859 case LOCAL_GL_COLOR_CLEAR_VALUE:
1860 retval.set(Create<dom::Float32Array>(cx, this, state.mClearColor, rv));
1861 return;
1863 case LOCAL_GL_BLEND_COLOR:
1864 retval.set(Create<dom::Float32Array>(cx, this, state.mBlendColor, rv));
1865 return;
1867 // 2 ints
1868 case LOCAL_GL_MAX_VIEWPORT_DIMS:
1869 retval.set(CreateAs<dom::Int32Array, const int32_t*>(
1870 cx, this, limits.maxViewportDims, rv));
1871 return;
1873 // 4 ints
1874 case LOCAL_GL_SCISSOR_BOX:
1875 retval.set(Create<dom::Int32Array>(cx, this, state.mScissor, rv));
1876 return;
1878 case LOCAL_GL_VIEWPORT:
1879 retval.set(Create<dom::Int32Array>(cx, this, state.mViewport, rv));
1880 return;
1882 // 4 bools
1883 case LOCAL_GL_COLOR_WRITEMASK: {
1884 JS::Rooted<JS::Value> arr(cx);
1885 const auto& src = state.mColorWriteMask;
1886 if (!dom::ToJSValue(cx, src.data(), src.size(), &arr)) {
1887 rv = NS_ERROR_OUT_OF_MEMORY;
1889 retval.set(arr);
1890 return;
1893 // any
1894 case LOCAL_GL_COMPRESSED_TEXTURE_FORMATS:
1895 retval.set(Create<dom::Uint32Array>(cx, this,
1896 state.mCompressedTextureFormats, rv));
1897 return;
1900 if (mIsWebGL2) {
1901 switch (pname) {
1902 case LOCAL_GL_COPY_READ_BUFFER_BINDING:
1903 fnSetRetval_Buffer(LOCAL_GL_COPY_READ_BUFFER);
1904 return;
1906 case LOCAL_GL_COPY_WRITE_BUFFER_BINDING:
1907 fnSetRetval_Buffer(LOCAL_GL_COPY_WRITE_BUFFER);
1908 return;
1910 case LOCAL_GL_DRAW_FRAMEBUFFER_BINDING:
1911 (void)ToJSValueOrNull(cx, state.mBoundDrawFb, retval);
1912 return;
1914 case LOCAL_GL_PIXEL_PACK_BUFFER_BINDING:
1915 fnSetRetval_Buffer(LOCAL_GL_PIXEL_PACK_BUFFER);
1916 return;
1918 case LOCAL_GL_PIXEL_UNPACK_BUFFER_BINDING:
1919 fnSetRetval_Buffer(LOCAL_GL_PIXEL_UNPACK_BUFFER);
1920 return;
1922 case LOCAL_GL_READ_FRAMEBUFFER_BINDING:
1923 (void)ToJSValueOrNull(cx, state.mBoundReadFb, retval);
1924 return;
1926 case LOCAL_GL_SAMPLER_BINDING: {
1927 const auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
1928 (void)ToJSValueOrNull(cx, texUnit.sampler, retval);
1929 return;
1932 case LOCAL_GL_TEXTURE_BINDING_2D_ARRAY:
1933 fnSetRetval_Tex(LOCAL_GL_TEXTURE_2D_ARRAY);
1934 return;
1936 case LOCAL_GL_TEXTURE_BINDING_3D:
1937 fnSetRetval_Tex(LOCAL_GL_TEXTURE_3D);
1938 return;
1940 case LOCAL_GL_TRANSFORM_FEEDBACK_BINDING: {
1941 auto ret = state.mBoundTfo;
1942 if (ret == state.mDefaultTfo) {
1943 ret = nullptr;
1945 (void)ToJSValueOrNull(cx, ret, retval);
1946 return;
1949 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING:
1950 fnSetRetval_Buffer(LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER);
1951 return;
1953 case LOCAL_GL_UNIFORM_BUFFER_BINDING:
1954 fnSetRetval_Buffer(LOCAL_GL_UNIFORM_BUFFER);
1955 return;
1957 case LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS:
1958 retval.set(JS::NumberValue(limits.maxTransformFeedbackSeparateAttribs));
1959 return;
1960 case LOCAL_GL_MAX_UNIFORM_BUFFER_BINDINGS:
1961 retval.set(JS::NumberValue(limits.maxUniformBufferBindings));
1962 return;
1963 case LOCAL_GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT:
1964 retval.set(JS::NumberValue(limits.uniformBufferOffsetAlignment));
1965 return;
1966 case LOCAL_GL_MAX_3D_TEXTURE_SIZE:
1967 retval.set(JS::NumberValue(limits.maxTex3dSize));
1968 return;
1969 case LOCAL_GL_MAX_ARRAY_TEXTURE_LAYERS:
1970 retval.set(JS::NumberValue(limits.maxTexArrayLayers));
1971 return;
1973 case LOCAL_GL_PACK_ROW_LENGTH:
1974 retval.set(JS::NumberValue(state.mPixelPackState.rowLength));
1975 return;
1976 case LOCAL_GL_PACK_SKIP_PIXELS:
1977 retval.set(JS::NumberValue(state.mPixelPackState.skipPixels));
1978 return;
1979 case LOCAL_GL_PACK_SKIP_ROWS:
1980 retval.set(JS::NumberValue(state.mPixelPackState.skipRows));
1981 return;
1983 case LOCAL_GL_UNPACK_IMAGE_HEIGHT:
1984 retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackImageHeight));
1985 return;
1986 case LOCAL_GL_UNPACK_ROW_LENGTH:
1987 retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackRowLength));
1988 return;
1989 case LOCAL_GL_UNPACK_SKIP_IMAGES:
1990 retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackSkipImages));
1991 return;
1992 case LOCAL_GL_UNPACK_SKIP_PIXELS:
1993 retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackSkipPixels));
1994 return;
1995 case LOCAL_GL_UNPACK_SKIP_ROWS:
1996 retval.set(JS::NumberValue(state.mPixelUnpackState.mUnpackSkipRows));
1997 return;
1998 } // switch pname
1999 } // if webgl2
2001 // -
2003 if (!debug) {
2004 const char* ret = nullptr;
2006 switch (pname) {
2007 case LOCAL_GL_VENDOR:
2008 case LOCAL_GL_RENDERER:
2009 ret = "Mozilla";
2010 break;
2012 case LOCAL_GL_VERSION:
2013 if (mIsWebGL2) {
2014 ret = "WebGL 2.0";
2015 } else {
2016 ret = "WebGL 1.0";
2018 break;
2020 case LOCAL_GL_SHADING_LANGUAGE_VERSION:
2021 if (mIsWebGL2) {
2022 ret = "WebGL GLSL ES 3.00";
2023 } else {
2024 ret = "WebGL GLSL ES 1.0";
2026 break;
2028 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
2029 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL: {
2030 if (!IsExtensionEnabled(WebGLExtensionID::WEBGL_debug_renderer_info)) {
2031 EnqueueError_ArgEnum("pname", pname);
2032 return;
2035 const char* overridePref;
2036 GLenum driverEnum;
2037 switch (pname) {
2038 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_RENDERER_WEBGL:
2039 overridePref = "webgl.renderer-string-override";
2040 driverEnum = LOCAL_GL_RENDERER;
2041 break;
2042 case dom::WEBGL_debug_renderer_info_Binding::UNMASKED_VENDOR_WEBGL:
2043 overridePref = "webgl.vendor-string-override";
2044 driverEnum = LOCAL_GL_VENDOR;
2045 break;
2046 default:
2047 MOZ_CRASH();
2050 nsCString overrideStr;
2051 const auto res = Preferences::GetCString(overridePref, overrideStr);
2052 if (NS_SUCCEEDED(res) && overrideStr.Length() > 0) {
2053 retval.set(StringValue(cx, overrideStr.BeginReading(), rv));
2054 return;
2057 const auto maybe = GetString(driverEnum);
2058 if (maybe) {
2059 std::string renderer = *maybe;
2060 mozilla::dom::SanitizeRenderer(renderer);
2061 retval.set(StringValue(cx, renderer, rv));
2063 return;
2066 default:
2067 break;
2070 if (ret) {
2071 retval.set(StringValue(cx, ret, rv));
2072 return;
2074 } // if (!debug)
2076 // -
2078 bool debugOnly = false;
2079 bool asString = false;
2081 switch (pname) {
2082 case LOCAL_GL_EXTENSIONS:
2083 case LOCAL_GL_RENDERER:
2084 case LOCAL_GL_VENDOR:
2085 case LOCAL_GL_VERSION:
2086 case dom::MOZ_debug_Binding::WSI_INFO:
2087 debugOnly = true;
2088 asString = true;
2089 break;
2091 case dom::MOZ_debug_Binding::DOES_INDEX_VALIDATION:
2092 debugOnly = true;
2093 break;
2095 default:
2096 break;
2099 if (debugOnly && !debug) {
2100 EnqueueError_ArgEnum("pname", pname);
2101 return;
2104 // -
2106 if (asString) {
2107 const auto maybe = GetString(pname);
2108 if (maybe) {
2109 auto str = *maybe;
2110 if (pname == dom::MOZ_debug_Binding::WSI_INFO) {
2111 nsPrintfCString more("\nIsWebglOutOfProcessEnabled: %i",
2112 int(IsWebglOutOfProcessEnabled()));
2113 str += more.BeginReading();
2115 retval.set(StringValue(cx, str.c_str(), rv));
2117 } else {
2118 const auto maybe = GetNumber(pname);
2119 if (maybe) {
2120 switch (pname) {
2121 // WebGL 1:
2122 case LOCAL_GL_BLEND:
2123 case LOCAL_GL_CULL_FACE:
2124 case LOCAL_GL_DEPTH_TEST:
2125 case LOCAL_GL_DEPTH_WRITEMASK:
2126 case LOCAL_GL_DITHER:
2127 case LOCAL_GL_POLYGON_OFFSET_FILL:
2128 case LOCAL_GL_SAMPLE_ALPHA_TO_COVERAGE:
2129 case LOCAL_GL_SAMPLE_COVERAGE:
2130 case LOCAL_GL_SAMPLE_COVERAGE_INVERT:
2131 case LOCAL_GL_SCISSOR_TEST:
2132 case LOCAL_GL_STENCIL_TEST:
2133 // WebGL 2:
2134 case LOCAL_GL_RASTERIZER_DISCARD:
2135 case LOCAL_GL_TRANSFORM_FEEDBACK_ACTIVE:
2136 case LOCAL_GL_TRANSFORM_FEEDBACK_PAUSED:
2137 retval.set(JS::BooleanValue(*maybe));
2138 break;
2140 default:
2141 retval.set(JS::NumberValue(*maybe));
2142 break;
2148 void ClientWebGLContext::GetBufferParameter(
2149 JSContext* cx, GLenum target, GLenum pname,
2150 JS::MutableHandle<JS::Value> retval) const {
2151 retval.set(JS::NullValue());
2152 if (IsContextLost()) return;
2154 const auto maybe = [&]() {
2155 const auto& inProcess = mNotLost->inProcess;
2156 if (inProcess) {
2157 return inProcess->GetBufferParameter(target, pname);
2159 const auto& child = mNotLost->outOfProcess;
2160 child->FlushPendingCmds();
2161 Maybe<double> ret;
2162 if (!child->SendGetBufferParameter(target, pname, &ret)) {
2163 ret.reset();
2165 return ret;
2166 }();
2167 if (maybe) {
2168 retval.set(JS::NumberValue(*maybe));
2172 bool IsFramebufferTarget(const bool isWebgl2, const GLenum target) {
2173 switch (target) {
2174 case LOCAL_GL_FRAMEBUFFER:
2175 return true;
2177 case LOCAL_GL_DRAW_FRAMEBUFFER:
2178 case LOCAL_GL_READ_FRAMEBUFFER:
2179 return isWebgl2;
2181 default:
2182 return false;
2186 void ClientWebGLContext::GetFramebufferAttachmentParameter(
2187 JSContext* const cx, const GLenum target, const GLenum attachment,
2188 const GLenum pname, JS::MutableHandle<JS::Value> retval,
2189 ErrorResult& rv) const {
2190 retval.set(JS::NullValue());
2191 const FuncScope funcScope(*this, "getFramebufferAttachmentParameter");
2192 if (IsContextLost()) return;
2194 const auto& state = State();
2196 if (!IsFramebufferTarget(mIsWebGL2, target)) {
2197 EnqueueError_ArgEnum("target", target);
2198 return;
2200 auto fb = state.mBoundDrawFb;
2201 if (target == LOCAL_GL_READ_FRAMEBUFFER) {
2202 fb = state.mBoundReadFb;
2205 const auto fnGet = [&](const GLenum pname) {
2206 const auto fbId = fb ? fb->mId : 0;
2208 const auto& inProcess = mNotLost->inProcess;
2209 if (inProcess) {
2210 return inProcess->GetFramebufferAttachmentParameter(fbId, attachment,
2211 pname);
2213 const auto& child = mNotLost->outOfProcess;
2214 child->FlushPendingCmds();
2215 Maybe<double> ret;
2216 if (!child->SendGetFramebufferAttachmentParameter(fbId, attachment, pname,
2217 &ret)) {
2218 ret.reset();
2220 return ret;
2223 if (fb) {
2224 if (fb->mOpaque) {
2225 EnqueueError(LOCAL_GL_INVALID_OPERATION,
2226 "An opaque framebuffer's attachments cannot be inspected or "
2227 "changed.");
2228 return;
2230 auto attachmentSlotEnum = attachment;
2231 if (mIsWebGL2 && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
2232 // In webgl2, DEPTH_STENCIL is valid iff the DEPTH and STENCIL images
2233 // match, so check if the server errors.
2234 const auto maybe = fnGet(LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE);
2235 if (!maybe) return;
2236 attachmentSlotEnum = LOCAL_GL_DEPTH_ATTACHMENT;
2239 const auto maybeSlot = fb->GetAttachment(attachmentSlotEnum);
2240 if (!maybeSlot) {
2241 EnqueueError_ArgEnum("attachment", attachment);
2242 return;
2244 const auto& attached = *maybeSlot;
2246 // -
2248 if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME) {
2249 if (attached.rb) {
2250 (void)ToJSValueOrNull(cx, attached.rb, retval);
2251 } else {
2252 if (!mIsWebGL2 && !attached.tex) {
2253 EnqueueError_ArgEnum("pname", pname);
2254 return;
2256 (void)ToJSValueOrNull(cx, attached.tex, retval);
2258 return;
2262 const auto maybe = fnGet(pname);
2263 if (maybe) {
2264 retval.set(JS::NumberValue(*maybe));
2268 void ClientWebGLContext::GetRenderbufferParameter(
2269 JSContext* cx, GLenum target, GLenum pname,
2270 JS::MutableHandle<JS::Value> retval) const {
2271 retval.set(JS::NullValue());
2272 const FuncScope funcScope(*this, "getRenderbufferParameter");
2273 if (IsContextLost()) return;
2275 if (target != LOCAL_GL_RENDERBUFFER) {
2276 EnqueueError_ArgEnum("target", target);
2277 return;
2280 const auto& state = State();
2281 const auto& rb = state.mBoundRb;
2282 const auto rbId = rb ? rb->mId : 0;
2283 const auto maybe = [&]() {
2284 const auto& inProcess = mNotLost->inProcess;
2285 if (inProcess) {
2286 return inProcess->GetRenderbufferParameter(rbId, pname);
2288 const auto& child = mNotLost->outOfProcess;
2289 child->FlushPendingCmds();
2290 Maybe<double> ret;
2291 if (!child->SendGetRenderbufferParameter(rbId, pname, &ret)) {
2292 ret.reset();
2294 return ret;
2295 }();
2296 if (maybe) {
2297 retval.set(JS::NumberValue(*maybe));
2301 void ClientWebGLContext::GetIndexedParameter(
2302 JSContext* cx, GLenum target, GLuint index,
2303 JS::MutableHandle<JS::Value> retval, ErrorResult& rv) const {
2304 retval.set(JS::NullValue());
2305 const FuncScope funcScope(*this, "getIndexedParameter");
2306 if (IsContextLost()) return;
2308 const auto& state = State();
2310 switch (target) {
2311 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_BINDING: {
2312 const auto& list = state.mBoundTfo->mAttribBuffers;
2313 if (index >= list.size()) {
2314 EnqueueError(LOCAL_GL_INVALID_VALUE,
2315 "`index` (%u) >= MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS",
2316 index);
2317 return;
2319 (void)ToJSValueOrNull(cx, list[index], retval);
2320 return;
2323 case LOCAL_GL_UNIFORM_BUFFER_BINDING: {
2324 const auto& list = state.mBoundUbos;
2325 if (index >= list.size()) {
2326 EnqueueError(LOCAL_GL_INVALID_VALUE,
2327 "`index` (%u) >= MAX_UNIFORM_BUFFER_BINDINGS", index);
2328 return;
2330 (void)ToJSValueOrNull(cx, list[index], retval);
2331 return;
2335 const auto maybe = [&]() {
2336 const auto& inProcess = mNotLost->inProcess;
2337 if (inProcess) {
2338 return inProcess->GetIndexedParameter(target, index);
2340 const auto& child = mNotLost->outOfProcess;
2341 child->FlushPendingCmds();
2342 Maybe<double> ret;
2343 if (!child->SendGetIndexedParameter(target, index, &ret)) {
2344 ret.reset();
2346 return ret;
2347 }();
2348 if (maybe) {
2349 retval.set(JS::NumberValue(*maybe));
2353 void ClientWebGLContext::GetUniform(JSContext* const cx,
2354 const WebGLProgramJS& prog,
2355 const WebGLUniformLocationJS& loc,
2356 JS::MutableHandle<JS::Value> retval) {
2357 retval.set(JS::NullValue());
2358 const FuncScope funcScope(*this, "getUniform");
2359 if (IsContextLost()) return;
2360 if (!prog.ValidateUsable(*this, "prog")) return;
2361 if (!loc.ValidateUsable(*this, "loc")) return;
2363 const auto& activeLinkResult = GetActiveLinkResult();
2364 if (!activeLinkResult) {
2365 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No active linked Program.");
2366 return;
2368 const auto& reqLinkInfo = loc.mParent.lock();
2369 if (reqLinkInfo.get() != activeLinkResult) {
2370 EnqueueError(LOCAL_GL_INVALID_OPERATION,
2371 "UniformLocation is not from the current active Program.");
2372 return;
2375 const auto res = [&]() {
2376 const auto& inProcess = mNotLost->inProcess;
2377 if (inProcess) {
2378 return inProcess->GetUniform(prog.mId, loc.mLocation);
2380 const auto& child = mNotLost->outOfProcess;
2381 child->FlushPendingCmds();
2382 webgl::GetUniformData ret;
2383 if (!child->SendGetUniform(prog.mId, loc.mLocation, &ret)) {
2384 ret = {};
2386 return ret;
2387 }();
2388 if (!res.type) return;
2390 const auto elemCount = ElemTypeComponents(res.type);
2391 MOZ_ASSERT(elemCount);
2393 switch (res.type) {
2394 case LOCAL_GL_BOOL:
2395 retval.set(JS::BooleanValue(res.data[0]));
2396 return;
2398 case LOCAL_GL_FLOAT: {
2399 const auto ptr = reinterpret_cast<const float*>(res.data);
2400 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2401 return;
2403 case LOCAL_GL_INT: {
2404 const auto ptr = reinterpret_cast<const int32_t*>(res.data);
2405 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2406 return;
2408 case LOCAL_GL_UNSIGNED_INT:
2409 case LOCAL_GL_SAMPLER_2D:
2410 case LOCAL_GL_SAMPLER_3D:
2411 case LOCAL_GL_SAMPLER_CUBE:
2412 case LOCAL_GL_SAMPLER_2D_SHADOW:
2413 case LOCAL_GL_SAMPLER_2D_ARRAY:
2414 case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
2415 case LOCAL_GL_SAMPLER_CUBE_SHADOW:
2416 case LOCAL_GL_INT_SAMPLER_2D:
2417 case LOCAL_GL_INT_SAMPLER_3D:
2418 case LOCAL_GL_INT_SAMPLER_CUBE:
2419 case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
2420 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
2421 case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
2422 case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
2423 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: {
2424 const auto ptr = reinterpret_cast<const uint32_t*>(res.data);
2425 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, *ptr, retval));
2426 return;
2429 // -
2431 case LOCAL_GL_BOOL_VEC2:
2432 case LOCAL_GL_BOOL_VEC3:
2433 case LOCAL_GL_BOOL_VEC4: {
2434 const auto intArr = reinterpret_cast<const int32_t*>(res.data);
2435 bool boolArr[4] = {};
2436 for (const auto i : IntegerRange(elemCount)) {
2437 boolArr[i] = bool(intArr[i]);
2439 MOZ_ALWAYS_TRUE(dom::ToJSValue(cx, boolArr, elemCount, retval));
2440 return;
2443 case LOCAL_GL_FLOAT_VEC2:
2444 case LOCAL_GL_FLOAT_VEC3:
2445 case LOCAL_GL_FLOAT_VEC4:
2446 case LOCAL_GL_FLOAT_MAT2:
2447 case LOCAL_GL_FLOAT_MAT3:
2448 case LOCAL_GL_FLOAT_MAT4:
2449 case LOCAL_GL_FLOAT_MAT2x3:
2450 case LOCAL_GL_FLOAT_MAT2x4:
2451 case LOCAL_GL_FLOAT_MAT3x2:
2452 case LOCAL_GL_FLOAT_MAT3x4:
2453 case LOCAL_GL_FLOAT_MAT4x2:
2454 case LOCAL_GL_FLOAT_MAT4x3: {
2455 const auto ptr = reinterpret_cast<const float*>(res.data);
2456 JSObject* obj = dom::Float32Array::Create(cx, this, elemCount, ptr);
2457 MOZ_ASSERT(obj);
2458 retval.set(JS::ObjectOrNullValue(obj));
2459 return;
2462 case LOCAL_GL_INT_VEC2:
2463 case LOCAL_GL_INT_VEC3:
2464 case LOCAL_GL_INT_VEC4: {
2465 const auto ptr = reinterpret_cast<const int32_t*>(res.data);
2466 JSObject* obj = dom::Int32Array::Create(cx, this, elemCount, ptr);
2467 MOZ_ASSERT(obj);
2468 retval.set(JS::ObjectOrNullValue(obj));
2469 return;
2472 case LOCAL_GL_UNSIGNED_INT_VEC2:
2473 case LOCAL_GL_UNSIGNED_INT_VEC3:
2474 case LOCAL_GL_UNSIGNED_INT_VEC4: {
2475 const auto ptr = reinterpret_cast<const uint32_t*>(res.data);
2476 JSObject* obj = dom::Uint32Array::Create(cx, this, elemCount, ptr);
2477 MOZ_ASSERT(obj);
2478 retval.set(JS::ObjectOrNullValue(obj));
2479 return;
2482 default:
2483 MOZ_CRASH("GFX: Invalid elemType.");
2487 already_AddRefed<WebGLShaderPrecisionFormatJS>
2488 ClientWebGLContext::GetShaderPrecisionFormat(const GLenum shadertype,
2489 const GLenum precisiontype) {
2490 if (IsContextLost()) return nullptr;
2491 const auto info = [&]() {
2492 const auto& inProcess = mNotLost->inProcess;
2493 if (inProcess) {
2494 return inProcess->GetShaderPrecisionFormat(shadertype, precisiontype);
2496 const auto& child = mNotLost->outOfProcess;
2497 child->FlushPendingCmds();
2498 Maybe<webgl::ShaderPrecisionFormat> ret;
2499 if (!child->SendGetShaderPrecisionFormat(shadertype, precisiontype, &ret)) {
2500 ret.reset();
2502 return ret;
2503 }();
2505 if (!info) return nullptr;
2506 return AsAddRefed(new WebGLShaderPrecisionFormatJS(*info));
2509 void ClientWebGLContext::BlendColor(GLclampf r, GLclampf g, GLclampf b,
2510 GLclampf a) {
2511 const FuncScope funcScope(*this, "blendColor");
2512 if (IsContextLost()) return;
2513 auto& state = State();
2515 auto& cache = state.mBlendColor;
2516 cache[0] = r;
2517 cache[1] = g;
2518 cache[2] = b;
2519 cache[3] = a;
2521 Run<RPROC(BlendColor)>(r, g, b, a);
2524 void ClientWebGLContext::BlendEquationSeparate(GLenum modeRGB,
2525 GLenum modeAlpha) {
2526 Run<RPROC(BlendEquationSeparate)>(modeRGB, modeAlpha);
2529 void ClientWebGLContext::BlendFuncSeparate(GLenum srcRGB, GLenum dstRGB,
2530 GLenum srcAlpha, GLenum dstAlpha) {
2531 Run<RPROC(BlendFuncSeparate)>(srcRGB, dstRGB, srcAlpha, dstAlpha);
2534 GLenum ClientWebGLContext::CheckFramebufferStatus(GLenum target) {
2535 if (IsContextLost()) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
2537 const auto& inProcess = mNotLost->inProcess;
2538 if (inProcess) {
2539 return inProcess->CheckFramebufferStatus(target);
2541 const auto& child = mNotLost->outOfProcess;
2542 child->FlushPendingCmds();
2543 GLenum ret = 0;
2544 if (!child->SendCheckFramebufferStatus(target, &ret)) {
2545 ret = 0;
2547 return ret;
2550 void ClientWebGLContext::Clear(GLbitfield mask) {
2551 Run<RPROC(Clear)>(mask);
2553 AfterDrawCall();
2556 // -
2558 void ClientWebGLContext::ClearBufferTv(const GLenum buffer,
2559 const GLint drawBuffer,
2560 const webgl::AttribBaseType type,
2561 const Range<const uint8_t>& view,
2562 const GLuint srcElemOffset) {
2563 const FuncScope funcScope(*this, "clearBufferu?[fi]v");
2564 if (IsContextLost()) return;
2566 const auto byteOffset = CheckedInt<size_t>(srcElemOffset) * sizeof(float);
2567 if (!byteOffset.isValid() || byteOffset.value() > view.length()) {
2568 EnqueueError(LOCAL_GL_INVALID_VALUE, "`srcOffset` too large for `values`.");
2569 return;
2571 webgl::TypedQuad data;
2572 data.type = type;
2574 auto dataSize = sizeof(data.data);
2575 switch (buffer) {
2576 case LOCAL_GL_COLOR:
2577 break;
2579 case LOCAL_GL_DEPTH:
2580 dataSize = sizeof(float);
2581 break;
2583 case LOCAL_GL_STENCIL:
2584 dataSize = sizeof(int32_t);
2585 break;
2587 default:
2588 EnqueueError_ArgEnum("buffer", buffer);
2589 return;
2592 const auto requiredBytes = byteOffset + dataSize;
2593 if (!requiredBytes.isValid() || requiredBytes.value() > view.length()) {
2594 EnqueueError(LOCAL_GL_INVALID_VALUE, "`values` too small.");
2595 return;
2598 memcpy(data.data, view.begin().get() + byteOffset.value(), dataSize);
2599 Run<RPROC(ClearBufferTv)>(buffer, drawBuffer, data);
2601 AfterDrawCall();
2604 void ClientWebGLContext::ClearBufferfi(GLenum buffer, GLint drawBuffer,
2605 GLfloat depth, GLint stencil) {
2606 Run<RPROC(ClearBufferfi)>(buffer, drawBuffer, depth, stencil);
2608 AfterDrawCall();
2611 // -
2613 void ClientWebGLContext::ClearColor(GLclampf r, GLclampf g, GLclampf b,
2614 GLclampf a) {
2615 const FuncScope funcScope(*this, "clearColor");
2616 if (IsContextLost()) return;
2617 auto& state = State();
2619 auto& cache = state.mClearColor;
2620 cache[0] = r;
2621 cache[1] = g;
2622 cache[2] = b;
2623 cache[3] = a;
2625 Run<RPROC(ClearColor)>(r, g, b, a);
2628 void ClientWebGLContext::ClearDepth(GLclampf v) { Run<RPROC(ClearDepth)>(v); }
2630 void ClientWebGLContext::ClearStencil(GLint v) { Run<RPROC(ClearStencil)>(v); }
2632 void ClientWebGLContext::ColorMask(WebGLboolean r, WebGLboolean g,
2633 WebGLboolean b, WebGLboolean a) {
2634 const FuncScope funcScope(*this, "colorMask");
2635 if (IsContextLost()) return;
2636 auto& state = State();
2638 state.mColorWriteMask = {r, g, b, a};
2640 Run<RPROC(ColorMask)>(r, g, b, a);
2643 void ClientWebGLContext::CullFace(GLenum face) { Run<RPROC(CullFace)>(face); }
2645 void ClientWebGLContext::DepthFunc(GLenum func) { Run<RPROC(DepthFunc)>(func); }
2647 void ClientWebGLContext::DepthMask(WebGLboolean b) { Run<RPROC(DepthMask)>(b); }
2649 void ClientWebGLContext::DepthRange(GLclampf zNear, GLclampf zFar) {
2650 const FuncScope funcScope(*this, "depthRange");
2651 if (IsContextLost()) return;
2652 auto& state = State();
2654 state.mDepthRange = {zNear, zFar};
2656 Run<RPROC(DepthRange)>(zNear, zFar);
2659 void ClientWebGLContext::Flush() {
2660 const FuncScope funcScope(*this, "flush");
2661 const auto notLost = mNotLost;
2662 if (IsContextLost()) return;
2664 Run<RPROC(Flush)>();
2666 if (notLost->inProcess) return;
2667 const auto& child = mNotLost->outOfProcess;
2668 child->FlushPendingCmds();
2671 void ClientWebGLContext::Finish() {
2672 if (IsContextLost()) return;
2674 const auto& inProcess = mNotLost->inProcess;
2675 if (inProcess) {
2676 inProcess->Finish();
2677 return;
2679 const auto& child = mNotLost->outOfProcess;
2680 child->FlushPendingCmds();
2681 (void)child->SendFinish();
2684 void ClientWebGLContext::FrontFace(GLenum mode) { Run<RPROC(FrontFace)>(mode); }
2686 GLenum ClientWebGLContext::GetError() {
2687 const auto notLost = mNotLost;
2688 if (mNextError) {
2689 const auto ret = mNextError;
2690 mNextError = 0;
2691 return ret;
2693 if (IsContextLost()) return 0;
2695 const auto& inProcess = notLost->inProcess;
2696 if (inProcess) {
2697 return inProcess->GetError();
2699 const auto& child = notLost->outOfProcess;
2700 child->FlushPendingCmds();
2701 GLenum ret = 0;
2702 if (!child->SendGetError(&ret)) {
2703 ret = 0;
2705 return ret;
2708 void ClientWebGLContext::Hint(GLenum target, GLenum mode) {
2709 Run<RPROC(Hint)>(target, mode);
2712 void ClientWebGLContext::LineWidth(GLfloat width) {
2713 Run<RPROC(LineWidth)>(width);
2716 Maybe<webgl::ErrorInfo> SetPixelUnpack(const bool isWebgl2,
2717 WebGLPixelStore* const unpacking,
2718 const GLenum pname, const GLint param);
2720 void ClientWebGLContext::PixelStorei(const GLenum pname, const GLint iparam) {
2721 const FuncScope funcScope(*this, "pixelStorei");
2722 if (IsContextLost()) return;
2723 if (!ValidateNonNegative("param", iparam)) return;
2724 const auto param = static_cast<uint32_t>(iparam);
2726 auto& state = State();
2727 auto& packState = state.mPixelPackState;
2728 switch (pname) {
2729 case LOCAL_GL_PACK_ALIGNMENT:
2730 switch (param) {
2731 case 1:
2732 case 2:
2733 case 4:
2734 case 8:
2735 break;
2736 default:
2737 EnqueueError(LOCAL_GL_INVALID_VALUE,
2738 "PACK_ALIGNMENT must be one of [1,2,4,8], was %i.",
2739 iparam);
2740 return;
2742 packState.alignment = param;
2743 return;
2745 case LOCAL_GL_PACK_ROW_LENGTH:
2746 if (!mIsWebGL2) break;
2747 packState.rowLength = param;
2748 return;
2750 case LOCAL_GL_PACK_SKIP_PIXELS:
2751 if (!mIsWebGL2) break;
2752 packState.skipPixels = param;
2753 return;
2755 case LOCAL_GL_PACK_SKIP_ROWS:
2756 if (!mIsWebGL2) break;
2757 packState.skipRows = param;
2758 return;
2760 case dom::MOZ_debug_Binding::UNPACK_REQUIRE_FASTPATH:
2761 if (!IsSupported(WebGLExtensionID::MOZ_debug)) {
2762 EnqueueError_ArgEnum("pname", pname);
2763 return;
2765 break;
2767 default:
2768 break;
2771 const auto err =
2772 SetPixelUnpack(mIsWebGL2, &state.mPixelUnpackState, pname, iparam);
2773 if (err) {
2774 EnqueueError(*err);
2775 return;
2779 void ClientWebGLContext::PolygonOffset(GLfloat factor, GLfloat units) {
2780 Run<RPROC(PolygonOffset)>(factor, units);
2783 void ClientWebGLContext::SampleCoverage(GLclampf value, WebGLboolean invert) {
2784 Run<RPROC(SampleCoverage)>(value, invert);
2787 void ClientWebGLContext::Scissor(GLint x, GLint y, GLsizei width,
2788 GLsizei height) {
2789 const FuncScope funcScope(*this, "scissor");
2790 if (IsContextLost()) return;
2791 auto& state = State();
2793 if (!ValidateNonNegative("width", width) ||
2794 !ValidateNonNegative("height", height)) {
2795 return;
2798 state.mScissor = {x, y, width, height};
2800 Run<RPROC(Scissor)>(x, y, width, height);
2803 void ClientWebGLContext::StencilFuncSeparate(GLenum face, GLenum func,
2804 GLint ref, GLuint mask) {
2805 Run<RPROC(StencilFuncSeparate)>(face, func, ref, mask);
2808 void ClientWebGLContext::StencilMaskSeparate(GLenum face, GLuint mask) {
2809 Run<RPROC(StencilMaskSeparate)>(face, mask);
2812 void ClientWebGLContext::StencilOpSeparate(GLenum face, GLenum sfail,
2813 GLenum dpfail, GLenum dppass) {
2814 Run<RPROC(StencilOpSeparate)>(face, sfail, dpfail, dppass);
2817 void ClientWebGLContext::Viewport(GLint x, GLint y, GLsizei width,
2818 GLsizei height) {
2819 const FuncScope funcScope(*this, "viewport");
2820 if (IsContextLost()) return;
2821 auto& state = State();
2823 if (!ValidateNonNegative("width", width) ||
2824 !ValidateNonNegative("height", height)) {
2825 return;
2828 state.mViewport = {x, y, width, height};
2830 Run<RPROC(Viewport)>(x, y, width, height);
2833 // ------------------------- Buffer Objects -------------------------
2835 Maybe<const webgl::ErrorInfo> ValidateBindBuffer(
2836 const GLenum target, const webgl::BufferKind curKind) {
2837 if (curKind == webgl::BufferKind::Undefined) return {};
2839 auto requiredKind = webgl::BufferKind::NonIndex;
2840 switch (target) {
2841 case LOCAL_GL_COPY_READ_BUFFER:
2842 case LOCAL_GL_COPY_WRITE_BUFFER:
2843 return {}; // Always ok
2845 case LOCAL_GL_ELEMENT_ARRAY_BUFFER:
2846 requiredKind = webgl::BufferKind::Index;
2847 break;
2849 default:
2850 break;
2853 if (curKind != requiredKind) {
2854 const auto fnKindStr = [&](const webgl::BufferKind kind) {
2855 if (kind == webgl::BufferKind::Index) return "ELEMENT_ARRAY_BUFFER";
2856 return "non-ELEMENT_ARRAY_BUFFER";
2858 const auto info = nsPrintfCString(
2859 "Buffer previously bound to %s cannot be now bound to %s.",
2860 fnKindStr(curKind), fnKindStr(requiredKind));
2861 return Some(
2862 webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION, info.BeginReading()});
2865 return {};
2868 Maybe<webgl::ErrorInfo> CheckBindBufferRange(
2869 const GLenum target, const GLuint index, const bool isBuffer,
2870 const uint64_t offset, const uint64_t size, const webgl::Limits& limits) {
2871 const auto fnSome = [&](const GLenum type, const nsACString& info) {
2872 return Some(webgl::ErrorInfo{type, info.BeginReading()});
2875 switch (target) {
2876 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
2877 if (index >= limits.maxTransformFeedbackSeparateAttribs) {
2878 const auto info = nsPrintfCString(
2879 "`index` (%u) must be less than "
2880 "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS (%u).",
2881 index, limits.maxTransformFeedbackSeparateAttribs);
2882 return fnSome(LOCAL_GL_INVALID_VALUE, info);
2885 if (isBuffer) {
2886 if (offset % 4 != 0 || size % 4 != 0) {
2887 const auto info =
2888 nsPrintfCString("`offset` (%" PRIu64 ") and `size` (%" PRIu64
2889 ") must both be aligned to 4 for"
2890 " TRANSFORM_FEEDBACK_BUFFER.",
2891 offset, size);
2892 return fnSome(LOCAL_GL_INVALID_VALUE, info);
2895 break;
2897 case LOCAL_GL_UNIFORM_BUFFER:
2898 if (index >= limits.maxUniformBufferBindings) {
2899 const auto info = nsPrintfCString(
2900 "`index` (%u) must be less than MAX_UNIFORM_BUFFER_BINDINGS (%u).",
2901 index, limits.maxUniformBufferBindings);
2902 return fnSome(LOCAL_GL_INVALID_VALUE, info);
2905 if (isBuffer) {
2906 if (offset % limits.uniformBufferOffsetAlignment != 0) {
2907 const auto info =
2908 nsPrintfCString("`offset` (%" PRIu64
2909 ") must be aligned to "
2910 "UNIFORM_BUFFER_OFFSET_ALIGNMENT (%u).",
2911 offset, limits.uniformBufferOffsetAlignment);
2912 return fnSome(LOCAL_GL_INVALID_VALUE, info);
2915 break;
2917 default: {
2918 const auto info =
2919 nsPrintfCString("Unrecognized `target`: 0x%04x", target);
2920 return fnSome(LOCAL_GL_INVALID_ENUM, info);
2924 return {};
2927 // -
2929 void ClientWebGLContext::BindBuffer(const GLenum target,
2930 WebGLBufferJS* const buffer) {
2931 const FuncScope funcScope(*this, "bindBuffer");
2932 if (IsContextLost()) return;
2933 if (buffer && !buffer->ValidateUsable(*this, "buffer")) return;
2935 // -
2936 // Check for INVALID_ENUM
2938 auto& state = State();
2939 auto* slot = &(state.mBoundVao->mIndexBuffer);
2940 if (target != LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
2941 const auto itr = state.mBoundBufferByTarget.find(target);
2942 if (itr == state.mBoundBufferByTarget.end()) {
2943 EnqueueError_ArgEnum("target", target);
2944 return;
2946 slot = &(itr->second);
2949 // -
2951 auto kind = webgl::BufferKind::Undefined;
2952 if (buffer) {
2953 kind = buffer->mKind;
2955 const auto err = ValidateBindBuffer(target, kind);
2956 if (err) {
2957 EnqueueError(err->type, "%s", err->info.c_str());
2958 return;
2961 // -
2962 // Validation complete
2964 if (buffer && buffer->mKind == webgl::BufferKind::Undefined) {
2965 if (target == LOCAL_GL_ELEMENT_ARRAY_BUFFER) {
2966 buffer->mKind = webgl::BufferKind::Index;
2967 } else {
2968 buffer->mKind = webgl::BufferKind::NonIndex;
2971 *slot = buffer;
2973 // -
2975 Run<RPROC(BindBuffer)>(target, buffer ? buffer->mId : 0);
2978 // -
2980 void ClientWebGLContext::BindBufferRangeImpl(const GLenum target,
2981 const GLuint index,
2982 WebGLBufferJS* const buffer,
2983 const uint64_t offset,
2984 const uint64_t size) {
2985 if (buffer && !buffer->ValidateUsable(*this, "buffer")) return;
2986 auto& state = State();
2988 // -
2990 const auto& limits = Limits();
2991 auto err =
2992 CheckBindBufferRange(target, index, bool(buffer), offset, size, limits);
2993 if (err) {
2994 EnqueueError(err->type, "%s", err->info.c_str());
2995 return;
2998 // -
3000 auto kind = webgl::BufferKind::Undefined;
3001 if (buffer) {
3002 kind = buffer->mKind;
3004 err = ValidateBindBuffer(target, kind);
3005 if (err) {
3006 EnqueueError(err->type, "%s", err->info.c_str());
3007 return;
3010 if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) {
3011 if (state.mTfActiveAndNotPaused) {
3012 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3013 "Cannot change TRANSFORM_FEEDBACK_BUFFER while "
3014 "TransformFeedback is active and not paused.");
3015 return;
3019 // -
3020 // Validation complete
3022 if (buffer && buffer->mKind == webgl::BufferKind::Undefined) {
3023 buffer->mKind = webgl::BufferKind::NonIndex;
3026 // -
3028 switch (target) {
3029 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER:
3030 state.mBoundTfo->mAttribBuffers[index] = buffer;
3031 break;
3033 case LOCAL_GL_UNIFORM_BUFFER:
3034 state.mBoundUbos[index] = buffer;
3035 break;
3037 default:
3038 MOZ_CRASH("Bad `target`");
3040 state.mBoundBufferByTarget[target] = buffer;
3042 // -
3044 Run<RPROC(BindBufferRange)>(target, index, buffer ? buffer->mId : 0, offset,
3045 size);
3048 void ClientWebGLContext::GetBufferSubData(GLenum target, GLintptr srcByteOffset,
3049 const dom::ArrayBufferView& dstData,
3050 GLuint dstElemOffset,
3051 GLuint dstElemCountOverride) {
3052 const FuncScope funcScope(*this, "getBufferSubData");
3053 if (IsContextLost()) return;
3054 const auto notLost =
3055 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
3056 if (!ValidateNonNegative("srcByteOffset", srcByteOffset)) return;
3058 uint8_t* bytes;
3059 size_t byteLen;
3060 if (!ValidateArrayBufferView(dstData, dstElemOffset, dstElemCountOverride,
3061 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
3062 return;
3064 const auto destView = Range<uint8_t>{bytes, byteLen};
3066 const auto& inProcessContext = notLost->inProcess;
3067 if (inProcessContext) {
3068 inProcessContext->GetBufferSubData(target, srcByteOffset, destView);
3069 return;
3072 const auto& child = notLost->outOfProcess;
3073 child->FlushPendingCmds();
3074 mozilla::ipc::Shmem rawShmem;
3075 if (!child->SendGetBufferSubData(target, srcByteOffset, destView.length(),
3076 &rawShmem)) {
3077 return;
3079 const webgl::RaiiShmem shmem{child, rawShmem};
3081 const auto shmemView = shmem.ByteRange();
3082 MOZ_RELEASE_ASSERT(shmemView.length() == 1 + destView.length());
3084 const auto ok = bool(*(shmemView.begin().get()));
3085 const auto srcView =
3086 Range<const uint8_t>{shmemView.begin() + 1, shmemView.end()};
3087 if (ok) {
3088 Memcpy(destView.begin(), srcView.begin(), srcView.length());
3092 ////
3094 void ClientWebGLContext::BufferData(GLenum target, WebGLsizeiptr rawSize,
3095 GLenum usage) {
3096 const FuncScope funcScope(*this, "bufferData");
3097 if (!ValidateNonNegative("size", rawSize)) return;
3099 const auto size = MaybeAs<size_t>(rawSize);
3100 if (!size) {
3101 EnqueueError(LOCAL_GL_OUT_OF_MEMORY, "`size` too large for platform.");
3102 return;
3105 const auto data = RawBuffer<>{*size};
3106 Run<RPROC(BufferData)>(target, data, usage);
3109 void ClientWebGLContext::BufferData(
3110 GLenum target, const dom::Nullable<dom::ArrayBuffer>& maybeSrc,
3111 GLenum usage) {
3112 const FuncScope funcScope(*this, "bufferData");
3113 if (!ValidateNonNull("src", maybeSrc)) return;
3114 const auto& src = maybeSrc.Value();
3116 src.ComputeState();
3117 const auto range = Range<const uint8_t>{src.Data(), src.Length()};
3118 Run<RPROC(BufferData)>(target, RawBuffer<>(range), usage);
3121 void ClientWebGLContext::BufferData(GLenum target,
3122 const dom::ArrayBufferView& src,
3123 GLenum usage, GLuint srcElemOffset,
3124 GLuint srcElemCountOverride) {
3125 const FuncScope funcScope(*this, "bufferData");
3126 uint8_t* bytes;
3127 size_t byteLen;
3128 if (!ValidateArrayBufferView(src, srcElemOffset, srcElemCountOverride,
3129 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
3130 return;
3132 const auto range = Range<const uint8_t>{bytes, byteLen};
3133 Run<RPROC(BufferData)>(target, RawBuffer<>(range), usage);
3136 ////
3138 void ClientWebGLContext::BufferSubData(GLenum target,
3139 WebGLsizeiptr dstByteOffset,
3140 const dom::ArrayBuffer& src) {
3141 const FuncScope funcScope(*this, "bufferSubData");
3142 src.ComputeState();
3143 const auto range = Range<const uint8_t>{src.Data(), src.Length()};
3144 Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(range));
3147 void ClientWebGLContext::BufferSubData(GLenum target,
3148 WebGLsizeiptr dstByteOffset,
3149 const dom::ArrayBufferView& src,
3150 GLuint srcElemOffset,
3151 GLuint srcElemCountOverride) {
3152 const FuncScope funcScope(*this, "bufferSubData");
3153 uint8_t* bytes;
3154 size_t byteLen;
3155 if (!ValidateArrayBufferView(src, srcElemOffset, srcElemCountOverride,
3156 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
3157 return;
3159 const auto range = Range<const uint8_t>{bytes, byteLen};
3160 Run<RPROC(BufferSubData)>(target, dstByteOffset, RawBuffer<>(range));
3163 void ClientWebGLContext::CopyBufferSubData(GLenum readTarget,
3164 GLenum writeTarget,
3165 GLintptr readOffset,
3166 GLintptr writeOffset,
3167 GLsizeiptr size) {
3168 const FuncScope funcScope(*this, "copyBufferSubData");
3169 if (!ValidateNonNegative("readOffset", readOffset) ||
3170 !ValidateNonNegative("writeOffset", writeOffset) ||
3171 !ValidateNonNegative("size", size)) {
3172 return;
3174 Run<RPROC(CopyBufferSubData)>(
3175 readTarget, writeTarget, static_cast<uint64_t>(readOffset),
3176 static_cast<uint64_t>(writeOffset), static_cast<uint64_t>(size));
3179 // -------------------------- Framebuffer Objects --------------------------
3181 void ClientWebGLContext::BindFramebuffer(const GLenum target,
3182 WebGLFramebufferJS* const fb) {
3183 const FuncScope funcScope(*this, "bindFramebuffer");
3184 if (IsContextLost()) return;
3185 if (fb && !fb->ValidateUsable(*this, "fb")) return;
3187 if (!IsFramebufferTarget(mIsWebGL2, target)) {
3188 EnqueueError_ArgEnum("target", target);
3189 return;
3192 // -
3194 auto& state = State();
3196 switch (target) {
3197 case LOCAL_GL_FRAMEBUFFER:
3198 state.mBoundDrawFb = fb;
3199 state.mBoundReadFb = fb;
3200 break;
3202 case LOCAL_GL_DRAW_FRAMEBUFFER:
3203 state.mBoundDrawFb = fb;
3204 break;
3205 case LOCAL_GL_READ_FRAMEBUFFER:
3206 state.mBoundReadFb = fb;
3207 break;
3209 default:
3210 MOZ_CRASH();
3213 // -
3215 if (fb) {
3216 fb->mHasBeenBound = true;
3219 Run<RPROC(BindFramebuffer)>(target, fb ? fb->mId : 0);
3222 // -
3224 void ClientWebGLContext::FramebufferTexture2D(GLenum target, GLenum attachSlot,
3225 GLenum bindImageTarget,
3226 WebGLTextureJS* const tex,
3227 GLint mipLevel) const {
3228 const FuncScope funcScope(*this, "framebufferTexture2D");
3229 if (IsContextLost()) return;
3231 const auto bindTexTarget = ImageToTexTarget(bindImageTarget);
3232 uint32_t zLayer = 0;
3233 switch (bindTexTarget) {
3234 case LOCAL_GL_TEXTURE_2D:
3235 break;
3236 case LOCAL_GL_TEXTURE_CUBE_MAP:
3237 zLayer = bindImageTarget - LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X;
3238 break;
3239 default:
3240 EnqueueError_ArgEnum("imageTarget", bindImageTarget);
3241 return;
3244 if (!mIsWebGL2 &&
3245 !IsExtensionEnabled(WebGLExtensionID::OES_fbo_render_mipmap)) {
3246 if (mipLevel != 0) {
3247 EnqueueError(LOCAL_GL_INVALID_VALUE,
3248 "mipLevel != 0 requires OES_fbo_render_mipmap.");
3249 return;
3253 FramebufferAttach(target, attachSlot, bindImageTarget, nullptr, tex,
3254 static_cast<uint32_t>(mipLevel), zLayer, 0);
3257 Maybe<webgl::ErrorInfo> CheckFramebufferAttach(const GLenum bindImageTarget,
3258 const GLenum curTexTarget,
3259 const uint32_t mipLevel,
3260 const uint32_t zLayerBase,
3261 const uint32_t zLayerCount,
3262 const webgl::Limits& limits) {
3263 if (!curTexTarget) {
3264 return Some(
3265 webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3266 "`tex` not yet bound. Call bindTexture first."});
3269 auto texTarget = curTexTarget;
3270 if (bindImageTarget) {
3271 // FramebufferTexture2D
3272 const auto bindTexTarget = ImageToTexTarget(bindImageTarget);
3273 if (curTexTarget != bindTexTarget) {
3274 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3275 "`tex` cannot be rebound to a new target."});
3278 switch (bindTexTarget) {
3279 case LOCAL_GL_TEXTURE_2D:
3280 case LOCAL_GL_TEXTURE_CUBE_MAP:
3281 break;
3282 default:
3283 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_ENUM,
3284 "`tex` must have been bound to target "
3285 "TEXTURE_2D or TEXTURE_CUBE_MAP."});
3287 texTarget = bindTexTarget;
3288 } else {
3289 // FramebufferTextureLayer/Multiview
3290 switch (curTexTarget) {
3291 case LOCAL_GL_TEXTURE_2D_ARRAY:
3292 case LOCAL_GL_TEXTURE_3D:
3293 break;
3294 default:
3295 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_OPERATION,
3296 "`tex` must have been bound to target "
3297 "TEXTURE_2D_ARRAY or TEXTURE_3D."});
3300 MOZ_ASSERT(texTarget);
3301 uint32_t maxSize;
3302 uint32_t maxZ;
3303 switch (texTarget) {
3304 case LOCAL_GL_TEXTURE_2D:
3305 maxSize = limits.maxTex2dSize;
3306 maxZ = 1;
3307 break;
3308 case LOCAL_GL_TEXTURE_CUBE_MAP:
3309 maxSize = limits.maxTexCubeSize;
3310 maxZ = 6;
3311 break;
3312 case LOCAL_GL_TEXTURE_2D_ARRAY:
3313 maxSize = limits.maxTex2dSize;
3314 maxZ = limits.maxTexArrayLayers;
3315 break;
3316 case LOCAL_GL_TEXTURE_3D:
3317 maxSize = limits.maxTex3dSize;
3318 maxZ = limits.maxTex3dSize;
3319 break;
3320 default:
3321 MOZ_CRASH();
3323 const auto maxMipLevel = FloorLog2(maxSize);
3324 if (mipLevel > maxMipLevel) {
3325 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE,
3326 "`mipLevel` too large for texture target."});
3328 const auto requiredZLayers = CheckedInt<uint32_t>(zLayerBase) + zLayerCount;
3329 if (!requiredZLayers.isValid() || requiredZLayers.value() > maxZ) {
3330 return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE,
3331 "`zLayer` too large for texture target."});
3334 return {};
3337 void ClientWebGLContext::FramebufferAttach(
3338 const GLenum target, const GLenum attachSlot, const GLenum bindImageTarget,
3339 WebGLRenderbufferJS* const rb, WebGLTextureJS* const tex,
3340 const uint32_t mipLevel, const uint32_t zLayerBase,
3341 const uint32_t numViewLayers) const {
3342 if (rb && !rb->ValidateUsable(*this, "rb")) return;
3343 if (tex && !tex->ValidateUsable(*this, "tex")) return;
3344 const auto& state = State();
3345 const auto& limits = Limits();
3347 if (!IsFramebufferTarget(mIsWebGL2, target)) {
3348 EnqueueError_ArgEnum("target", target);
3349 return;
3351 auto fb = state.mBoundDrawFb;
3352 if (target == LOCAL_GL_READ_FRAMEBUFFER) {
3353 fb = state.mBoundReadFb;
3355 if (!fb) {
3356 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No framebuffer bound.");
3357 return;
3360 if (fb->mOpaque) {
3361 EnqueueError(
3362 LOCAL_GL_INVALID_OPERATION,
3363 "An opaque framebuffer's attachments cannot be inspected or changed.");
3364 return;
3367 // -
3368 // Multiview-specific validation skipped by Host.
3370 if (tex && numViewLayers) {
3371 if (tex->mTarget != LOCAL_GL_TEXTURE_2D_ARRAY) {
3372 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3373 "`tex` must have been bound to target TEXTURE_2D_ARRAY.");
3374 return;
3376 if (numViewLayers > limits.maxMultiviewLayers) {
3377 EnqueueError(LOCAL_GL_INVALID_VALUE,
3378 "`numViews` (%u) must be <= MAX_VIEWS (%u).", numViewLayers,
3379 limits.maxMultiviewLayers);
3380 return;
3384 // -
3386 webgl::ObjectId id = 0;
3387 if (tex) {
3388 auto zLayerCount = numViewLayers;
3389 if (!zLayerCount) {
3390 zLayerCount = 1;
3392 const auto err =
3393 CheckFramebufferAttach(bindImageTarget, tex->mTarget, mipLevel,
3394 zLayerBase, zLayerCount, limits);
3395 if (err) {
3396 EnqueueError(err->type, "%s", err->info.c_str());
3397 return;
3399 id = tex->mId;
3400 } else if (rb) {
3401 if (!rb->mHasBeenBound) {
3402 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3403 "`rb` has not yet been bound with BindRenderbuffer.");
3404 return;
3406 id = rb->mId;
3409 // Ready!
3410 // But DEPTH_STENCIL in webgl2 is actually two slots!
3412 const auto fnAttachTo = [&](const GLenum actualAttachSlot) {
3413 const auto slot = fb->GetAttachment(actualAttachSlot);
3414 if (!slot) {
3415 EnqueueError_ArgEnum("attachment", actualAttachSlot);
3416 return;
3419 slot->rb = rb;
3420 slot->tex = tex;
3422 Run<RPROC(FramebufferAttach)>(target, actualAttachSlot, bindImageTarget, id,
3423 mipLevel, zLayerBase, numViewLayers);
3426 if (mIsWebGL2 && attachSlot == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
3427 fnAttachTo(LOCAL_GL_DEPTH_ATTACHMENT);
3428 fnAttachTo(LOCAL_GL_STENCIL_ATTACHMENT);
3429 } else {
3430 fnAttachTo(attachSlot);
3433 if (bindImageTarget) {
3434 if (rb) {
3435 rb->mHasBeenBound = true;
3437 if (tex) {
3438 tex->mTarget = ImageToTexTarget(bindImageTarget);
3443 // -
3445 void ClientWebGLContext::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1,
3446 GLint srcY1, GLint dstX0, GLint dstY0,
3447 GLint dstX1, GLint dstY1,
3448 GLbitfield mask, GLenum filter) {
3449 Run<RPROC(BlitFramebuffer)>(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1,
3450 dstY1, mask, filter);
3452 AfterDrawCall();
3455 void ClientWebGLContext::InvalidateFramebuffer(
3456 GLenum target, const dom::Sequence<GLenum>& attachments,
3457 ErrorResult& unused) {
3458 const auto range = MakeRange(attachments);
3459 const auto& buffer = RawBufferView(range);
3460 Run<RPROC(InvalidateFramebuffer)>(target, buffer);
3462 // Never invalidate the backbuffer, so never needs AfterDrawCall.
3465 void ClientWebGLContext::InvalidateSubFramebuffer(
3466 GLenum target, const dom::Sequence<GLenum>& attachments, GLint x, GLint y,
3467 GLsizei width, GLsizei height, ErrorResult& unused) {
3468 const auto range = MakeRange(attachments);
3469 const auto& buffer = RawBufferView(range);
3470 Run<RPROC(InvalidateSubFramebuffer)>(target, buffer, x, y, width, height);
3472 // Never invalidate the backbuffer, so never needs AfterDrawCall.
3475 void ClientWebGLContext::ReadBuffer(GLenum mode) {
3476 Run<RPROC(ReadBuffer)>(mode);
3479 // ----------------------- Renderbuffer objects -----------------------
3481 void ClientWebGLContext::BindRenderbuffer(const GLenum target,
3482 WebGLRenderbufferJS* const rb) {
3483 const FuncScope funcScope(*this, "bindRenderbuffer");
3484 if (IsContextLost()) return;
3485 if (rb && !rb->ValidateUsable(*this, "rb")) return;
3486 auto& state = State();
3488 if (target != LOCAL_GL_RENDERBUFFER) {
3489 EnqueueError_ArgEnum("target", target);
3490 return;
3493 state.mBoundRb = rb;
3494 if (rb) {
3495 rb->mHasBeenBound = true;
3499 void ClientWebGLContext::RenderbufferStorageMultisample(GLenum target,
3500 GLsizei samples,
3501 GLenum internalFormat,
3502 GLsizei width,
3503 GLsizei height) const {
3504 const FuncScope funcScope(*this, "renderbufferStorageMultisample");
3505 if (IsContextLost()) return;
3507 if (target != LOCAL_GL_RENDERBUFFER) {
3508 EnqueueError_ArgEnum("target", target);
3509 return;
3512 const auto& state = State();
3514 const auto& rb = state.mBoundRb;
3515 if (!rb) {
3516 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No renderbuffer bound");
3517 return;
3520 if (!ValidateNonNegative("width", width) ||
3521 !ValidateNonNegative("height", height) ||
3522 !ValidateNonNegative("samples", samples)) {
3523 return;
3526 if (internalFormat == LOCAL_GL_DEPTH_STENCIL && samples > 0) {
3527 // While our backend supports it trivially, the spec forbids it.
3528 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3529 "WebGL 1's DEPTH_STENCIL format may not be multisampled. Use "
3530 "DEPTH24_STENCIL8 when `samples > 0`.");
3531 return;
3534 Run<RPROC(RenderbufferStorageMultisample)>(
3535 rb->mId, static_cast<uint32_t>(samples), internalFormat,
3536 static_cast<uint32_t>(width), static_cast<uint32_t>(height));
3539 // --------------------------- Texture objects ---------------------------
3541 void ClientWebGLContext::ActiveTexture(const GLenum texUnitEnum) {
3542 const FuncScope funcScope(*this, "activeTexture");
3543 if (IsContextLost()) return;
3545 if (texUnitEnum < LOCAL_GL_TEXTURE0) {
3546 EnqueueError(LOCAL_GL_INVALID_VALUE,
3547 "`texture` (0x%04x) must be >= TEXTURE0 (0x%04x).",
3548 texUnitEnum, LOCAL_GL_TEXTURE0);
3549 return;
3552 const auto texUnit = texUnitEnum - LOCAL_GL_TEXTURE0;
3554 auto& state = State();
3555 if (texUnit >= state.mTexUnits.size()) {
3556 EnqueueError(LOCAL_GL_INVALID_VALUE,
3557 "TEXTURE%u must be < MAX_COMBINED_TEXTURE_IMAGE_UNITS (%zu).",
3558 texUnit, state.mTexUnits.size());
3559 return;
3564 state.mActiveTexUnit = texUnit;
3565 Run<RPROC(ActiveTexture)>(texUnit);
3568 static bool IsTexTarget(const GLenum texTarget, const bool webgl2) {
3569 switch (texTarget) {
3570 case LOCAL_GL_TEXTURE_2D:
3571 case LOCAL_GL_TEXTURE_CUBE_MAP:
3572 return true;
3574 case LOCAL_GL_TEXTURE_2D_ARRAY:
3575 case LOCAL_GL_TEXTURE_3D:
3576 return webgl2;
3578 default:
3579 return false;
3583 void ClientWebGLContext::BindTexture(const GLenum texTarget,
3584 WebGLTextureJS* const tex) {
3585 const FuncScope funcScope(*this, "bindTexture");
3586 if (IsContextLost()) return;
3587 if (tex && !tex->ValidateUsable(*this, "tex")) return;
3589 if (!IsTexTarget(texTarget, mIsWebGL2)) {
3590 EnqueueError_ArgEnum("texTarget", texTarget);
3591 return;
3594 if (tex && tex->mTarget) {
3595 if (texTarget != tex->mTarget) {
3596 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3597 "Texture previously bound to %s cannot be bound now to %s.",
3598 EnumString(tex->mTarget).c_str(),
3599 EnumString(texTarget).c_str());
3600 return;
3604 auto& state = State();
3605 auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
3606 texUnit.texByTarget[texTarget] = tex;
3607 if (tex) {
3608 tex->mTarget = texTarget;
3611 Run<RPROC(BindTexture)>(texTarget, tex ? tex->mId : 0);
3614 void ClientWebGLContext::GenerateMipmap(GLenum texTarget) const {
3615 Run<RPROC(GenerateMipmap)>(texTarget);
3618 void ClientWebGLContext::GetTexParameter(
3619 JSContext* cx, GLenum texTarget, GLenum pname,
3620 JS::MutableHandle<JS::Value> retval) const {
3621 retval.set(JS::NullValue());
3622 const FuncScope funcScope(*this, "getTexParameter");
3623 if (IsContextLost()) return;
3624 auto& state = State();
3626 auto& texUnit = state.mTexUnits[state.mActiveTexUnit];
3628 const auto& tex = Find(texUnit.texByTarget, texTarget, nullptr);
3629 if (!tex) {
3630 if (!IsTexTarget(texTarget, mIsWebGL2)) {
3631 EnqueueError_ArgEnum("texTarget", texTarget);
3632 } else {
3633 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No texture bound to %s[%u].",
3634 EnumString(texTarget).c_str(), state.mActiveTexUnit);
3636 return;
3639 const auto maybe = [&]() {
3640 const auto& inProcess = mNotLost->inProcess;
3641 if (inProcess) {
3642 return inProcess->GetTexParameter(tex->mId, pname);
3644 const auto& child = mNotLost->outOfProcess;
3645 child->FlushPendingCmds();
3646 Maybe<double> ret;
3647 if (!child->SendGetTexParameter(tex->mId, pname, &ret)) {
3648 ret.reset();
3650 return ret;
3651 }();
3653 if (maybe) {
3654 switch (pname) {
3655 case LOCAL_GL_TEXTURE_IMMUTABLE_FORMAT:
3656 retval.set(JS::BooleanValue(*maybe));
3657 break;
3659 default:
3660 retval.set(JS::NumberValue(*maybe));
3661 break;
3666 void ClientWebGLContext::TexParameterf(GLenum texTarget, GLenum pname,
3667 GLfloat param) {
3668 Run<RPROC(TexParameter_base)>(texTarget, pname, FloatOrInt(param));
3671 void ClientWebGLContext::TexParameteri(GLenum texTarget, GLenum pname,
3672 GLint param) {
3673 Run<RPROC(TexParameter_base)>(texTarget, pname, FloatOrInt(param));
3676 ////////////////////////////////////
3678 static GLenum JSTypeMatchUnpackTypeError(GLenum unpackType,
3679 js::Scalar::Type jsType) {
3680 bool matches = false;
3681 switch (unpackType) {
3682 case LOCAL_GL_BYTE:
3683 matches = (jsType == js::Scalar::Type::Int8);
3684 break;
3686 case LOCAL_GL_UNSIGNED_BYTE:
3687 matches = (jsType == js::Scalar::Type::Uint8 ||
3688 jsType == js::Scalar::Type::Uint8Clamped);
3689 break;
3691 case LOCAL_GL_SHORT:
3692 matches = (jsType == js::Scalar::Type::Int16);
3693 break;
3695 case LOCAL_GL_UNSIGNED_SHORT:
3696 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
3697 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
3698 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
3699 case LOCAL_GL_HALF_FLOAT:
3700 case LOCAL_GL_HALF_FLOAT_OES:
3701 matches = (jsType == js::Scalar::Type::Uint16);
3702 break;
3704 case LOCAL_GL_INT:
3705 matches = (jsType == js::Scalar::Type::Int32);
3706 break;
3708 case LOCAL_GL_UNSIGNED_INT:
3709 case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
3710 case LOCAL_GL_UNSIGNED_INT_10F_11F_11F_REV:
3711 case LOCAL_GL_UNSIGNED_INT_5_9_9_9_REV:
3712 case LOCAL_GL_UNSIGNED_INT_24_8:
3713 matches = (jsType == js::Scalar::Type::Uint32);
3714 break;
3716 case LOCAL_GL_FLOAT:
3717 matches = (jsType == js::Scalar::Type::Float32);
3718 break;
3720 case LOCAL_GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
3721 matches = false; // No valid jsType, but we allow uploads with null.
3722 break;
3724 default:
3725 return LOCAL_GL_INVALID_ENUM;
3727 if (!matches) return LOCAL_GL_INVALID_OPERATION;
3728 return 0;
3731 static std::string ToString(const js::Scalar::Type type) {
3732 switch (type) {
3733 #define _(X) \
3734 case js::Scalar::Type::X: \
3735 return #X;
3736 _(Int8)
3737 _(Uint8)
3738 _(Uint8Clamped)
3739 _(Int16)
3740 _(Uint16)
3741 _(Int32)
3742 _(Uint32)
3743 _(Float32)
3744 #undef _
3745 default:
3746 break;
3748 MOZ_ASSERT(false);
3749 return std::string("#") + std::to_string(UnderlyingValue(type));
3752 /////////////////////////////////////////////////
3754 static inline uvec2 CastUvec2(const ivec2& val) {
3755 return {static_cast<uint32_t>(val.x), static_cast<uint32_t>(val.y)};
3758 static inline uvec3 CastUvec3(const ivec3& val) {
3759 return {static_cast<uint32_t>(val.x), static_cast<uint32_t>(val.y),
3760 static_cast<uint32_t>(val.z)};
3763 template <typename T>
3764 Range<T> SubRange(const Range<T>& full, const size_t offset,
3765 const size_t length) {
3766 const auto newBegin = full.begin() + offset;
3767 return Range<T>{newBegin, newBegin + length};
3770 static inline size_t SizeOfViewElem(const dom::ArrayBufferView& view) {
3771 const auto& elemType = view.Type();
3772 if (elemType == js::Scalar::MaxTypedArrayViewType) // DataViews.
3773 return 1;
3775 return js::Scalar::byteSize(elemType);
3778 Maybe<Range<const uint8_t>> GetRangeFromView(const dom::ArrayBufferView& view,
3779 GLuint elemOffset,
3780 GLuint elemCountOverride) {
3781 const auto byteRange = MakeRangeAbv(view); // In bytes.
3782 const auto bytesPerElem = SizeOfViewElem(view);
3784 auto elemCount = byteRange.length() / bytesPerElem;
3785 if (elemOffset > elemCount) return {};
3786 elemCount -= elemOffset;
3788 if (elemCountOverride) {
3789 if (elemCountOverride > elemCount) return {};
3790 elemCount = elemCountOverride;
3792 const auto subrange =
3793 SubRange(byteRange, elemOffset * bytesPerElem, elemCount * bytesPerElem);
3794 return Some(subrange);
3797 // -
3799 static bool IsTexTargetForDims(const GLenum texTarget, const bool webgl2,
3800 const uint8_t funcDims) {
3801 if (!IsTexTarget(texTarget, webgl2)) return false;
3802 switch (texTarget) {
3803 case LOCAL_GL_TEXTURE_2D:
3804 case LOCAL_GL_TEXTURE_CUBE_MAP:
3805 return funcDims == 2;
3807 default:
3808 return funcDims == 3;
3812 void ClientWebGLContext::TexStorage(uint8_t funcDims, GLenum texTarget,
3813 GLsizei levels, GLenum internalFormat,
3814 const ivec3& size) const {
3815 const FuncScope funcScope(*this, "texStorage[23]D");
3816 if (IsContextLost()) return;
3817 if (!IsTexTargetForDims(texTarget, mIsWebGL2, funcDims)) {
3818 EnqueueError_ArgEnum("texTarget", texTarget);
3819 return;
3821 Run<RPROC(TexStorage)>(texTarget, static_cast<uint32_t>(levels),
3822 internalFormat, CastUvec3(size));
3825 namespace webgl {
3826 // TODO: Move these definitions into statics here.
3827 Maybe<webgl::TexUnpackBlobDesc> FromImageBitmap(
3828 GLenum target, uvec3 size, const dom::ImageBitmap& imageBitmap,
3829 ErrorResult* const out_rv);
3831 webgl::TexUnpackBlobDesc FromImageData(GLenum target, uvec3 size,
3832 const dom::ImageData& imageData,
3833 dom::Uint8ClampedArray* const scopedArr);
3835 Maybe<webgl::TexUnpackBlobDesc> FromDomElem(const ClientWebGLContext&,
3836 GLenum target, uvec3 size,
3837 const dom::Element& src,
3838 ErrorResult* const out_error);
3839 } // namespace webgl
3841 void webgl::TexUnpackBlobDesc::Shrink(const webgl::PackingInfo& pi) {
3842 if (cpuData) {
3843 if (!size.x || !size.y || !size.z) return;
3844 MOZ_ASSERT(unpacking.mUnpackRowLength);
3845 MOZ_ASSERT(unpacking.mUnpackImageHeight);
3847 uint8_t bpp = 0;
3848 if (!GetBytesPerPixel(pi, &bpp)) return;
3850 const auto bytesPerRowUnaligned =
3851 CheckedInt<size_t>(unpacking.mUnpackRowLength) * bpp;
3852 const auto bytesPerRowStride =
3853 RoundUpToMultipleOf(bytesPerRowUnaligned, unpacking.mUnpackAlignment);
3855 const auto bytesPerImageStride =
3856 bytesPerRowStride * unpacking.mUnpackImageHeight;
3857 // SKIP_IMAGES is ignored for 2D targets, but at worst this just makes our
3858 // shrinking less accurate.
3859 const auto images =
3860 CheckedInt<size_t>(unpacking.mUnpackSkipImages) + size.z;
3861 const auto bytesUpperBound = bytesPerImageStride * images;
3863 if (bytesUpperBound.isValid()) {
3864 cpuData->Shrink(bytesUpperBound.value());
3866 return;
3870 void ClientWebGLContext::TexImage(uint8_t funcDims, GLenum imageTarget,
3871 GLint level, GLenum respecFormat,
3872 const ivec3& offset, const ivec3& isize,
3873 GLint border, const webgl::PackingInfo& pi,
3874 const TexImageSource& src) const {
3875 const FuncScope funcScope(*this, "tex(Sub)Image[23]D");
3876 if (IsContextLost()) return;
3877 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
3878 EnqueueError_ArgEnum("imageTarget", imageTarget);
3879 return;
3881 if (border != 0) {
3882 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
3883 return;
3885 const auto explicitSize = CastUvec3(isize);
3887 // -
3889 // Demarcate the region within which GC is disallowed. Typed arrays can move
3890 // their data during a GC, so this will allow the rooting hazard analysis to
3891 // report if a GC is possible while any data pointers extracted from the
3892 // typed array are still live.
3893 dom::Uint8ClampedArray scopedArr;
3895 // -
3896 bool isDataUpload = false;
3897 auto desc = [&]() -> Maybe<webgl::TexUnpackBlobDesc> {
3898 if (src.mPboOffset) {
3899 isDataUpload = true;
3900 const auto offset = static_cast<uint64_t>(*src.mPboOffset);
3901 return Some(webgl::TexUnpackBlobDesc{imageTarget,
3902 explicitSize,
3903 gfxAlphaType::NonPremult,
3905 Some(offset)});
3908 if (src.mView) {
3909 isDataUpload = true;
3910 const auto& view = *src.mView;
3911 const auto& jsType = view.Type();
3912 const auto err = JSTypeMatchUnpackTypeError(pi.type, jsType);
3913 switch (err) {
3914 case LOCAL_GL_INVALID_ENUM:
3915 EnqueueError_ArgEnum("unpackType", pi.type);
3916 return {};
3917 case LOCAL_GL_INVALID_OPERATION:
3918 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3919 "ArrayBufferView type %s not compatible with `type` %s.",
3920 ToString(jsType).c_str(), EnumString(pi.type).c_str());
3921 return {};
3922 default:
3923 break;
3926 const auto range = GetRangeFromView(view, src.mViewElemOffset,
3927 src.mViewElemLengthOverride);
3928 if (!range) {
3929 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`source` too small.");
3930 return {};
3932 return Some(webgl::TexUnpackBlobDesc{imageTarget,
3933 explicitSize,
3934 gfxAlphaType::NonPremult,
3935 Some(RawBuffer<>{*range}),
3936 {}});
3939 if (src.mImageBitmap) {
3940 return webgl::FromImageBitmap(imageTarget, explicitSize,
3941 *(src.mImageBitmap), src.mOut_error);
3944 if (src.mImageData) {
3945 return Some(webgl::FromImageData(imageTarget, explicitSize,
3946 *(src.mImageData), &scopedArr));
3949 if (src.mDomElem) {
3950 return webgl::FromDomElem(*this, imageTarget, explicitSize,
3951 *(src.mDomElem), src.mOut_error);
3954 return Some(webgl::TexUnpackBlobDesc{
3955 imageTarget, explicitSize, gfxAlphaType::NonPremult, {}, {}});
3956 }();
3957 if (!desc) {
3958 scopedArr.Reset();
3959 return;
3962 // -
3963 // Further, for uploads from TexImageSource, implied UNPACK_ROW_LENGTH and
3964 // UNPACK_ALIGNMENT are not strictly defined. These restrictions ensure
3965 // consistent and efficient behavior regardless of implied UNPACK_ params.
3967 Maybe<uvec2> structuredSrcSize;
3968 if (desc->dataSurf || desc->sd) {
3969 structuredSrcSize = Some(desc->imageSize);
3971 if (structuredSrcSize) {
3972 auto& size = desc->size;
3973 if (!size.x) {
3974 size.x = structuredSrcSize->x;
3976 if (!size.y) {
3977 size.y = structuredSrcSize->x;
3981 const auto& rawUnpacking = State().mPixelUnpackState;
3982 const bool isSubrect =
3983 (rawUnpacking.mUnpackImageHeight || rawUnpacking.mUnpackSkipImages ||
3984 rawUnpacking.mUnpackRowLength || rawUnpacking.mUnpackSkipRows ||
3985 rawUnpacking.mUnpackSkipPixels);
3986 if (isDataUpload && isSubrect) {
3987 if (rawUnpacking.mFlipY || rawUnpacking.mPremultiplyAlpha) {
3988 EnqueueError(LOCAL_GL_INVALID_OPERATION,
3989 "Non-DOM-Element uploads with alpha-premult"
3990 " or y-flip do not support subrect selection.");
3991 scopedArr.Reset(); // (For the hazard analysis) Done with the data.
3992 return;
3995 desc->unpacking =
3996 rawUnpacking.ForUseWith(desc->imageTarget, desc->size, structuredSrcSize);
3998 // -
4000 if (desc->sd) {
4001 auto fallbackReason = BlitPreventReason(level, offset, pi, *desc);
4003 const auto& sd = *(desc->sd);
4004 const auto sdType = sd.type();
4005 const auto& contextInfo = mNotLost->info;
4007 const bool canUploadViaSd = contextInfo.uploadableSdTypes[sdType];
4008 if (!canUploadViaSd) {
4009 const nsPrintfCString msg(
4010 "Fast uploads for resource type %i not implemented.", int(sdType));
4011 fallbackReason.reset();
4012 fallbackReason.emplace(ToString(msg));
4015 if (StaticPrefs::webgl_disable_DOM_blit_uploads()) {
4016 fallbackReason.reset();
4017 fallbackReason.emplace("DOM blit uploads are disabled.");
4020 if (fallbackReason) {
4021 EnqueuePerfWarning("Missed GPU-copy fast-path: %s",
4022 fallbackReason->c_str());
4024 const auto& image = desc->image;
4025 const RefPtr<gfx::SourceSurface> surf = image->GetAsSourceSurface();
4026 if (surf) {
4027 // WARNING: OSX can lose our MakeCurrent here.
4028 desc->dataSurf = surf->GetDataSurface();
4030 if (!desc->dataSurf) {
4031 EnqueueError(LOCAL_GL_OUT_OF_MEMORY,
4032 "Failed to retrieve source bytes for CPU upload.");
4033 return;
4035 desc->sd = Nothing();
4038 desc->image = nullptr;
4040 // -
4042 desc->Shrink(pi);
4044 Run<RPROC(TexImage)>(static_cast<uint32_t>(level), respecFormat,
4045 CastUvec3(offset), pi, std::move(*desc));
4046 scopedArr.Reset(); // (For the hazard analysis) Done with the data.
4049 void ClientWebGLContext::CompressedTexImage(bool sub, uint8_t funcDims,
4050 GLenum imageTarget, GLint level,
4051 GLenum format, const ivec3& offset,
4052 const ivec3& isize, GLint border,
4053 const TexImageSource& src,
4054 GLsizei pboImageSize) const {
4055 const FuncScope funcScope(*this, "compressedTex(Sub)Image[23]D");
4056 if (IsContextLost()) return;
4057 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
4058 EnqueueError_ArgEnum("imageTarget", imageTarget);
4059 return;
4061 if (border != 0) {
4062 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
4063 return;
4066 RawBuffer<> range;
4067 Maybe<uint64_t> pboOffset;
4068 if (src.mView) {
4069 const auto maybe = GetRangeFromView(*src.mView, src.mViewElemOffset,
4070 src.mViewElemLengthOverride);
4071 if (!maybe) {
4072 EnqueueError(LOCAL_GL_INVALID_VALUE, "`source` too small.");
4073 return;
4075 range = RawBuffer<>{*maybe};
4076 } else if (src.mPboOffset) {
4077 if (!ValidateNonNegative("offset", *src.mPboOffset)) return;
4078 pboOffset = Some(*src.mPboOffset);
4079 } else {
4080 MOZ_CRASH("impossible");
4083 // We don't need to shrink `range` because valid calls require `range` to
4084 // match requirements exactly.
4086 Run<RPROC(CompressedTexImage)>(
4087 sub, imageTarget, static_cast<uint32_t>(level), format, CastUvec3(offset),
4088 CastUvec3(isize), range, static_cast<uint32_t>(pboImageSize), pboOffset);
4091 void ClientWebGLContext::CopyTexImage(uint8_t funcDims, GLenum imageTarget,
4092 GLint level, GLenum respecFormat,
4093 const ivec3& dstOffset,
4094 const ivec2& srcOffset, const ivec2& size,
4095 GLint border) const {
4096 const FuncScope funcScope(*this, "copy(Sub)Image[23]D");
4097 if (IsContextLost()) return;
4098 if (!IsTexTargetForDims(ImageToTexTarget(imageTarget), mIsWebGL2, funcDims)) {
4099 EnqueueError_ArgEnum("imageTarget", imageTarget);
4100 return;
4102 if (border != 0) {
4103 EnqueueError(LOCAL_GL_INVALID_VALUE, "`border` must be 0.");
4104 return;
4106 Run<RPROC(CopyTexImage)>(imageTarget, static_cast<uint32_t>(level),
4107 respecFormat, CastUvec3(dstOffset), srcOffset,
4108 CastUvec2(size));
4111 // ------------------- Programs and shaders --------------------------------
4113 void ClientWebGLContext::UseProgram(WebGLProgramJS* const prog) {
4114 const FuncScope funcScope(*this, "useProgram");
4115 if (IsContextLost()) return;
4116 if (prog && !prog->ValidateUsable(*this, "prog")) return;
4118 auto& state = State();
4120 if (state.mTfActiveAndNotPaused) {
4121 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4122 "Transform feedback is active and not paused.");
4123 return;
4126 if (prog) {
4127 const auto& res = GetLinkResult(*prog);
4128 if (!res.success) {
4129 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4130 "Program must be linked successfully.");
4131 return;
4135 // -
4137 state.mCurrentProgram = prog;
4138 state.mProgramKeepAlive = prog ? prog->mKeepAliveWeak.lock() : nullptr;
4139 state.mActiveLinkResult = prog ? prog->mResult : nullptr;
4141 Run<RPROC(UseProgram)>(prog ? prog->mId : 0);
4144 void ClientWebGLContext::ValidateProgram(WebGLProgramJS& prog) const {
4145 const FuncScope funcScope(*this, "validateProgram");
4146 if (IsContextLost()) return;
4147 if (!prog.ValidateUsable(*this, "prog")) return;
4149 prog.mLastValidate = [&]() {
4150 const auto& inProcess = mNotLost->inProcess;
4151 if (inProcess) {
4152 return inProcess->ValidateProgram(prog.mId);
4154 const auto& child = mNotLost->outOfProcess;
4155 child->FlushPendingCmds();
4156 bool ret = {};
4157 if (!child->SendValidateProgram(prog.mId, &ret)) {
4158 ret = {};
4160 return ret;
4161 }();
4164 // ------------------------ Uniforms and attributes ------------------------
4166 Maybe<double> ClientWebGLContext::GetVertexAttribPriv(const GLuint index,
4167 const GLenum pname) {
4168 const auto& inProcess = mNotLost->inProcess;
4169 if (inProcess) {
4170 return inProcess->GetVertexAttrib(index, pname);
4172 const auto& child = mNotLost->outOfProcess;
4173 child->FlushPendingCmds();
4174 Maybe<double> ret;
4175 if (!child->SendGetVertexAttrib(index, pname, &ret)) {
4176 ret.reset();
4178 return ret;
4181 void ClientWebGLContext::GetVertexAttrib(JSContext* cx, GLuint index,
4182 GLenum pname,
4183 JS::MutableHandle<JS::Value> retval,
4184 ErrorResult& rv) {
4185 retval.set(JS::NullValue());
4186 const FuncScope funcScope(*this, "getVertexAttrib");
4187 if (IsContextLost()) return;
4188 const auto& state = State();
4190 const auto& genericAttribs = state.mGenericVertexAttribs;
4191 if (index >= genericAttribs.size()) {
4192 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` (%u) >= MAX_VERTEX_ATTRIBS",
4193 index);
4194 return;
4197 switch (pname) {
4198 case LOCAL_GL_CURRENT_VERTEX_ATTRIB: {
4199 JS::RootedObject obj(cx);
4201 const auto& attrib = genericAttribs[index];
4202 switch (attrib.type) {
4203 case webgl::AttribBaseType::Float:
4204 obj = dom::Float32Array::Create(
4205 cx, this, 4, reinterpret_cast<const float*>(attrib.data));
4206 break;
4207 case webgl::AttribBaseType::Int:
4208 obj = dom::Int32Array::Create(
4209 cx, this, 4, reinterpret_cast<const int32_t*>(attrib.data));
4210 break;
4211 case webgl::AttribBaseType::Uint:
4212 obj = dom::Uint32Array::Create(
4213 cx, this, 4, reinterpret_cast<const uint32_t*>(attrib.data));
4214 break;
4215 case webgl::AttribBaseType::Boolean:
4216 MOZ_CRASH("impossible");
4219 if (!obj) {
4220 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
4221 return;
4223 retval.set(JS::ObjectValue(*obj));
4224 return;
4227 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: {
4228 const auto& buffers = state.mBoundVao->mAttribBuffers;
4229 const auto& buffer = buffers[index];
4230 (void)ToJSValueOrNull(cx, buffer, retval);
4231 return;
4234 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER:
4235 // Disallowed from JS, but allowed in Host.
4236 EnqueueError_ArgEnum("pname", pname);
4237 return;
4239 default:
4240 break;
4243 const auto maybe = GetVertexAttribPriv(index, pname);
4244 if (maybe) {
4245 switch (pname) {
4246 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_ENABLED:
4247 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_NORMALIZED:
4248 case LOCAL_GL_VERTEX_ATTRIB_ARRAY_INTEGER:
4249 retval.set(JS::BooleanValue(*maybe));
4250 break;
4252 default:
4253 retval.set(JS::NumberValue(*maybe));
4254 break;
4259 void ClientWebGLContext::UniformData(const GLenum funcElemType,
4260 const WebGLUniformLocationJS* const loc,
4261 bool transpose,
4262 const Range<const uint8_t>& bytes,
4263 GLuint elemOffset,
4264 GLuint elemCountOverride) const {
4265 const FuncScope funcScope(*this, "uniform setter");
4266 if (IsContextLost()) return;
4268 const auto& activeLinkResult = GetActiveLinkResult();
4269 if (!activeLinkResult) {
4270 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No active linked Program.");
4271 return;
4274 // -
4276 auto availCount = bytes.length() / sizeof(float);
4277 if (elemOffset > availCount) {
4278 EnqueueError(LOCAL_GL_INVALID_VALUE, "`elemOffset` too large for `data`.");
4279 return;
4281 availCount -= elemOffset;
4282 if (elemCountOverride) {
4283 if (elemCountOverride > availCount) {
4284 EnqueueError(LOCAL_GL_INVALID_VALUE,
4285 "`elemCountOverride` too large for `data`.");
4286 return;
4288 availCount = elemCountOverride;
4291 // -
4293 const auto channels = ElemTypeComponents(funcElemType);
4294 if (!availCount || availCount % channels != 0) {
4295 EnqueueError(LOCAL_GL_INVALID_VALUE,
4296 "`values` length (%u) must be a positive "
4297 "integer multiple of size of %s.",
4298 availCount, EnumString(funcElemType).c_str());
4299 return;
4302 // -
4304 uint32_t locId = -1;
4305 if (MOZ_LIKELY(loc)) {
4306 locId = loc->mLocation;
4307 if (!loc->ValidateUsable(*this, "location")) return;
4309 // -
4311 const auto& reqLinkInfo = loc->mParent.lock();
4312 if (reqLinkInfo.get() != activeLinkResult) {
4313 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4314 "UniformLocation is not from the current active Program.");
4315 return;
4318 // -
4320 bool funcMatchesLocation = false;
4321 for (const auto allowed : loc->mValidUploadElemTypes) {
4322 funcMatchesLocation |= (funcElemType == allowed);
4324 if (MOZ_UNLIKELY(!funcMatchesLocation)) {
4325 std::string validSetters;
4326 for (const auto allowed : loc->mValidUploadElemTypes) {
4327 validSetters += EnumString(allowed);
4328 validSetters += '/';
4330 validSetters.pop_back(); // Cheekily discard the extra trailing '/'.
4332 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4333 "Uniform's `type` requires uniform setter of type %s.",
4334 validSetters.c_str());
4335 return;
4339 // -
4341 const auto ptr = bytes.begin().get() + (elemOffset * sizeof(float));
4342 const auto range = Range<const uint8_t>{ptr, availCount * sizeof(float)};
4343 Run<RPROC(UniformData)>(locId, transpose, RawBuffer<>(range));
4346 // -
4348 void ClientWebGLContext::BindVertexArray(WebGLVertexArrayJS* const vao) {
4349 const FuncScope funcScope(*this, "bindVertexArray");
4350 if (IsContextLost()) return;
4351 if (vao && !vao->ValidateUsable(*this, "vao")) return;
4352 auto& state = State();
4354 if (vao) {
4355 vao->mHasBeenBound = true;
4356 state.mBoundVao = vao;
4357 } else {
4358 state.mBoundVao = state.mDefaultVao;
4361 Run<RPROC(BindVertexArray)>(vao ? vao->mId : 0);
4364 void ClientWebGLContext::EnableVertexAttribArray(GLuint index) {
4365 Run<RPROC(EnableVertexAttribArray)>(index);
4368 void ClientWebGLContext::DisableVertexAttribArray(GLuint index) {
4369 Run<RPROC(DisableVertexAttribArray)>(index);
4372 WebGLsizeiptr ClientWebGLContext::GetVertexAttribOffset(GLuint index,
4373 GLenum pname) {
4374 const FuncScope funcScope(*this, "getVertexAttribOffset");
4375 if (IsContextLost()) return 0;
4377 if (pname != LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER) {
4378 EnqueueError_ArgEnum("pname", pname);
4379 return 0;
4382 const auto maybe = GetVertexAttribPriv(index, pname);
4383 if (!maybe) return 0;
4384 return *maybe;
4387 void ClientWebGLContext::VertexAttrib4Tv(GLuint index, webgl::AttribBaseType t,
4388 const Range<const uint8_t>& src) {
4389 const FuncScope funcScope(*this, "vertexAttrib[1234]u?[fi]{v}");
4390 if (IsContextLost()) return;
4391 auto& state = State();
4393 if (src.length() / sizeof(float) < 4) {
4394 EnqueueError(LOCAL_GL_INVALID_VALUE, "Array must have >=4 elements.");
4395 return;
4398 auto& list = state.mGenericVertexAttribs;
4399 if (index >= list.size()) {
4400 EnqueueError(LOCAL_GL_INVALID_VALUE,
4401 "`index` must be < MAX_VERTEX_ATTRIBS.");
4402 return;
4405 auto& attrib = list[index];
4406 attrib.type = t;
4407 memcpy(attrib.data, src.begin().get(), sizeof(attrib.data));
4409 Run<RPROC(VertexAttrib4T)>(index, attrib);
4412 // -
4414 void ClientWebGLContext::VertexAttribDivisor(GLuint index, GLuint divisor) {
4415 Run<RPROC(VertexAttribDivisor)>(index, divisor);
4418 // -
4420 void ClientWebGLContext::VertexAttribPointerImpl(bool isFuncInt, GLuint index,
4421 GLint rawChannels, GLenum type,
4422 bool normalized,
4423 GLsizei rawByteStrideOrZero,
4424 WebGLintptr rawByteOffset) {
4425 const FuncScope funcScope(*this, "vertexAttribI?Pointer");
4426 if (IsContextLost()) return;
4427 auto& state = State();
4429 const auto channels = MaybeAs<uint8_t>(rawChannels);
4430 if (!channels) {
4431 EnqueueError(LOCAL_GL_INVALID_VALUE,
4432 "Channel count `size` must be within [1,4].");
4433 return;
4436 const auto byteStrideOrZero = MaybeAs<uint8_t>(rawByteStrideOrZero);
4437 if (!byteStrideOrZero) {
4438 EnqueueError(LOCAL_GL_INVALID_VALUE, "`stride` must be within [0,255].");
4439 return;
4442 if (!ValidateNonNegative("byteOffset", rawByteOffset)) return;
4443 const auto byteOffset = static_cast<uint64_t>(rawByteOffset);
4445 // -
4447 const webgl::VertAttribPointerDesc desc{
4448 isFuncInt, *channels, normalized, *byteStrideOrZero, type, byteOffset};
4450 const auto res = CheckVertexAttribPointer(mIsWebGL2, desc);
4451 if (res.isErr()) {
4452 const auto& err = res.inspectErr();
4453 EnqueueError(err.type, "%s", err.info.c_str());
4454 return;
4457 auto& list = state.mBoundVao->mAttribBuffers;
4458 if (index >= list.size()) {
4459 EnqueueError(LOCAL_GL_INVALID_VALUE,
4460 "`index` (%u) must be < MAX_VERTEX_ATTRIBS.", index);
4461 return;
4464 const auto buffer = state.mBoundBufferByTarget[LOCAL_GL_ARRAY_BUFFER];
4465 if (!buffer && byteOffset) {
4466 return EnqueueError(LOCAL_GL_INVALID_OPERATION,
4467 "If ARRAY_BUFFER is null, byteOffset must be zero.");
4470 Run<RPROC(VertexAttribPointer)>(index, desc);
4472 list[index] = buffer;
4475 // -------------------------------- Drawing -------------------------------
4477 void ClientWebGLContext::DrawArraysInstanced(GLenum mode, GLint first,
4478 GLsizei count, GLsizei primcount,
4479 FuncScopeId) {
4480 Run<RPROC(DrawArraysInstanced)>(mode, first, count, primcount);
4481 AfterDrawCall();
4484 void ClientWebGLContext::DrawElementsInstanced(GLenum mode, GLsizei count,
4485 GLenum type, WebGLintptr offset,
4486 GLsizei primcount, FuncScopeId) {
4487 Run<RPROC(DrawElementsInstanced)>(mode, count, type, offset, primcount);
4488 AfterDrawCall();
4491 // ------------------------------ Readback -------------------------------
4492 void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
4493 GLsizei height, GLenum format, GLenum type,
4494 WebGLsizeiptr offset,
4495 dom::CallerType aCallerType,
4496 ErrorResult& out_error) const {
4497 const FuncScope funcScope(*this, "readPixels");
4498 if (!ReadPixels_SharedPrecheck(aCallerType, out_error)) return;
4499 const auto& state = State();
4500 if (!ValidateNonNegative("width", width)) return;
4501 if (!ValidateNonNegative("height", height)) return;
4502 if (!ValidateNonNegative("offset", offset)) return;
4504 const auto desc = webgl::ReadPixelsDesc{{x, y},
4505 *uvec2::From(width, height),
4506 {format, type},
4507 state.mPixelPackState};
4508 Run<RPROC(ReadPixelsPbo)>(desc, static_cast<uint64_t>(offset));
4511 void ClientWebGLContext::ReadPixels(GLint x, GLint y, GLsizei width,
4512 GLsizei height, GLenum format, GLenum type,
4513 const dom::ArrayBufferView& dstData,
4514 GLuint dstElemOffset,
4515 dom::CallerType aCallerType,
4516 ErrorResult& out_error) const {
4517 const FuncScope funcScope(*this, "readPixels");
4518 if (!ReadPixels_SharedPrecheck(aCallerType, out_error)) return;
4519 const auto& state = State();
4520 if (!ValidateNonNegative("width", width)) return;
4521 if (!ValidateNonNegative("height", height)) return;
4523 ////
4525 js::Scalar::Type reqScalarType;
4526 if (!GetJSScalarFromGLType(type, &reqScalarType)) {
4527 nsCString name;
4528 WebGLContext::EnumName(type, &name);
4529 EnqueueError(LOCAL_GL_INVALID_ENUM, "type: invalid enum value %s",
4530 name.BeginReading());
4531 return;
4534 auto viewElemType = dstData.Type();
4535 if (viewElemType == js::Scalar::Uint8Clamped) {
4536 viewElemType = js::Scalar::Uint8;
4538 if (viewElemType != reqScalarType) {
4539 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4540 "`pixels` type does not match `type`.");
4541 return;
4544 uint8_t* bytes;
4545 size_t byteLen;
4546 if (!ValidateArrayBufferView(dstData, dstElemOffset, 0,
4547 LOCAL_GL_INVALID_VALUE, &bytes, &byteLen)) {
4548 return;
4551 const auto desc = webgl::ReadPixelsDesc{{x, y},
4552 *uvec2::From(width, height),
4553 {format, type},
4554 state.mPixelPackState};
4555 const auto range = Range<uint8_t>(bytes, byteLen);
4556 DoReadPixels(desc, range);
4559 void ClientWebGLContext::DoReadPixels(const webgl::ReadPixelsDesc& desc,
4560 const Range<uint8_t> dest) const {
4561 const auto notLost =
4562 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
4563 if (!notLost) return;
4564 const auto& inProcess = notLost->inProcess;
4565 if (inProcess) {
4566 inProcess->ReadPixelsInto(desc, dest);
4567 return;
4569 const auto& child = notLost->outOfProcess;
4570 child->FlushPendingCmds();
4571 webgl::ReadPixelsResultIpc res = {};
4572 if (!child->SendReadPixels(desc, dest.length(), &res)) {
4573 res = {};
4575 if (!res.byteStride) return;
4576 const auto& byteStride = res.byteStride;
4577 const auto& subrect = res.subrect;
4578 const webgl::RaiiShmem shmem{child, res.shmem};
4579 const auto& shmemBytes = shmem.ByteRange();
4581 uint8_t bpp;
4582 if (!GetBytesPerPixel(desc.pi, &bpp)) {
4583 MOZ_ASSERT(false);
4584 return;
4587 const auto& packing = desc.packState;
4588 auto packRect = *uvec2::From(subrect.x, subrect.y);
4589 packRect.x += packing.skipPixels;
4590 packRect.y += packing.skipRows;
4592 const auto xByteSize = bpp * static_cast<uint32_t>(subrect.width);
4593 const ptrdiff_t byteOffset = packRect.y * byteStride + packRect.x * bpp;
4595 auto srcItr = shmemBytes.begin() + byteOffset;
4596 auto destItr = dest.begin() + byteOffset;
4598 for (const auto i : IntegerRange(subrect.height)) {
4599 if (i) {
4600 // Don't trigger an assert on the last loop by pushing a RangedPtr past
4601 // its bounds.
4602 srcItr += byteStride;
4603 destItr += byteStride;
4604 MOZ_RELEASE_ASSERT(srcItr + xByteSize <= shmemBytes.end());
4605 MOZ_RELEASE_ASSERT(destItr + xByteSize <= dest.end());
4607 Memcpy(destItr, srcItr, xByteSize);
4611 bool ClientWebGLContext::ReadPixels_SharedPrecheck(
4612 dom::CallerType aCallerType, ErrorResult& out_error) const {
4613 if (IsContextLost()) return false;
4615 if (mCanvasElement && mCanvasElement->IsWriteOnly() &&
4616 aCallerType != dom::CallerType::System) {
4617 JsWarning("readPixels: Not allowed");
4618 out_error.Throw(NS_ERROR_DOM_SECURITY_ERR);
4619 return false;
4622 return true;
4625 // --------------------------------- GL Query ---------------------------------
4627 static inline GLenum QuerySlotTarget(const GLenum specificTarget) {
4628 if (specificTarget == LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
4629 return LOCAL_GL_ANY_SAMPLES_PASSED;
4631 return specificTarget;
4634 void ClientWebGLContext::GetQuery(JSContext* cx, GLenum specificTarget,
4635 GLenum pname,
4636 JS::MutableHandle<JS::Value> retval) const {
4637 retval.set(JS::NullValue());
4638 const FuncScope funcScope(*this, "getQuery");
4639 if (IsContextLost()) return;
4640 const auto& limits = Limits();
4641 auto& state = State();
4643 if (IsExtensionEnabled(WebGLExtensionID::EXT_disjoint_timer_query)) {
4644 if (pname == LOCAL_GL_QUERY_COUNTER_BITS) {
4645 switch (specificTarget) {
4646 case LOCAL_GL_TIME_ELAPSED_EXT:
4647 retval.set(JS::NumberValue(limits.queryCounterBitsTimeElapsed));
4648 return;
4650 case LOCAL_GL_TIMESTAMP_EXT:
4651 retval.set(JS::NumberValue(limits.queryCounterBitsTimestamp));
4652 return;
4654 default:
4655 EnqueueError_ArgEnum("target", specificTarget);
4656 return;
4661 if (pname != LOCAL_GL_CURRENT_QUERY) {
4662 EnqueueError_ArgEnum("pname", pname);
4663 return;
4666 const auto slotTarget = QuerySlotTarget(specificTarget);
4667 const auto& slot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
4668 if (!slot) {
4669 EnqueueError_ArgEnum("target", specificTarget);
4670 return;
4673 auto query = *slot;
4674 if (query && query->mTarget != specificTarget) {
4675 query = nullptr;
4678 (void)ToJSValueOrNull(cx, query, retval);
4681 void ClientWebGLContext::GetQueryParameter(
4682 JSContext*, WebGLQueryJS& query, const GLenum pname,
4683 JS::MutableHandle<JS::Value> retval) const {
4684 retval.set(JS::NullValue());
4685 const FuncScope funcScope(*this, "getQueryParameter");
4686 if (IsContextLost()) return;
4687 if (!query.ValidateUsable(*this, "query")) return;
4689 auto maybe = [&]() {
4690 const auto& inProcess = mNotLost->inProcess;
4691 if (inProcess) {
4692 return inProcess->GetQueryParameter(query.mId, pname);
4694 const auto& child = mNotLost->outOfProcess;
4695 child->FlushPendingCmds();
4696 Maybe<double> ret;
4697 if (!child->SendGetQueryParameter(query.mId, pname, &ret)) {
4698 ret.reset();
4700 return ret;
4701 }();
4702 if (!maybe) return;
4704 // We must usually wait for an event loop before the query can be available.
4705 const bool canBeAvailable =
4706 (query.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
4707 if (!canBeAvailable) {
4708 if (pname != LOCAL_GL_QUERY_RESULT_AVAILABLE) {
4709 return;
4711 maybe = Some(0.0);
4714 switch (pname) {
4715 case LOCAL_GL_QUERY_RESULT_AVAILABLE:
4716 retval.set(JS::BooleanValue(*maybe));
4717 break;
4719 default:
4720 retval.set(JS::NumberValue(*maybe));
4721 break;
4725 void ClientWebGLContext::BeginQuery(const GLenum specificTarget,
4726 WebGLQueryJS& query) {
4727 const FuncScope funcScope(*this, "beginQuery");
4728 if (IsContextLost()) return;
4729 if (!query.ValidateUsable(*this, "query")) return;
4730 auto& state = State();
4732 const auto slotTarget = QuerySlotTarget(specificTarget);
4733 const auto& slot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
4734 if (!slot) {
4735 EnqueueError_ArgEnum("target", specificTarget);
4736 return;
4739 if (*slot) {
4740 auto enumStr = EnumString(slotTarget);
4741 if (slotTarget == LOCAL_GL_ANY_SAMPLES_PASSED) {
4742 enumStr += "/ANY_SAMPLES_PASSED_CONSERVATIVE";
4744 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4745 "A Query is already active for %s.", enumStr.c_str());
4746 return;
4749 if (query.mTarget && query.mTarget != specificTarget) {
4750 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4751 "`query` cannot be changed to a different target.");
4752 return;
4755 *slot = &query;
4756 query.mTarget = specificTarget;
4758 Run<RPROC(BeginQuery)>(specificTarget, query.mId);
4761 void ClientWebGLContext::EndQuery(const GLenum specificTarget) {
4762 const FuncScope funcScope(*this, "endQuery");
4763 if (IsContextLost()) return;
4764 auto& state = State();
4766 const auto slotTarget = QuerySlotTarget(specificTarget);
4767 const auto& maybeSlot = MaybeFind(state.mCurrentQueryByTarget, slotTarget);
4768 if (!maybeSlot) {
4769 EnqueueError_ArgEnum("target", specificTarget);
4770 return;
4772 auto& slot = *maybeSlot;
4773 if (!slot || slot->mTarget != specificTarget) {
4774 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No Query is active for %s.",
4775 EnumString(specificTarget).c_str());
4776 return;
4778 const auto query = slot;
4779 slot = nullptr;
4781 Run<RPROC(EndQuery)>(specificTarget);
4783 auto& availRunnable = EnsureAvailabilityRunnable();
4784 availRunnable.mQueries.push_back(query.get());
4785 query->mCanBeAvailable = false;
4788 void ClientWebGLContext::QueryCounter(WebGLQueryJS& query,
4789 const GLenum target) const {
4790 const FuncScope funcScope(*this, "queryCounter");
4791 if (IsContextLost()) return;
4792 if (!query.ValidateUsable(*this, "query")) return;
4794 if (target != LOCAL_GL_TIMESTAMP) {
4795 EnqueueError(LOCAL_GL_INVALID_ENUM, "`target` must be TIMESTAMP.");
4796 return;
4799 if (query.mTarget && query.mTarget != target) {
4800 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4801 "`query` cannot be changed to a different target.");
4802 return;
4804 query.mTarget = target;
4806 Run<RPROC(QueryCounter)>(query.mId);
4808 auto& availRunnable = EnsureAvailabilityRunnable();
4809 availRunnable.mQueries.push_back(&query);
4810 query.mCanBeAvailable = false;
4813 // -------------------------------- Sampler -------------------------------
4814 void ClientWebGLContext::GetSamplerParameter(
4815 JSContext* cx, const WebGLSamplerJS& sampler, const GLenum pname,
4816 JS::MutableHandle<JS::Value> retval) const {
4817 retval.set(JS::NullValue());
4818 const FuncScope funcScope(*this, "getSamplerParameter");
4819 if (IsContextLost()) return;
4820 if (!sampler.ValidateUsable(*this, "sampler")) return;
4822 const auto maybe = [&]() {
4823 const auto& inProcess = mNotLost->inProcess;
4824 if (inProcess) {
4825 return inProcess->GetSamplerParameter(sampler.mId, pname);
4827 const auto& child = mNotLost->outOfProcess;
4828 child->FlushPendingCmds();
4829 Maybe<double> ret;
4830 if (!child->SendGetSamplerParameter(sampler.mId, pname, &ret)) {
4831 ret.reset();
4833 return ret;
4834 }();
4835 if (maybe) {
4836 retval.set(JS::NumberValue(*maybe));
4840 void ClientWebGLContext::BindSampler(const GLuint unit,
4841 WebGLSamplerJS* const sampler) {
4842 const FuncScope funcScope(*this, "bindSampler");
4843 if (IsContextLost()) return;
4844 if (sampler && !sampler->ValidateUsable(*this, "sampler")) return;
4845 auto& state = State();
4847 auto& texUnits = state.mTexUnits;
4848 if (unit >= texUnits.size()) {
4849 EnqueueError(LOCAL_GL_INVALID_VALUE, "`unit` (%u) larger than %zu.", unit,
4850 texUnits.size());
4851 return;
4854 // -
4856 texUnits[unit].sampler = sampler;
4858 Run<RPROC(BindSampler)>(unit, sampler ? sampler->mId : 0);
4861 void ClientWebGLContext::SamplerParameteri(WebGLSamplerJS& sampler,
4862 const GLenum pname,
4863 const GLint param) const {
4864 const FuncScope funcScope(*this, "samplerParameteri");
4865 if (IsContextLost()) return;
4866 if (!sampler.ValidateUsable(*this, "sampler")) return;
4868 Run<RPROC(SamplerParameteri)>(sampler.mId, pname, param);
4871 void ClientWebGLContext::SamplerParameterf(WebGLSamplerJS& sampler,
4872 const GLenum pname,
4873 const GLfloat param) const {
4874 const FuncScope funcScope(*this, "samplerParameterf");
4875 if (IsContextLost()) return;
4876 if (!sampler.ValidateUsable(*this, "sampler")) return;
4878 Run<RPROC(SamplerParameterf)>(sampler.mId, pname, param);
4881 // ------------------------------- GL Sync ---------------------------------
4883 void ClientWebGLContext::GetSyncParameter(
4884 JSContext* const cx, WebGLSyncJS& sync, const GLenum pname,
4885 JS::MutableHandle<JS::Value> retval) const {
4886 retval.set(JS::NullValue());
4887 const FuncScope funcScope(*this, "getSyncParameter");
4888 if (IsContextLost()) return;
4889 if (!sync.ValidateUsable(*this, "sync")) return;
4891 retval.set([&]() -> JS::Value {
4892 switch (pname) {
4893 case LOCAL_GL_OBJECT_TYPE:
4894 return JS::NumberValue(LOCAL_GL_SYNC_FENCE);
4895 case LOCAL_GL_SYNC_CONDITION:
4896 return JS::NumberValue(LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE);
4897 case LOCAL_GL_SYNC_FLAGS:
4898 return JS::NumberValue(0);
4899 case LOCAL_GL_SYNC_STATUS: {
4900 if (!sync.mSignaled) {
4901 const auto res = ClientWaitSync(sync, 0, 0);
4902 sync.mSignaled = (res == LOCAL_GL_ALREADY_SIGNALED ||
4903 res == LOCAL_GL_CONDITION_SATISFIED);
4905 return JS::NumberValue(sync.mSignaled ? LOCAL_GL_SIGNALED
4906 : LOCAL_GL_UNSIGNALED);
4908 default:
4909 EnqueueError_ArgEnum("pname", pname);
4910 return JS::NullValue();
4912 }());
4915 GLenum ClientWebGLContext::ClientWaitSync(WebGLSyncJS& sync,
4916 const GLbitfield flags,
4917 const GLuint64 timeout) const {
4918 const FuncScope funcScope(*this, "clientWaitSync");
4919 if (IsContextLost()) return LOCAL_GL_WAIT_FAILED;
4920 if (!sync.ValidateUsable(*this, "sync")) return LOCAL_GL_WAIT_FAILED;
4922 if (flags != 0 && flags != LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT) {
4923 EnqueueError(LOCAL_GL_INVALID_VALUE,
4924 "`flags` must be SYNC_FLUSH_COMMANDS_BIT or 0.");
4925 return LOCAL_GL_WAIT_FAILED;
4928 const auto ret = [&]() {
4929 const auto& inProcess = mNotLost->inProcess;
4930 if (inProcess) {
4931 return inProcess->ClientWaitSync(sync.mId, flags, timeout);
4933 const auto& child = mNotLost->outOfProcess;
4934 child->FlushPendingCmds();
4935 GLenum ret = {};
4936 if (!child->SendClientWaitSync(sync.mId, flags, timeout, &ret)) {
4937 ret = {};
4939 return ret;
4940 }();
4942 switch (ret) {
4943 case LOCAL_GL_CONDITION_SATISFIED:
4944 case LOCAL_GL_ALREADY_SIGNALED:
4945 sync.mSignaled = true;
4946 break;
4949 // -
4951 const bool canBeAvailable =
4952 (sync.mCanBeAvailable || StaticPrefs::webgl_allow_immediate_queries());
4953 if (!canBeAvailable) {
4954 if (timeout) {
4955 EnqueueWarning(
4956 "Sync object not yet queryable. Please wait for the event"
4957 " loop.");
4959 return LOCAL_GL_WAIT_FAILED;
4962 return ret;
4965 void ClientWebGLContext::WaitSync(const WebGLSyncJS& sync,
4966 const GLbitfield flags,
4967 const GLint64 timeout) const {
4968 const FuncScope funcScope(*this, "waitSync");
4969 if (IsContextLost()) return;
4970 if (!sync.ValidateUsable(*this, "sync")) return;
4972 if (flags != 0) {
4973 EnqueueError(LOCAL_GL_INVALID_VALUE, "`flags` must be 0.");
4974 return;
4976 if (timeout != -1) {
4977 EnqueueError(LOCAL_GL_INVALID_VALUE, "`timeout` must be TIMEOUT_IGNORED.");
4978 return;
4981 JsWarning("waitSync is a no-op.");
4984 // -------------------------- Transform Feedback ---------------------------
4986 void ClientWebGLContext::BindTransformFeedback(
4987 const GLenum target, WebGLTransformFeedbackJS* const tf) {
4988 const FuncScope funcScope(*this, "bindTransformFeedback");
4989 if (IsContextLost()) return;
4990 if (tf && !tf->ValidateUsable(*this, "tf")) return;
4991 auto& state = State();
4993 if (target != LOCAL_GL_TRANSFORM_FEEDBACK) {
4994 EnqueueError(LOCAL_GL_INVALID_ENUM, "`target` must be TRANSFORM_FEEDBACK.");
4995 return;
4997 if (state.mTfActiveAndNotPaused) {
4998 EnqueueError(LOCAL_GL_INVALID_OPERATION,
4999 "Current Transform Feedback object is active and not paused.");
5000 return;
5003 if (tf) {
5004 tf->mHasBeenBound = true;
5005 state.mBoundTfo = tf;
5006 } else {
5007 state.mBoundTfo = state.mDefaultTfo;
5010 Run<RPROC(BindTransformFeedback)>(tf ? tf->mId : 0);
5013 void ClientWebGLContext::BeginTransformFeedback(const GLenum primMode) {
5014 const FuncScope funcScope(*this, "beginTransformFeedback");
5015 if (IsContextLost()) return;
5016 auto& state = State();
5017 auto& tfo = *(state.mBoundTfo);
5019 if (tfo.mActiveOrPaused) {
5020 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5021 "Transform Feedback is already active or paused.");
5022 return;
5024 MOZ_ASSERT(!state.mTfActiveAndNotPaused);
5026 auto& prog = state.mCurrentProgram;
5027 if (!prog) {
5028 EnqueueError(LOCAL_GL_INVALID_OPERATION, "No program in use.");
5029 return;
5031 const auto& linkResult = GetLinkResult(*prog);
5032 if (!linkResult.success) {
5033 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5034 "Program is not successfully linked.");
5035 return;
5038 auto tfBufferCount = linkResult.active.activeTfVaryings.size();
5039 if (tfBufferCount &&
5040 linkResult.tfBufferMode == LOCAL_GL_INTERLEAVED_ATTRIBS) {
5041 tfBufferCount = 1;
5043 if (!tfBufferCount) {
5044 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5045 "Program does not use Transform Feedback.");
5046 return;
5049 const auto& buffers = tfo.mAttribBuffers;
5050 for (const auto i : IntegerRange(tfBufferCount)) {
5051 if (!buffers[i]) {
5052 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5053 "Transform Feedback buffer %u is null.", i);
5054 return;
5058 switch (primMode) {
5059 case LOCAL_GL_POINTS:
5060 case LOCAL_GL_LINES:
5061 case LOCAL_GL_TRIANGLES:
5062 break;
5063 default:
5064 EnqueueError(LOCAL_GL_INVALID_ENUM,
5065 "`primitiveMode` must be POINTS, LINES< or TRIANGLES.");
5066 return;
5069 // -
5071 tfo.mActiveOrPaused = true;
5072 tfo.mActiveProgram = prog;
5073 tfo.mActiveProgramKeepAlive = prog->mKeepAliveWeak.lock();
5074 prog->mActiveTfos.insert(&tfo);
5075 state.mTfActiveAndNotPaused = true;
5077 Run<RPROC(BeginTransformFeedback)>(primMode);
5080 void ClientWebGLContext::EndTransformFeedback() {
5081 const FuncScope funcScope(*this, "endTransformFeedback");
5082 if (IsContextLost()) return;
5083 auto& state = State();
5084 auto& tfo = *(state.mBoundTfo);
5086 if (!tfo.mActiveOrPaused) {
5087 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5088 "Transform Feedback is not active or paused.");
5089 return;
5092 tfo.mActiveOrPaused = false;
5093 tfo.mActiveProgram->mActiveTfos.erase(&tfo);
5094 tfo.mActiveProgram = nullptr;
5095 tfo.mActiveProgramKeepAlive = nullptr;
5096 state.mTfActiveAndNotPaused = false;
5097 Run<RPROC(EndTransformFeedback)>();
5100 void ClientWebGLContext::PauseTransformFeedback() {
5101 const FuncScope funcScope(*this, "pauseTransformFeedback");
5102 if (IsContextLost()) return;
5103 auto& state = State();
5104 auto& tfo = *(state.mBoundTfo);
5106 if (!tfo.mActiveOrPaused) {
5107 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5108 "Transform Feedback is not active.");
5109 return;
5111 if (!state.mTfActiveAndNotPaused) {
5112 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5113 "Transform Feedback is already paused.");
5114 return;
5117 state.mTfActiveAndNotPaused = false;
5118 Run<RPROC(PauseTransformFeedback)>();
5121 void ClientWebGLContext::ResumeTransformFeedback() {
5122 const FuncScope funcScope(*this, "resumeTransformFeedback");
5123 if (IsContextLost()) return;
5124 auto& state = State();
5125 auto& tfo = *(state.mBoundTfo);
5127 if (!tfo.mActiveOrPaused) {
5128 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5129 "Transform Feedback is not active and paused.");
5130 return;
5132 if (state.mTfActiveAndNotPaused) {
5133 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5134 "Transform Feedback is not paused.");
5135 return;
5137 if (state.mCurrentProgram != tfo.mActiveProgram) {
5138 EnqueueError(
5139 LOCAL_GL_INVALID_OPERATION,
5140 "Cannot Resume Transform Feedback with a program link result different"
5141 " from when Begin was called.");
5142 return;
5145 state.mTfActiveAndNotPaused = true;
5146 Run<RPROC(ResumeTransformFeedback)>();
5149 void ClientWebGLContext::SetFramebufferIsInOpaqueRAF(WebGLFramebufferJS* fb,
5150 bool value) {
5151 fb->mInOpaqueRAF = value;
5152 Run<RPROC(SetFramebufferIsInOpaqueRAF)>(fb->mId, value);
5155 // ---------------------------- Misc Extensions ----------------------------
5156 void ClientWebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers) {
5157 const auto range = MakeRange(buffers);
5158 const auto vec = std::vector<GLenum>(range.begin().get(), range.end().get());
5159 Run<RPROC(DrawBuffers)>(vec);
5162 void ClientWebGLContext::EnqueueErrorImpl(const GLenum error,
5163 const nsACString& text) const {
5164 if (!mNotLost) return; // Ignored if context is lost.
5165 Run<RPROC(GenerateError)>(error, ToString(text));
5168 void ClientWebGLContext::RequestExtension(const WebGLExtensionID ext) const {
5169 Run<RPROC(RequestExtension)>(ext);
5172 // -
5174 static bool IsExtensionForbiddenForCaller(const WebGLExtensionID ext,
5175 const dom::CallerType callerType) {
5176 if (callerType == dom::CallerType::System) return false;
5178 if (StaticPrefs::webgl_enable_privileged_extensions()) return false;
5180 const bool resistFingerprinting =
5181 nsContentUtils::ShouldResistFingerprinting();
5182 switch (ext) {
5183 case WebGLExtensionID::MOZ_debug:
5184 return true;
5186 case WebGLExtensionID::WEBGL_debug_renderer_info:
5187 return resistFingerprinting ||
5188 !StaticPrefs::webgl_enable_debug_renderer_info();
5190 case WebGLExtensionID::WEBGL_debug_shaders:
5191 return resistFingerprinting;
5193 default:
5194 return false;
5198 bool ClientWebGLContext::IsSupported(const WebGLExtensionID ext,
5199 const dom::CallerType callerType) const {
5200 if (IsExtensionForbiddenForCaller(ext, callerType)) return false;
5201 const auto& limits = Limits();
5202 return limits.supportedExtensions[ext];
5205 void ClientWebGLContext::GetSupportedExtensions(
5206 dom::Nullable<nsTArray<nsString>>& retval,
5207 const dom::CallerType callerType) const {
5208 retval.SetNull();
5209 if (!mNotLost) return;
5211 auto& retarr = retval.SetValue();
5212 for (const auto i : MakeEnumeratedRange(WebGLExtensionID::Max)) {
5213 if (!IsSupported(i, callerType)) continue;
5215 const auto& extStr = GetExtensionName(i);
5216 retarr.AppendElement(NS_ConvertUTF8toUTF16(extStr));
5220 // -
5222 void ClientWebGLContext::GetSupportedProfilesASTC(
5223 dom::Nullable<nsTArray<nsString>>& retval) const {
5224 retval.SetNull();
5225 if (!mNotLost) return;
5226 const auto& limits = Limits();
5228 auto& retarr = retval.SetValue();
5229 retarr.AppendElement(u"ldr"_ns);
5230 if (limits.astcHdr) {
5231 retarr.AppendElement(u"hdr"_ns);
5235 // -
5237 bool ClientWebGLContext::ShouldResistFingerprinting() const {
5238 if (NS_IsMainThread()) {
5239 if (mCanvasElement) {
5240 // If we're constructed from a canvas element
5241 return nsContentUtils::ShouldResistFingerprinting(GetOwnerDoc());
5243 // if (mOffscreenCanvas->GetOwnerGlobal()) {
5244 // // If we're constructed from an offscreen canvas
5245 // return nsContentUtils::ShouldResistFingerprinting(
5246 // mOffscreenCanvas->GetOwnerGlobal()->PrincipalOrNull());
5248 // Last resort, just check the global preference
5249 return nsContentUtils::ShouldResistFingerprinting();
5251 dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate();
5252 MOZ_ASSERT(workerPrivate);
5253 return nsContentUtils::ShouldResistFingerprinting(workerPrivate);
5256 // ---------------------------
5258 void ClientWebGLContext::EnqueueError_ArgEnum(const char* const argName,
5259 const GLenum val) const {
5260 EnqueueError(LOCAL_GL_INVALID_ENUM, "Bad `%s`: 0x%04x", argName, val);
5263 // -
5264 // WebGLProgramJS
5266 void ClientWebGLContext::AttachShader(WebGLProgramJS& prog,
5267 WebGLShaderJS& shader) const {
5268 const FuncScope funcScope(*this, "attachShader");
5269 if (IsContextLost()) return;
5270 if (!prog.ValidateUsable(*this, "program")) return;
5271 if (!shader.ValidateUsable(*this, "shader")) return;
5273 auto& slot = *MaybeFind(prog.mNextLink_Shaders, shader.mType);
5274 if (slot.shader) {
5275 if (&shader == slot.shader) {
5276 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`shader` is already attached.");
5277 } else {
5278 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5279 "Only one of each type of"
5280 " shader may be attached to a program.");
5282 return;
5284 slot = {&shader, shader.mKeepAliveWeak.lock()};
5286 Run<RPROC(AttachShader)>(prog.mId, shader.mId);
5289 void ClientWebGLContext::BindAttribLocation(WebGLProgramJS& prog,
5290 const GLuint location,
5291 const nsAString& name) const {
5292 const FuncScope funcScope(*this, "detachShader");
5293 if (IsContextLost()) return;
5294 if (!prog.ValidateUsable(*this, "program")) return;
5296 const auto& nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
5297 Run<RPROC(BindAttribLocation)>(prog.mId, location, nameU8);
5300 void ClientWebGLContext::DetachShader(WebGLProgramJS& prog,
5301 const WebGLShaderJS& shader) const {
5302 const FuncScope funcScope(*this, "detachShader");
5303 if (IsContextLost()) return;
5304 if (!prog.ValidateUsable(*this, "program")) return;
5305 if (!shader.ValidateUsable(*this, "shader")) return;
5307 auto& slot = *MaybeFind(prog.mNextLink_Shaders, shader.mType);
5309 if (slot.shader != &shader) {
5310 EnqueueError(LOCAL_GL_INVALID_OPERATION, "`shader` is not attached.");
5311 return;
5313 slot = {};
5315 Run<RPROC(DetachShader)>(prog.mId, shader.mId);
5318 void ClientWebGLContext::GetAttachedShaders(
5319 const WebGLProgramJS& prog,
5320 dom::Nullable<nsTArray<RefPtr<WebGLShaderJS>>>& retval) const {
5321 const FuncScope funcScope(*this, "getAttachedShaders");
5322 if (IsContextLost()) return;
5323 if (!prog.ValidateUsable(*this, "program")) return;
5325 auto& arr = retval.SetValue();
5326 for (const auto& pair : prog.mNextLink_Shaders) {
5327 const auto& attachment = pair.second;
5328 if (!attachment.shader) continue;
5329 arr.AppendElement(attachment.shader);
5333 void ClientWebGLContext::LinkProgram(WebGLProgramJS& prog) const {
5334 const FuncScope funcScope(*this, "linkProgram");
5335 if (IsContextLost()) return;
5336 if (!prog.ValidateUsable(*this, "program")) return;
5338 if (!prog.mActiveTfos.empty()) {
5339 EnqueueError(LOCAL_GL_INVALID_OPERATION,
5340 "Program still in use by active or paused"
5341 " Transform Feedback objects.");
5342 return;
5345 prog.mResult = std::make_shared<webgl::LinkResult>();
5346 prog.mUniformLocByName = Nothing();
5347 prog.mUniformBlockBindings = {};
5348 Run<RPROC(LinkProgram)>(prog.mId);
5351 void ClientWebGLContext::TransformFeedbackVaryings(
5352 WebGLProgramJS& prog, const dom::Sequence<nsString>& varyings,
5353 const GLenum bufferMode) const {
5354 const FuncScope funcScope(*this, "transformFeedbackVaryings");
5355 if (IsContextLost()) return;
5356 if (!prog.ValidateUsable(*this, "program")) return;
5358 std::vector<std::string> varyingsU8;
5359 varyingsU8.reserve(varyings.Length());
5360 for (const auto& cur : varyings) {
5361 const auto curU8 = ToString(NS_ConvertUTF16toUTF8(cur));
5362 varyingsU8.push_back(curU8);
5365 Run<RPROC(TransformFeedbackVaryings)>(prog.mId, varyingsU8, bufferMode);
5368 void ClientWebGLContext::UniformBlockBinding(WebGLProgramJS& prog,
5369 const GLuint blockIndex,
5370 const GLuint blockBinding) const {
5371 const FuncScope funcScope(*this, "uniformBlockBinding");
5372 if (IsContextLost()) return;
5373 if (!prog.ValidateUsable(*this, "program")) return;
5374 const auto& state = State();
5376 (void)GetLinkResult(prog);
5377 auto& list = prog.mUniformBlockBindings;
5378 if (blockIndex >= list.size()) {
5379 EnqueueError(
5380 LOCAL_GL_INVALID_VALUE,
5381 "`blockIndex` (%u) must be less than ACTIVE_UNIFORM_BLOCKS (%zu).",
5382 blockIndex, list.size());
5383 return;
5385 if (blockBinding >= state.mBoundUbos.size()) {
5386 EnqueueError(LOCAL_GL_INVALID_VALUE,
5387 "`blockBinding` (%u) must be less than "
5388 "MAX_UNIFORM_BUFFER_BINDINGS (%zu).",
5389 blockBinding, state.mBoundUbos.size());
5390 return;
5393 list[blockIndex] = blockBinding;
5394 Run<RPROC(UniformBlockBinding)>(prog.mId, blockIndex, blockBinding);
5397 // WebGLProgramJS link result reflection
5399 already_AddRefed<WebGLActiveInfoJS> ClientWebGLContext::GetActiveAttrib(
5400 const WebGLProgramJS& prog, const GLuint index) {
5401 const FuncScope funcScope(*this, "getActiveAttrib");
5402 if (IsContextLost()) return nullptr;
5403 if (!prog.ValidateUsable(*this, "program")) return nullptr;
5405 const auto& res = GetLinkResult(prog);
5406 const auto& list = res.active.activeAttribs;
5407 if (index >= list.size()) {
5408 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5409 return nullptr;
5412 const auto& info = list[index];
5413 return AsAddRefed(new WebGLActiveInfoJS(info));
5416 already_AddRefed<WebGLActiveInfoJS> ClientWebGLContext::GetActiveUniform(
5417 const WebGLProgramJS& prog, const GLuint index) {
5418 const FuncScope funcScope(*this, "getActiveUniform");
5419 if (IsContextLost()) return nullptr;
5420 if (!prog.ValidateUsable(*this, "program")) return nullptr;
5422 const auto& res = GetLinkResult(prog);
5423 const auto& list = res.active.activeUniforms;
5424 if (index >= list.size()) {
5425 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5426 return nullptr;
5429 const auto& info = list[index];
5430 return AsAddRefed(new WebGLActiveInfoJS(info));
5433 void ClientWebGLContext::GetActiveUniformBlockName(const WebGLProgramJS& prog,
5434 const GLuint index,
5435 nsAString& retval) const {
5436 retval.SetIsVoid(true);
5437 const FuncScope funcScope(*this, "getActiveUniformBlockName");
5438 if (IsContextLost()) return;
5439 if (!prog.ValidateUsable(*this, "program")) return;
5441 const auto& res = GetLinkResult(prog);
5442 if (!res.success) {
5443 EnqueueError(LOCAL_GL_INVALID_OPERATION, "Program has not been linked.");
5444 return;
5447 const auto& list = res.active.activeUniformBlocks;
5448 if (index >= list.size()) {
5449 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5450 return;
5453 const auto& block = list[index];
5454 CopyUTF8toUTF16(block.name, retval);
5457 void ClientWebGLContext::GetActiveUniformBlockParameter(
5458 JSContext* const cx, const WebGLProgramJS& prog, const GLuint index,
5459 const GLenum pname, JS::MutableHandle<JS::Value> retval, ErrorResult& rv) {
5460 retval.set(JS::NullValue());
5461 const FuncScope funcScope(*this, "getActiveUniformBlockParameter");
5462 if (IsContextLost()) return;
5463 if (!prog.ValidateUsable(*this, "program")) return;
5465 const auto& res = GetLinkResult(prog);
5466 const auto& list = res.active.activeUniformBlocks;
5467 if (index >= list.size()) {
5468 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5469 return;
5471 const auto& block = list[index];
5473 retval.set([&]() -> JS::Value {
5474 switch (pname) {
5475 case LOCAL_GL_UNIFORM_BLOCK_BINDING:
5476 return JS::NumberValue(prog.mUniformBlockBindings[index]);
5478 case LOCAL_GL_UNIFORM_BLOCK_DATA_SIZE:
5479 return JS::NumberValue(block.dataSize);
5481 case LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS:
5482 return JS::NumberValue(block.activeUniformIndices.size());
5484 case LOCAL_GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES: {
5485 const auto& indices = block.activeUniformIndices;
5486 JS::RootedObject obj(cx, dom::Uint32Array::Create(
5487 cx, this, indices.size(), indices.data()));
5488 if (!obj) {
5489 rv = NS_ERROR_OUT_OF_MEMORY;
5491 return JS::ObjectOrNullValue(obj);
5494 case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER:
5495 return JS::BooleanValue(block.referencedByVertexShader);
5497 case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER:
5498 return JS::BooleanValue(block.referencedByFragmentShader);
5500 default:
5501 EnqueueError_ArgEnum("pname", pname);
5502 return JS::NullValue();
5504 }());
5507 void ClientWebGLContext::GetActiveUniforms(
5508 JSContext* const cx, const WebGLProgramJS& prog,
5509 const dom::Sequence<GLuint>& uniformIndices, const GLenum pname,
5510 JS::MutableHandle<JS::Value> retval) const {
5511 retval.set(JS::NullValue());
5512 const FuncScope funcScope(*this, "getActiveUniforms");
5513 if (IsContextLost()) return;
5514 if (!prog.ValidateUsable(*this, "program")) return;
5516 const auto& res = GetLinkResult(prog);
5517 const auto& list = res.active.activeUniforms;
5519 const auto count = uniformIndices.Length();
5520 JS::Rooted<JSObject*> array(cx, JS::NewArrayObject(cx, count));
5521 if (!array) return; // Just bail.
5523 for (const auto i : IntegerRange(count)) {
5524 const auto index = uniformIndices[i];
5525 if (index >= list.size()) {
5526 EnqueueError(LOCAL_GL_INVALID_VALUE,
5527 "`uniformIndices[%u]`: `%u` too large.", i, index);
5528 return;
5530 const auto& uniform = list[index];
5532 JS::RootedValue value(cx);
5533 switch (pname) {
5534 case LOCAL_GL_UNIFORM_TYPE:
5535 value = JS::NumberValue(uniform.elemType);
5536 break;
5538 case LOCAL_GL_UNIFORM_SIZE:
5539 value = JS::NumberValue(uniform.elemCount);
5540 break;
5542 case LOCAL_GL_UNIFORM_BLOCK_INDEX:
5543 value = JS::NumberValue(uniform.block_index);
5544 break;
5546 case LOCAL_GL_UNIFORM_OFFSET:
5547 value = JS::NumberValue(uniform.block_offset);
5548 break;
5550 case LOCAL_GL_UNIFORM_ARRAY_STRIDE:
5551 value = JS::NumberValue(uniform.block_arrayStride);
5552 break;
5554 case LOCAL_GL_UNIFORM_MATRIX_STRIDE:
5555 value = JS::NumberValue(uniform.block_matrixStride);
5556 break;
5558 case LOCAL_GL_UNIFORM_IS_ROW_MAJOR:
5559 value = JS::BooleanValue(uniform.block_isRowMajor);
5560 break;
5562 default:
5563 EnqueueError_ArgEnum("pname", pname);
5564 return;
5566 if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE)) return;
5569 retval.setObject(*array);
5572 already_AddRefed<WebGLActiveInfoJS>
5573 ClientWebGLContext::GetTransformFeedbackVarying(const WebGLProgramJS& prog,
5574 const GLuint index) {
5575 const FuncScope funcScope(*this, "getTransformFeedbackVarying");
5576 if (IsContextLost()) return nullptr;
5577 if (!prog.ValidateUsable(*this, "program")) return nullptr;
5579 const auto& res = GetLinkResult(prog);
5580 const auto& list = res.active.activeTfVaryings;
5581 if (index >= list.size()) {
5582 EnqueueError(LOCAL_GL_INVALID_VALUE, "`index` too large.");
5583 return nullptr;
5586 const auto& info = list[index];
5587 return AsAddRefed(new WebGLActiveInfoJS(info));
5590 GLint ClientWebGLContext::GetAttribLocation(const WebGLProgramJS& prog,
5591 const nsAString& name) const {
5592 const FuncScope funcScope(*this, "getAttribLocation");
5593 if (IsContextLost()) return -1;
5594 if (!prog.ValidateUsable(*this, "program")) return -1;
5596 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
5597 const auto& res = GetLinkResult(prog);
5598 for (const auto& cur : res.active.activeAttribs) {
5599 if (cur.name == nameU8) return cur.location;
5602 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
5603 if (err) {
5604 EnqueueError(err->type, "%s", err->info.c_str());
5606 return -1;
5609 GLint ClientWebGLContext::GetFragDataLocation(const WebGLProgramJS& prog,
5610 const nsAString& name) const {
5611 const FuncScope funcScope(*this, "getFragDataLocation");
5612 if (IsContextLost()) return -1;
5613 if (!prog.ValidateUsable(*this, "program")) return -1;
5615 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
5617 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
5618 if (err) {
5619 EnqueueError(*err);
5620 return -1;
5623 return [&]() {
5624 const auto& inProcess = mNotLost->inProcess;
5625 if (inProcess) {
5626 return inProcess->GetFragDataLocation(prog.mId, nameU8);
5628 const auto& child = mNotLost->outOfProcess;
5629 child->FlushPendingCmds();
5630 GLint ret = {};
5631 if (!child->SendGetFragDataLocation(prog.mId, nameU8, &ret)) {
5632 ret = {};
5634 return ret;
5635 }();
5638 GLuint ClientWebGLContext::GetUniformBlockIndex(
5639 const WebGLProgramJS& prog, const nsAString& blockName) const {
5640 const FuncScope funcScope(*this, "getUniformBlockIndex");
5641 if (IsContextLost()) return LOCAL_GL_INVALID_INDEX;
5642 if (!prog.ValidateUsable(*this, "program")) return LOCAL_GL_INVALID_INDEX;
5644 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(blockName));
5646 const auto& res = GetLinkResult(prog);
5647 const auto& list = res.active.activeUniformBlocks;
5648 for (const auto i : IntegerRange(list.size())) {
5649 const auto& cur = list[i];
5650 if (cur.name == nameU8) {
5651 return i;
5654 return LOCAL_GL_INVALID_INDEX;
5657 void ClientWebGLContext::GetUniformIndices(
5658 const WebGLProgramJS& prog, const dom::Sequence<nsString>& uniformNames,
5659 dom::Nullable<nsTArray<GLuint>>& retval) const {
5660 const FuncScope funcScope(*this, "getUniformIndices");
5661 if (IsContextLost()) return;
5662 if (!prog.ValidateUsable(*this, "program")) return;
5664 const auto& res = GetLinkResult(prog);
5665 auto ret = nsTArray<GLuint>(uniformNames.Length());
5667 std::unordered_map<std::string, size_t> retIdByName;
5668 retIdByName.reserve(ret.Length());
5670 for (const auto i : IntegerRange(uniformNames.Length())) {
5671 const auto& name = uniformNames[i];
5672 auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
5673 retIdByName.insert({std::move(nameU8), i});
5674 ret.AppendElement(LOCAL_GL_INVALID_INDEX);
5677 GLuint i = 0;
5678 for (const auto& cur : res.active.activeUniforms) {
5679 const auto maybeRetId = MaybeFind(retIdByName, cur.name);
5680 if (maybeRetId) {
5681 ret[*maybeRetId] = i;
5683 i += 1;
5686 retval.SetValue(std::move(ret));
5689 already_AddRefed<WebGLUniformLocationJS> ClientWebGLContext::GetUniformLocation(
5690 const WebGLProgramJS& prog, const nsAString& name) const {
5691 const FuncScope funcScope(*this, "getUniformLocation");
5692 if (IsContextLost()) return nullptr;
5693 if (!prog.ValidateUsable(*this, "program")) return nullptr;
5695 const auto& res = GetLinkResult(prog);
5697 if (!prog.mUniformLocByName) {
5698 // Cache a map from name->location.
5699 // Since the only way to set uniforms requires calling GetUniformLocation,
5700 // we expect apps to query most active uniforms once for each scalar or
5701 // array. NB: Uniform array setters do have overflow semantics, even though
5702 // uniform locations aren't guaranteed contiguous, but GetUniformLocation
5703 // must still be called once per array.
5704 prog.mUniformLocByName.emplace();
5706 for (const auto& activeUniform : res.active.activeUniforms) {
5707 if (activeUniform.block_index != -1) continue;
5709 auto locName = activeUniform.name;
5710 const auto indexed = webgl::ParseIndexed(locName);
5711 if (indexed) {
5712 locName = indexed->name;
5715 const auto err = CheckGLSLVariableName(mIsWebGL2, locName);
5716 if (err) continue;
5718 const auto baseLength = locName.size();
5719 for (const auto& pair : activeUniform.locByIndex) {
5720 if (indexed) {
5721 locName.erase(baseLength); // Erase previous "[N]".
5722 locName += '[';
5723 locName += std::to_string(pair.first);
5724 locName += ']';
5726 const auto locInfo =
5727 WebGLProgramJS::UniformLocInfo{pair.second, activeUniform.elemType};
5728 prog.mUniformLocByName->insert({locName, locInfo});
5732 const auto& locByName = *(prog.mUniformLocByName);
5734 const auto nameU8 = ToString(NS_ConvertUTF16toUTF8(name));
5735 auto loc = MaybeFind(locByName, nameU8);
5736 if (!loc) {
5737 loc = MaybeFind(locByName, nameU8 + "[0]");
5739 if (!loc) {
5740 const auto err = CheckGLSLVariableName(mIsWebGL2, nameU8);
5741 if (err) {
5742 EnqueueError(err->type, "%s", err->info.c_str());
5744 return nullptr;
5747 return AsAddRefed(new WebGLUniformLocationJS(*this, prog.mResult,
5748 loc->location, loc->elemType));
5751 std::array<uint16_t, 3> ValidUploadElemTypes(const GLenum elemType) {
5752 std::vector<GLenum> ret;
5753 switch (elemType) {
5754 case LOCAL_GL_BOOL:
5755 ret = {LOCAL_GL_FLOAT, LOCAL_GL_INT, LOCAL_GL_UNSIGNED_INT};
5756 break;
5757 case LOCAL_GL_BOOL_VEC2:
5758 ret = {LOCAL_GL_FLOAT_VEC2, LOCAL_GL_INT_VEC2,
5759 LOCAL_GL_UNSIGNED_INT_VEC2};
5760 break;
5761 case LOCAL_GL_BOOL_VEC3:
5762 ret = {LOCAL_GL_FLOAT_VEC3, LOCAL_GL_INT_VEC3,
5763 LOCAL_GL_UNSIGNED_INT_VEC3};
5764 break;
5765 case LOCAL_GL_BOOL_VEC4:
5766 ret = {LOCAL_GL_FLOAT_VEC4, LOCAL_GL_INT_VEC4,
5767 LOCAL_GL_UNSIGNED_INT_VEC4};
5768 break;
5770 case LOCAL_GL_SAMPLER_2D:
5771 case LOCAL_GL_SAMPLER_3D:
5772 case LOCAL_GL_SAMPLER_CUBE:
5773 case LOCAL_GL_SAMPLER_2D_SHADOW:
5774 case LOCAL_GL_SAMPLER_2D_ARRAY:
5775 case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
5776 case LOCAL_GL_SAMPLER_CUBE_SHADOW:
5777 case LOCAL_GL_INT_SAMPLER_2D:
5778 case LOCAL_GL_INT_SAMPLER_3D:
5779 case LOCAL_GL_INT_SAMPLER_CUBE:
5780 case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
5781 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
5782 case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
5783 case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
5784 case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
5785 ret = {LOCAL_GL_INT};
5786 break;
5788 default:
5789 ret = {elemType};
5790 break;
5793 std::array<uint16_t, 3> arr = {};
5794 MOZ_ASSERT(arr[2] == 0);
5795 for (const auto i : IntegerRange(ret.size())) {
5796 arr[i] = AssertedCast<uint16_t>(ret[i]);
5798 return arr;
5801 void ClientWebGLContext::GetProgramInfoLog(const WebGLProgramJS& prog,
5802 nsAString& retval) const {
5803 retval.SetIsVoid(true);
5804 const FuncScope funcScope(*this, "getProgramInfoLog");
5805 if (IsContextLost()) return;
5806 if (!prog.ValidateUsable(*this, "program")) return;
5808 const auto& res = GetLinkResult(prog);
5809 CopyUTF8toUTF16(res.log, retval);
5812 void ClientWebGLContext::GetProgramParameter(
5813 JSContext* const js, const WebGLProgramJS& prog, const GLenum pname,
5814 JS::MutableHandle<JS::Value> retval) const {
5815 retval.set(JS::NullValue());
5816 const FuncScope funcScope(*this, "getProgramParameter");
5817 if (IsContextLost()) return;
5818 if (!prog.ValidateUsable(*this, "program")) return;
5820 retval.set([&]() -> JS::Value {
5821 switch (pname) {
5822 case LOCAL_GL_DELETE_STATUS:
5823 // "Is flagged for deletion?"
5824 return JS::BooleanValue(!prog.mKeepAlive);
5825 case LOCAL_GL_VALIDATE_STATUS:
5826 return JS::BooleanValue(prog.mLastValidate);
5827 case LOCAL_GL_ATTACHED_SHADERS: {
5828 size_t shaders = 0;
5829 for (const auto& pair : prog.mNextLink_Shaders) {
5830 const auto& slot = pair.second;
5831 if (slot.shader) {
5832 shaders += 1;
5835 return JS::NumberValue(shaders);
5837 default:
5838 break;
5841 const auto& res = GetLinkResult(prog);
5843 switch (pname) {
5844 case LOCAL_GL_LINK_STATUS:
5845 return JS::BooleanValue(res.success);
5847 case LOCAL_GL_ACTIVE_ATTRIBUTES:
5848 return JS::NumberValue(res.active.activeAttribs.size());
5850 case LOCAL_GL_ACTIVE_UNIFORMS:
5851 return JS::NumberValue(res.active.activeUniforms.size());
5853 case LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER_MODE:
5854 if (!mIsWebGL2) break;
5855 return JS::NumberValue(res.tfBufferMode);
5857 case LOCAL_GL_TRANSFORM_FEEDBACK_VARYINGS:
5858 if (!mIsWebGL2) break;
5859 return JS::NumberValue(res.active.activeTfVaryings.size());
5861 case LOCAL_GL_ACTIVE_UNIFORM_BLOCKS:
5862 if (!mIsWebGL2) break;
5863 return JS::NumberValue(res.active.activeUniformBlocks.size());
5865 default:
5866 break;
5868 EnqueueError_ArgEnum("pname", pname);
5869 return JS::NullValue();
5870 }());
5873 // -
5874 // WebGLShaderJS
5876 void ClientWebGLContext::CompileShader(WebGLShaderJS& shader) const {
5877 const FuncScope funcScope(*this, "compileShader");
5878 if (IsContextLost()) return;
5879 if (!shader.ValidateUsable(*this, "shader")) return;
5881 shader.mResult = {};
5882 Run<RPROC(CompileShader)>(shader.mId);
5885 void ClientWebGLContext::GetShaderInfoLog(const WebGLShaderJS& shader,
5886 nsAString& retval) const {
5887 retval.SetIsVoid(true);
5888 const FuncScope funcScope(*this, "getShaderInfoLog");
5889 if (IsContextLost()) return;
5890 if (!shader.ValidateUsable(*this, "shader")) return;
5892 const auto& result = GetCompileResult(shader);
5893 CopyUTF8toUTF16(result.log, retval);
5896 void ClientWebGLContext::GetShaderParameter(
5897 JSContext* const cx, const WebGLShaderJS& shader, const GLenum pname,
5898 JS::MutableHandle<JS::Value> retval) const {
5899 retval.set(JS::NullValue());
5900 const FuncScope funcScope(*this, "getShaderParameter");
5901 if (IsContextLost()) return;
5902 if (!shader.ValidateUsable(*this, "shader")) return;
5904 retval.set([&]() -> JS::Value {
5905 switch (pname) {
5906 case LOCAL_GL_SHADER_TYPE:
5907 return JS::NumberValue(shader.mType);
5909 case LOCAL_GL_DELETE_STATUS: // "Is flagged for deletion?"
5910 return JS::BooleanValue(!shader.mKeepAlive);
5912 case LOCAL_GL_COMPILE_STATUS: {
5913 const auto& result = GetCompileResult(shader);
5914 return JS::BooleanValue(result.success);
5917 default:
5918 EnqueueError_ArgEnum("pname", pname);
5919 return JS::NullValue();
5921 }());
5924 void ClientWebGLContext::GetShaderSource(const WebGLShaderJS& shader,
5925 nsAString& retval) const {
5926 retval.SetIsVoid(true);
5927 const FuncScope funcScope(*this, "getShaderSource");
5928 if (IsContextLost()) return;
5929 if (!shader.ValidateUsable(*this, "shader")) return;
5931 CopyUTF8toUTF16(shader.mSource, retval);
5934 void ClientWebGLContext::GetTranslatedShaderSource(const WebGLShaderJS& shader,
5935 nsAString& retval) const {
5936 retval.SetIsVoid(true);
5937 const FuncScope funcScope(*this, "getTranslatedShaderSource");
5938 if (IsContextLost()) return;
5939 if (!shader.ValidateUsable(*this, "shader")) return;
5941 const auto& result = GetCompileResult(shader);
5942 CopyUTF8toUTF16(result.translatedSource, retval);
5945 void ClientWebGLContext::ShaderSource(WebGLShaderJS& shader,
5946 const nsAString& sourceU16) const {
5947 const FuncScope funcScope(*this, "shaderSource");
5948 if (IsContextLost()) return;
5949 if (!shader.ValidateUsable(*this, "shader")) return;
5951 auto source = ToString(NS_ConvertUTF16toUTF8(sourceU16));
5952 const auto cleanSource = CommentsToSpaces(source);
5954 const auto badChar = CheckGLSLPreprocString(mIsWebGL2, cleanSource);
5955 if (badChar) {
5956 EnqueueError(LOCAL_GL_INVALID_VALUE,
5957 "`source` contains illegal character 0x%x.", *badChar);
5958 return;
5961 shader.mSource = std::move(source);
5962 Run<RPROC(ShaderSource)>(shader.mId, cleanSource);
5965 // -
5967 const webgl::CompileResult& ClientWebGLContext::GetCompileResult(
5968 const WebGLShaderJS& shader) const {
5969 if (shader.mResult.pending) {
5970 shader.mResult = [&]() {
5971 const auto& inProcess = mNotLost->inProcess;
5972 if (inProcess) {
5973 return inProcess->GetCompileResult(shader.mId);
5975 const auto& child = mNotLost->outOfProcess;
5976 child->FlushPendingCmds();
5977 webgl::CompileResult ret = {};
5978 if (!child->SendGetCompileResult(shader.mId, &ret)) {
5979 ret = {};
5981 return ret;
5982 }();
5984 return shader.mResult;
5987 const webgl::LinkResult& ClientWebGLContext::GetLinkResult(
5988 const WebGLProgramJS& prog) const {
5989 if (prog.mResult->pending) {
5990 const auto notLost =
5991 mNotLost; // Hold a strong-ref to prevent LoseContext=>UAF.
5992 if (!notLost) return *(prog.mResult);
5994 *(prog.mResult) = [&]() {
5995 const auto& inProcess = mNotLost->inProcess;
5996 if (inProcess) {
5997 return inProcess->GetLinkResult(prog.mId);
5999 const auto& child = mNotLost->outOfProcess;
6000 child->FlushPendingCmds();
6001 webgl::LinkResult ret;
6002 if (!child->SendGetLinkResult(prog.mId, &ret)) {
6003 ret = {};
6005 return ret;
6006 }();
6008 prog.mUniformBlockBindings.resize(
6009 prog.mResult->active.activeUniformBlocks.size());
6011 auto& state = State();
6012 if (state.mCurrentProgram == &prog && prog.mResult->success) {
6013 state.mActiveLinkResult = prog.mResult;
6016 return *(prog.mResult);
6019 #undef RPROC
6021 // ---------------------------
6023 bool ClientWebGLContext::ValidateArrayBufferView(
6024 const dom::ArrayBufferView& view, GLuint elemOffset,
6025 GLuint elemCountOverride, const GLenum errorEnum, uint8_t** const out_bytes,
6026 size_t* const out_byteLen) const {
6027 view.ComputeState();
6028 uint8_t* const bytes = view.Data();
6029 const size_t byteLen = view.Length();
6031 const auto& elemSize = SizeOfViewElem(view);
6033 size_t elemCount = byteLen / elemSize;
6034 if (elemOffset > elemCount) {
6035 EnqueueError(errorEnum, "Invalid offset into ArrayBufferView.");
6036 return false;
6038 elemCount -= elemOffset;
6040 if (elemCountOverride) {
6041 if (elemCountOverride > elemCount) {
6042 EnqueueError(errorEnum, "Invalid sub-length for ArrayBufferView.");
6043 return false;
6045 elemCount = elemCountOverride;
6048 *out_bytes = bytes + (elemOffset * elemSize);
6049 *out_byteLen = elemCount * elemSize;
6050 return true;
6053 // ---------------------------
6055 webgl::ObjectJS::ObjectJS(const ClientWebGLContext& webgl)
6056 : mGeneration(webgl.mNotLost), mId(webgl.mNotLost->state.NextId()) {}
6058 // -
6060 WebGLFramebufferJS::WebGLFramebufferJS(const ClientWebGLContext& webgl,
6061 bool opaque)
6062 : webgl::ObjectJS(webgl), mOpaque(opaque) {
6063 (void)mAttachments[LOCAL_GL_DEPTH_ATTACHMENT];
6064 (void)mAttachments[LOCAL_GL_STENCIL_ATTACHMENT];
6065 if (!webgl.mIsWebGL2) {
6066 (void)mAttachments[LOCAL_GL_DEPTH_STENCIL_ATTACHMENT];
6069 EnsureColorAttachments();
6072 void WebGLFramebufferJS::EnsureColorAttachments() {
6073 const auto& webgl = Context();
6074 const auto& limits = webgl->Limits();
6075 auto maxColorDrawBuffers = limits.maxColorDrawBuffers;
6076 if (!webgl->mIsWebGL2 &&
6077 !webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers)) {
6078 maxColorDrawBuffers = 1;
6080 for (const auto i : IntegerRange(maxColorDrawBuffers)) {
6081 (void)mAttachments[LOCAL_GL_COLOR_ATTACHMENT0 + i];
6085 WebGLProgramJS::WebGLProgramJS(const ClientWebGLContext& webgl)
6086 : webgl::ObjectJS(webgl),
6087 mKeepAlive(std::make_shared<webgl::ProgramKeepAlive>(*this)),
6088 mKeepAliveWeak(mKeepAlive) {
6089 (void)mNextLink_Shaders[LOCAL_GL_VERTEX_SHADER];
6090 (void)mNextLink_Shaders[LOCAL_GL_FRAGMENT_SHADER];
6092 mResult = std::make_shared<webgl::LinkResult>();
6095 WebGLShaderJS::WebGLShaderJS(const ClientWebGLContext& webgl, const GLenum type)
6096 : webgl::ObjectJS(webgl),
6097 mType(type),
6098 mKeepAlive(std::make_shared<webgl::ShaderKeepAlive>(*this)),
6099 mKeepAliveWeak(mKeepAlive) {}
6101 WebGLTransformFeedbackJS::WebGLTransformFeedbackJS(
6102 const ClientWebGLContext& webgl)
6103 : webgl::ObjectJS(webgl),
6104 mAttribBuffers(webgl.Limits().maxTransformFeedbackSeparateAttribs) {}
6106 WebGLVertexArrayJS::WebGLVertexArrayJS(const ClientWebGLContext& webgl)
6107 : webgl::ObjectJS(webgl), mAttribBuffers(webgl.Limits().maxVertexAttribs) {}
6109 // -
6111 #define _(WebGLType) \
6112 JSObject* WebGLType##JS::WrapObject(JSContext* const cx, \
6113 JS::Handle<JSObject*> givenProto) { \
6114 return dom::WebGLType##_Binding::Wrap(cx, this, givenProto); \
6117 _(WebGLBuffer)
6118 _(WebGLFramebuffer)
6119 _(WebGLProgram)
6120 _(WebGLQuery)
6121 _(WebGLRenderbuffer)
6122 _(WebGLSampler)
6123 _(WebGLShader)
6124 _(WebGLSync)
6125 _(WebGLTexture)
6126 _(WebGLTransformFeedback)
6127 _(WebGLUniformLocation)
6128 //_(WebGLVertexArray) // The webidl is `WebGLVertexArrayObject` :(
6130 #undef _
6132 JSObject* WebGLVertexArrayJS::WrapObject(JSContext* const cx,
6133 JS::Handle<JSObject*> givenProto) {
6134 return dom::WebGLVertexArrayObject_Binding::Wrap(cx, this, givenProto);
6137 bool WebGLActiveInfoJS::WrapObject(JSContext* const cx,
6138 JS::Handle<JSObject*> givenProto,
6139 JS::MutableHandle<JSObject*> reflector) {
6140 return dom::WebGLActiveInfo_Binding::Wrap(cx, this, givenProto, reflector);
6143 bool WebGLShaderPrecisionFormatJS::WrapObject(
6144 JSContext* const cx, JS::Handle<JSObject*> givenProto,
6145 JS::MutableHandle<JSObject*> reflector) {
6146 return dom::WebGLShaderPrecisionFormat_Binding::Wrap(cx, this, givenProto,
6147 reflector);
6150 // ---------------------
6152 // Todo: Move this to RefPtr.h.
6153 template <typename T>
6154 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
6155 const RefPtr<T>& field, const char* name,
6156 uint32_t flags) {
6157 ImplCycleCollectionTraverse(callback, const_cast<RefPtr<T>&>(field), name,
6158 flags);
6161 // -
6163 template <typename T>
6164 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
6165 const std::vector<RefPtr<T>>& field,
6166 const char* name, uint32_t flags) {
6167 for (const auto& cur : field) {
6168 ImplCycleCollectionTraverse(callback, cur, name, flags);
6172 template <typename T>
6173 void ImplCycleCollectionUnlink(std::vector<RefPtr<T>>& field) {
6174 field = {};
6177 // -
6179 template <typename T, size_t N>
6180 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
6181 const std::array<RefPtr<T>, N>& field,
6182 const char* name, uint32_t flags) {
6183 for (const auto& cur : field) {
6184 ImplCycleCollectionTraverse(callback, cur, name, flags);
6188 template <typename T, size_t N>
6189 void ImplCycleCollectionUnlink(std::array<RefPtr<T>, N>& field) {
6190 field = {};
6193 // -
6195 template <typename T>
6196 void ImplCycleCollectionTraverse(
6197 nsCycleCollectionTraversalCallback& callback,
6198 const std::unordered_map<GLenum, RefPtr<T>>& field, const char* name,
6199 uint32_t flags) {
6200 for (const auto& pair : field) {
6201 ImplCycleCollectionTraverse(callback, pair.second, name, flags);
6205 template <typename T>
6206 void ImplCycleCollectionUnlink(std::unordered_map<GLenum, RefPtr<T>>& field) {
6207 field = {};
6210 // -
6212 void ImplCycleCollectionTraverse(
6213 nsCycleCollectionTraversalCallback& callback,
6214 const std::unordered_map<GLenum, WebGLFramebufferJS::Attachment>& field,
6215 const char* name, uint32_t flags) {
6216 for (const auto& pair : field) {
6217 const auto& attach = pair.second;
6218 ImplCycleCollectionTraverse(callback, attach.rb, name, flags);
6219 ImplCycleCollectionTraverse(callback, attach.tex, name, flags);
6223 void ImplCycleCollectionUnlink(
6224 std::unordered_map<GLenum, WebGLFramebufferJS::Attachment>& field) {
6225 field = {};
6228 // -
6230 void ImplCycleCollectionTraverse(
6231 nsCycleCollectionTraversalCallback& callback,
6232 const std::unordered_map<GLenum, WebGLProgramJS::Attachment>& field,
6233 const char* name, uint32_t flags) {
6234 for (const auto& pair : field) {
6235 const auto& attach = pair.second;
6236 ImplCycleCollectionTraverse(callback, attach.shader, name, flags);
6240 void ImplCycleCollectionUnlink(
6241 std::unordered_map<GLenum, WebGLProgramJS::Attachment>& field) {
6242 field = {};
6245 // -
6247 void ImplCycleCollectionUnlink(
6248 const RefPtr<ClientWebGLExtensionLoseContext>& field) {
6249 const_cast<RefPtr<ClientWebGLExtensionLoseContext>&>(field) = nullptr;
6251 void ImplCycleCollectionUnlink(const RefPtr<WebGLProgramJS>& field) {
6252 const_cast<RefPtr<WebGLProgramJS>&>(field) = nullptr;
6254 void ImplCycleCollectionUnlink(const RefPtr<WebGLShaderJS>& field) {
6255 const_cast<RefPtr<WebGLShaderJS>&>(field) = nullptr;
6258 // ----------------------
6260 void ImplCycleCollectionTraverse(
6261 nsCycleCollectionTraversalCallback& callback,
6262 const std::shared_ptr<webgl::NotLostData>& field, const char* name,
6263 uint32_t flags) {
6264 if (!field) return;
6266 ImplCycleCollectionTraverse(callback, field->extensions,
6267 "NotLostData.extensions", flags);
6269 const auto& state = field->state;
6271 ImplCycleCollectionTraverse(callback, state.mDefaultTfo, "state.mDefaultTfo",
6272 flags);
6273 ImplCycleCollectionTraverse(callback, state.mDefaultVao, "state.mDefaultVao",
6274 flags);
6276 ImplCycleCollectionTraverse(callback, state.mCurrentProgram,
6277 "state.mCurrentProgram", flags);
6279 ImplCycleCollectionTraverse(callback, state.mBoundBufferByTarget,
6280 "state.mBoundBufferByTarget", flags);
6281 ImplCycleCollectionTraverse(callback, state.mBoundUbos, "state.mBoundUbos",
6282 flags);
6283 ImplCycleCollectionTraverse(callback, state.mBoundDrawFb,
6284 "state.mBoundDrawFb", flags);
6285 ImplCycleCollectionTraverse(callback, state.mBoundReadFb,
6286 "state.mBoundReadFb", flags);
6287 ImplCycleCollectionTraverse(callback, state.mBoundRb, "state.mBoundRb",
6288 flags);
6289 ImplCycleCollectionTraverse(callback, state.mBoundTfo, "state.mBoundTfo",
6290 flags);
6291 ImplCycleCollectionTraverse(callback, state.mBoundVao, "state.mBoundVao",
6292 flags);
6293 ImplCycleCollectionTraverse(callback, state.mCurrentQueryByTarget,
6294 "state.state.mCurrentQueryByTarget", flags);
6296 for (const auto& texUnit : state.mTexUnits) {
6297 ImplCycleCollectionTraverse(callback, texUnit.sampler,
6298 "state.mTexUnits[].sampler", flags);
6299 ImplCycleCollectionTraverse(callback, texUnit.texByTarget,
6300 "state.mTexUnits[].texByTarget", flags);
6304 void ImplCycleCollectionUnlink(std::shared_ptr<webgl::NotLostData>& field) {
6305 if (!field) return;
6306 const auto keepAlive = field;
6307 keepAlive->extensions = {};
6308 keepAlive->state = {};
6309 field = nullptr;
6312 // -----------------------------------------------------
6314 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLBufferJS)
6315 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLFramebufferJS, mAttachments)
6316 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLProgramJS, mNextLink_Shaders)
6317 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLQueryJS)
6318 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLRenderbufferJS)
6319 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLSamplerJS)
6320 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLShaderJS)
6321 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLSyncJS)
6322 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLTextureJS)
6323 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLTransformFeedbackJS, mAttribBuffers,
6324 mActiveProgram)
6325 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLUniformLocationJS)
6326 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLVertexArrayJS, mIndexBuffer,
6327 mAttribBuffers)
6329 // -
6331 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ClientWebGLContext)
6332 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
6333 NS_INTERFACE_MAP_ENTRY(nsICanvasRenderingContextInternal)
6334 NS_INTERFACE_MAP_ENTRY(nsISupports)
6335 NS_INTERFACE_MAP_END
6337 NS_IMPL_CYCLE_COLLECTING_ADDREF(ClientWebGLContext)
6338 NS_IMPL_CYCLE_COLLECTING_RELEASE(ClientWebGLContext)
6340 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(
6341 ClientWebGLContext, mExtLoseContext, mNotLost,
6342 // Don't forget nsICanvasRenderingContextInternal:
6343 mCanvasElement, mOffscreenCanvas)
6345 // -----------------------------
6347 #define _(X) \
6348 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGL##X##JS, AddRef) \
6349 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGL##X##JS, Release)
6351 _(Buffer)
6352 _(Framebuffer)
6353 _(Program)
6354 _(Query)
6355 _(Renderbuffer)
6356 _(Sampler)
6357 _(Shader)
6358 _(Sync)
6359 _(Texture)
6360 _(TransformFeedback)
6361 _(UniformLocation)
6362 _(VertexArray)
6364 #undef _
6366 } // namespace mozilla