summaryrefslogtreecommitdiffstats
path: root/dom/canvas/WebGLContextGL.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /dom/canvas/WebGLContextGL.cpp
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas/WebGLContextGL.cpp')
-rw-r--r--dom/canvas/WebGLContextGL.cpp1580
1 files changed, 1580 insertions, 0 deletions
diff --git a/dom/canvas/WebGLContextGL.cpp b/dom/canvas/WebGLContextGL.cpp
new file mode 100644
index 0000000000..23c45779a8
--- /dev/null
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -0,0 +1,1580 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebGLContext.h"
+#include "WebGL2Context.h"
+
+#include "WebGLContextUtils.h"
+#include "WebGLBuffer.h"
+#include "WebGLShader.h"
+#include "WebGLProgram.h"
+#include "WebGLFormats.h"
+#include "WebGLFramebuffer.h"
+#include "WebGLQuery.h"
+#include "WebGLRenderbuffer.h"
+#include "WebGLTexture.h"
+#include "WebGLVertexArray.h"
+
+#include "nsDebug.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+
+#include "gfxContext.h"
+#include "gfxPlatform.h"
+#include "GLContext.h"
+
+#include "nsContentUtils.h"
+#include "nsError.h"
+#include "nsLayoutUtils.h"
+
+#include "CanvasUtils.h"
+#include "gfxUtils.h"
+#include "MozFramebuffer.h"
+
+#include "jsfriendapi.h"
+
+#include "WebGLTexelConversions.h"
+#include "WebGLValidateStrings.h"
+#include <algorithm>
+
+// needed to check if current OS is lower than 10.7
+#if defined(MOZ_WIDGET_COCOA)
+# include "nsCocoaFeatures.h"
+#endif
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/ImageData.h"
+#include "mozilla/dom/WebGLRenderingContextBinding.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/StaticPrefs_webgl.h"
+
+namespace mozilla {
+
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::gl;
+
+//
+// WebGL API
+//
+
+void WebGLContext::ActiveTexture(uint32_t texUnit) {
+ FuncScope funcScope(*this, "activeTexture");
+ if (IsContextLost()) return;
+ funcScope.mBindFailureGuard = true;
+
+ if (texUnit >= Limits().maxTexUnits) {
+ return ErrorInvalidEnum("Texture unit %u out of range (%u).", texUnit,
+ Limits().maxTexUnits);
+ }
+
+ mActiveTexture = texUnit;
+ gl->fActiveTexture(LOCAL_GL_TEXTURE0 + texUnit);
+
+ funcScope.mBindFailureGuard = false;
+}
+
+void WebGLContext::AttachShader(WebGLProgram& prog, WebGLShader& shader) {
+ FuncScope funcScope(*this, "attachShader");
+ if (IsContextLost()) return;
+ funcScope.mBindFailureGuard = true;
+
+ prog.AttachShader(shader);
+
+ funcScope.mBindFailureGuard = false;
+}
+
+void WebGLContext::BindAttribLocation(WebGLProgram& prog, GLuint location,
+ const std::string& name) const {
+ const FuncScope funcScope(*this, "bindAttribLocation");
+ if (IsContextLost()) return;
+
+ prog.BindAttribLocation(location, name);
+}
+
+void WebGLContext::BindFramebuffer(GLenum target, WebGLFramebuffer* wfb) {
+ FuncScope funcScope(*this, "bindFramebuffer");
+ if (IsContextLost()) return;
+ funcScope.mBindFailureGuard = true;
+
+ if (!ValidateFramebufferTarget(target)) return;
+
+ if (!wfb) {
+ gl->fBindFramebuffer(target, 0);
+ } else {
+ GLuint framebuffername = wfb->mGLName;
+ gl->fBindFramebuffer(target, framebuffername);
+ wfb->mHasBeenBound = true;
+ }
+
+ switch (target) {
+ case LOCAL_GL_FRAMEBUFFER:
+ mBoundDrawFramebuffer = wfb;
+ mBoundReadFramebuffer = wfb;
+ break;
+ case LOCAL_GL_DRAW_FRAMEBUFFER:
+ mBoundDrawFramebuffer = wfb;
+ break;
+ case LOCAL_GL_READ_FRAMEBUFFER:
+ mBoundReadFramebuffer = wfb;
+ break;
+ default:
+ return;
+ }
+ funcScope.mBindFailureGuard = false;
+}
+
+void WebGLContext::BlendEquationSeparate(Maybe<GLuint> i, GLenum modeRGB,
+ GLenum modeAlpha) {
+ const FuncScope funcScope(*this, "blendEquationSeparate");
+ if (IsContextLost()) return;
+
+ if (!ValidateBlendEquationEnum(modeRGB, "modeRGB") ||
+ !ValidateBlendEquationEnum(modeAlpha, "modeAlpha")) {
+ return;
+ }
+
+ if (i) {
+ MOZ_RELEASE_ASSERT(
+ IsExtensionEnabled(WebGLExtensionID::OES_draw_buffers_indexed));
+ const auto limit = MaxValidDrawBuffers();
+ if (*i >= limit) {
+ ErrorInvalidValue("`index` (%u) must be < %s (%u)", *i,
+ "MAX_DRAW_BUFFERS", limit);
+ return;
+ }
+
+ gl->fBlendEquationSeparatei(*i, modeRGB, modeAlpha);
+ } else {
+ gl->fBlendEquationSeparate(modeRGB, modeAlpha);
+ }
+}
+
+static bool ValidateBlendFuncEnum(WebGLContext* webgl, GLenum factor,
+ const char* varName) {
+ switch (factor) {
+ case LOCAL_GL_ZERO:
+ case LOCAL_GL_ONE:
+ case LOCAL_GL_SRC_COLOR:
+ case LOCAL_GL_ONE_MINUS_SRC_COLOR:
+ case LOCAL_GL_DST_COLOR:
+ case LOCAL_GL_ONE_MINUS_DST_COLOR:
+ case LOCAL_GL_SRC_ALPHA:
+ case LOCAL_GL_ONE_MINUS_SRC_ALPHA:
+ case LOCAL_GL_DST_ALPHA:
+ case LOCAL_GL_ONE_MINUS_DST_ALPHA:
+ case LOCAL_GL_CONSTANT_COLOR:
+ case LOCAL_GL_ONE_MINUS_CONSTANT_COLOR:
+ case LOCAL_GL_CONSTANT_ALPHA:
+ case LOCAL_GL_ONE_MINUS_CONSTANT_ALPHA:
+ case LOCAL_GL_SRC_ALPHA_SATURATE:
+ return true;
+
+ default:
+ webgl->ErrorInvalidEnumInfo(varName, factor);
+ return false;
+ }
+}
+
+static bool ValidateBlendFuncEnums(WebGLContext* webgl, GLenum srcRGB,
+ GLenum srcAlpha, GLenum dstRGB,
+ GLenum dstAlpha) {
+ if (!webgl->IsWebGL2()) {
+ if (dstRGB == LOCAL_GL_SRC_ALPHA_SATURATE ||
+ dstAlpha == LOCAL_GL_SRC_ALPHA_SATURATE) {
+ webgl->ErrorInvalidEnum(
+ "LOCAL_GL_SRC_ALPHA_SATURATE as a destination"
+ " blend function is disallowed in WebGL 1 (dstRGB ="
+ " 0x%04x, dstAlpha = 0x%04x).",
+ dstRGB, dstAlpha);
+ return false;
+ }
+ }
+
+ if (!ValidateBlendFuncEnum(webgl, srcRGB, "srcRGB") ||
+ !ValidateBlendFuncEnum(webgl, srcAlpha, "srcAlpha") ||
+ !ValidateBlendFuncEnum(webgl, dstRGB, "dstRGB") ||
+ !ValidateBlendFuncEnum(webgl, dstAlpha, "dstAlpha")) {
+ return false;
+ }
+
+ return true;
+}
+
+void WebGLContext::BlendFuncSeparate(Maybe<GLuint> i, GLenum srcRGB,
+ GLenum dstRGB, GLenum srcAlpha,
+ GLenum dstAlpha) {
+ const FuncScope funcScope(*this, "blendFuncSeparate");
+ if (IsContextLost()) return;
+
+ if (!ValidateBlendFuncEnums(this, srcRGB, srcAlpha, dstRGB, dstAlpha)) return;
+
+ // note that we only check compatibity for the RGB enums, no need to for the
+ // Alpha enums, see "Section 6.8 forgetting to mention alpha factors?" thread
+ // on the public_webgl mailing list
+ if (!ValidateBlendFuncEnumsCompatibility(srcRGB, dstRGB, "srcRGB and dstRGB"))
+ return;
+
+ if (i) {
+ MOZ_RELEASE_ASSERT(
+ IsExtensionEnabled(WebGLExtensionID::OES_draw_buffers_indexed));
+ const auto limit = MaxValidDrawBuffers();
+ if (*i >= limit) {
+ ErrorInvalidValue("`index` (%u) must be < %s (%u)", *i,
+ "MAX_DRAW_BUFFERS", limit);
+ return;
+ }
+
+ gl->fBlendFuncSeparatei(*i, srcRGB, dstRGB, srcAlpha, dstAlpha);
+ } else {
+ gl->fBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
+ }
+}
+
+GLenum WebGLContext::CheckFramebufferStatus(GLenum target) {
+ const FuncScope funcScope(*this, "checkFramebufferStatus");
+ if (IsContextLost()) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
+
+ if (!ValidateFramebufferTarget(target)) return 0;
+
+ WebGLFramebuffer* fb;
+ switch (target) {
+ case LOCAL_GL_FRAMEBUFFER:
+ case LOCAL_GL_DRAW_FRAMEBUFFER:
+ fb = mBoundDrawFramebuffer;
+ break;
+
+ case LOCAL_GL_READ_FRAMEBUFFER:
+ fb = mBoundReadFramebuffer;
+ break;
+
+ default:
+ MOZ_CRASH("GFX: Bad target.");
+ }
+
+ if (!fb) return LOCAL_GL_FRAMEBUFFER_COMPLETE;
+
+ return fb->CheckFramebufferStatus().get();
+}
+
+RefPtr<WebGLProgram> WebGLContext::CreateProgram() {
+ const FuncScope funcScope(*this, "createProgram");
+ if (IsContextLost()) return nullptr;
+
+ return new WebGLProgram(this);
+}
+
+RefPtr<WebGLShader> WebGLContext::CreateShader(GLenum type) {
+ const FuncScope funcScope(*this, "createShader");
+ if (IsContextLost()) return nullptr;
+
+ if (type != LOCAL_GL_VERTEX_SHADER && type != LOCAL_GL_FRAGMENT_SHADER) {
+ ErrorInvalidEnumInfo("type", type);
+ return nullptr;
+ }
+
+ return new WebGLShader(this, type);
+}
+
+void WebGLContext::CullFace(GLenum face) {
+ const FuncScope funcScope(*this, "cullFace");
+ if (IsContextLost()) return;
+
+ if (!ValidateFaceEnum(face)) return;
+
+ gl->fCullFace(face);
+}
+
+void WebGLContext::DetachShader(WebGLProgram& prog, const WebGLShader& shader) {
+ FuncScope funcScope(*this, "detachShader");
+ if (IsContextLost()) return;
+ funcScope.mBindFailureGuard = true;
+
+ prog.DetachShader(shader);
+
+ funcScope.mBindFailureGuard = false;
+}
+
+static bool ValidateComparisonEnum(WebGLContext& webgl, const GLenum func) {
+ switch (func) {
+ case LOCAL_GL_NEVER:
+ case LOCAL_GL_LESS:
+ case LOCAL_GL_LEQUAL:
+ case LOCAL_GL_GREATER:
+ case LOCAL_GL_GEQUAL:
+ case LOCAL_GL_EQUAL:
+ case LOCAL_GL_NOTEQUAL:
+ case LOCAL_GL_ALWAYS:
+ return true;
+
+ default:
+ webgl.ErrorInvalidEnumInfo("func", func);
+ return false;
+ }
+}
+
+void WebGLContext::DepthFunc(GLenum func) {
+ const FuncScope funcScope(*this, "depthFunc");
+ if (IsContextLost()) return;
+
+ if (!ValidateComparisonEnum(*this, func)) return;
+
+ gl->fDepthFunc(func);
+}
+
+void WebGLContext::DepthRange(GLfloat zNear, GLfloat zFar) {
+ const FuncScope funcScope(*this, "depthRange");
+ if (IsContextLost()) return;
+
+ if (zNear > zFar)
+ return ErrorInvalidOperation(
+ "the near value is greater than the far value!");
+
+ gl->fDepthRange(zNear, zFar);
+}
+
+// -
+
+void WebGLContext::FramebufferAttach(const GLenum target,
+ const GLenum attachSlot,
+ const GLenum bindImageTarget,
+ const webgl::FbAttachInfo& toAttach) {
+ FuncScope funcScope(*this, "framebufferAttach");
+ funcScope.mBindFailureGuard = true;
+ const auto& limits = *mLimits;
+
+ if (!ValidateFramebufferTarget(target)) return;
+
+ auto fb = mBoundDrawFramebuffer;
+ if (target == LOCAL_GL_READ_FRAMEBUFFER) {
+ fb = mBoundReadFramebuffer;
+ }
+ if (!fb) return;
+
+ // `rb` needs no validation.
+
+ // `tex`
+ const auto& tex = toAttach.tex;
+ if (tex) {
+ const auto err = CheckFramebufferAttach(bindImageTarget, tex->mTarget.get(),
+ toAttach.mipLevel, toAttach.zLayer,
+ toAttach.zLayerCount, limits);
+ if (err) return;
+ }
+
+ auto safeToAttach = toAttach;
+ if (!toAttach.rb && !toAttach.tex) {
+ safeToAttach = {};
+ }
+ if (!IsWebGL2() &&
+ !IsExtensionEnabled(WebGLExtensionID::OES_fbo_render_mipmap)) {
+ safeToAttach.mipLevel = 0;
+ }
+ if (!IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) {
+ safeToAttach.isMultiview = false;
+ }
+
+ if (!fb->FramebufferAttach(attachSlot, safeToAttach)) return;
+
+ funcScope.mBindFailureGuard = false;
+}
+
+// -
+
+void WebGLContext::FrontFace(GLenum mode) {
+ const FuncScope funcScope(*this, "frontFace");
+ if (IsContextLost()) return;
+
+ switch (mode) {
+ case LOCAL_GL_CW:
+ case LOCAL_GL_CCW:
+ break;
+ default:
+ return ErrorInvalidEnumInfo("mode", mode);
+ }
+
+ gl->fFrontFace(mode);
+}
+
+Maybe<double> WebGLContext::GetBufferParameter(GLenum target, GLenum pname) {
+ const FuncScope funcScope(*this, "getBufferParameter");
+ if (IsContextLost()) return Nothing();
+
+ const auto& slot = ValidateBufferSlot(target);
+ if (!slot) return Nothing();
+ const auto& buffer = *slot;
+
+ if (!buffer) {
+ ErrorInvalidOperation("Buffer for `target` is null.");
+ return Nothing();
+ }
+
+ switch (pname) {
+ case LOCAL_GL_BUFFER_SIZE:
+ return Some(buffer->ByteLength());
+
+ case LOCAL_GL_BUFFER_USAGE:
+ return Some(buffer->Usage());
+
+ default:
+ ErrorInvalidEnumInfo("pname", pname);
+ return Nothing();
+ }
+}
+
+Maybe<double> WebGLContext::GetFramebufferAttachmentParameter(
+ WebGLFramebuffer* const fb, GLenum attachment, GLenum pname) const {
+ const FuncScope funcScope(*this, "getFramebufferAttachmentParameter");
+ if (IsContextLost()) return Nothing();
+
+ if (fb) return fb->GetAttachmentParameter(attachment, pname);
+
+ ////////////////////////////////////
+
+ if (!IsWebGL2()) {
+ ErrorInvalidOperation(
+ "Querying against the default framebuffer is not"
+ " allowed in WebGL 1.");
+ return Nothing();
+ }
+
+ switch (attachment) {
+ case LOCAL_GL_BACK:
+ case LOCAL_GL_DEPTH:
+ case LOCAL_GL_STENCIL:
+ break;
+
+ default:
+ ErrorInvalidEnum(
+ "For the default framebuffer, can only query COLOR, DEPTH,"
+ " or STENCIL.");
+ return Nothing();
+ }
+
+ switch (pname) {
+ case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
+ switch (attachment) {
+ case LOCAL_GL_BACK:
+ break;
+ case LOCAL_GL_DEPTH:
+ if (!mOptions.depth) {
+ return Some(LOCAL_GL_NONE);
+ }
+ break;
+ case LOCAL_GL_STENCIL:
+ if (!mOptions.stencil) {
+ return Some(LOCAL_GL_NONE);
+ }
+ break;
+ default:
+ ErrorInvalidEnum(
+ "With the default framebuffer, can only query COLOR, DEPTH,"
+ " or STENCIL for GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE");
+ return Nothing();
+ }
+ return Some(LOCAL_GL_FRAMEBUFFER_DEFAULT);
+
+ ////////////////
+
+ case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
+ case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
+ case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
+ if (attachment == LOCAL_GL_BACK) return Some(8);
+ return Some(0);
+
+ case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
+ if (attachment == LOCAL_GL_BACK) {
+ if (mOptions.alpha) {
+ return Some(8);
+ }
+ ErrorInvalidOperation(
+ "The default framebuffer doesn't contain an alpha buffer");
+ return Nothing();
+ }
+ return Some(0);
+
+ case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
+ if (attachment == LOCAL_GL_DEPTH) {
+ if (mOptions.depth) {
+ return Some(24);
+ }
+ ErrorInvalidOperation(
+ "The default framebuffer doesn't contain an depth buffer");
+ return Nothing();
+ }
+ return Some(0);
+
+ case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
+ if (attachment == LOCAL_GL_STENCIL) {
+ if (mOptions.stencil) {
+ return Some(8);
+ }
+ ErrorInvalidOperation(
+ "The default framebuffer doesn't contain an stencil buffer");
+ return Nothing();
+ }
+ return Some(0);
+
+ case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
+ if (attachment == LOCAL_GL_STENCIL) {
+ if (mOptions.stencil) {
+ return Some(LOCAL_GL_UNSIGNED_INT);
+ }
+ ErrorInvalidOperation(
+ "The default framebuffer doesn't contain an stencil buffer");
+ } else if (attachment == LOCAL_GL_DEPTH) {
+ if (mOptions.depth) {
+ return Some(LOCAL_GL_UNSIGNED_NORMALIZED);
+ }
+ ErrorInvalidOperation(
+ "The default framebuffer doesn't contain an depth buffer");
+ } else { // LOCAL_GL_BACK
+ return Some(LOCAL_GL_UNSIGNED_NORMALIZED);
+ }
+ return Nothing();
+
+ case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
+ if (attachment == LOCAL_GL_STENCIL) {
+ if (!mOptions.stencil) {
+ ErrorInvalidOperation(
+ "The default framebuffer doesn't contain an stencil buffer");
+ return Nothing();
+ }
+ } else if (attachment == LOCAL_GL_DEPTH) {
+ if (!mOptions.depth) {
+ ErrorInvalidOperation(
+ "The default framebuffer doesn't contain an depth buffer");
+ return Nothing();
+ }
+ }
+ return Some(LOCAL_GL_LINEAR);
+ }
+
+ ErrorInvalidEnumInfo("pname", pname);
+ return Nothing();
+}
+
+Maybe<double> WebGLContext::GetRenderbufferParameter(
+ const WebGLRenderbuffer& rb, GLenum pname) const {
+ const FuncScope funcScope(*this, "getRenderbufferParameter");
+ if (IsContextLost()) return Nothing();
+
+ switch (pname) {
+ case LOCAL_GL_RENDERBUFFER_SAMPLES:
+ if (!IsWebGL2()) break;
+ [[fallthrough]];
+
+ case LOCAL_GL_RENDERBUFFER_WIDTH:
+ case LOCAL_GL_RENDERBUFFER_HEIGHT:
+ case LOCAL_GL_RENDERBUFFER_RED_SIZE:
+ case LOCAL_GL_RENDERBUFFER_GREEN_SIZE:
+ case LOCAL_GL_RENDERBUFFER_BLUE_SIZE:
+ case LOCAL_GL_RENDERBUFFER_ALPHA_SIZE:
+ case LOCAL_GL_RENDERBUFFER_DEPTH_SIZE:
+ case LOCAL_GL_RENDERBUFFER_STENCIL_SIZE:
+ case LOCAL_GL_RENDERBUFFER_INTERNAL_FORMAT: {
+ // RB emulation means we have to ask the RB itself.
+ GLint i = rb.GetRenderbufferParameter(pname);
+ return Some(i);
+ }
+
+ default:
+ break;
+ }
+
+ ErrorInvalidEnumInfo("pname", pname);
+ return Nothing();
+}
+
+RefPtr<WebGLTexture> WebGLContext::CreateTexture() {
+ const FuncScope funcScope(*this, "createTexture");
+ if (IsContextLost()) return nullptr;
+
+ GLuint tex = 0;
+ gl->fGenTextures(1, &tex);
+
+ return new WebGLTexture(this, tex);
+}
+
+GLenum WebGLContext::GetError() {
+ const FuncScope funcScope(*this, "getError");
+
+ /* WebGL 1.0: Section 5.14.3: Setting and getting state:
+ * If the context's webgl context lost flag is set, returns
+ * CONTEXT_LOST_WEBGL the first time this method is called.
+ * Afterward, returns NO_ERROR until the context has been
+ * restored.
+ *
+ * WEBGL_lose_context:
+ * [When this extension is enabled: ] loseContext and
+ * restoreContext are allowed to generate INVALID_OPERATION errors
+ * even when the context is lost.
+ */
+
+ auto err = mWebGLError;
+ mWebGLError = 0;
+ if (IsContextLost() || err) // Must check IsContextLost in all flow paths.
+ return err;
+
+ // Either no WebGL-side error, or it's already been cleared.
+ // UnderlyingGL-side errors, now.
+ err = gl->fGetError();
+ if (gl->IsContextLost()) {
+ CheckForContextLoss();
+ return GetError();
+ }
+ MOZ_ASSERT(err != LOCAL_GL_CONTEXT_LOST);
+
+ if (err) {
+ GenerateWarning("Driver error unexpected by WebGL: 0x%04x", err);
+ // This might be:
+ // - INVALID_OPERATION from ANGLE due to incomplete RBAB implementation for
+ // DrawElements
+ // with DYNAMIC_DRAW index buffer.
+ }
+ return err;
+}
+
+webgl::GetUniformData WebGLContext::GetUniform(const WebGLProgram& prog,
+ const uint32_t loc) const {
+ const FuncScope funcScope(*this, "getUniform");
+ webgl::GetUniformData ret;
+ [&]() {
+ if (IsContextLost()) return;
+
+ const auto& info = prog.LinkInfo();
+ if (!info) return;
+
+ const auto locInfo = MaybeFind(info->locationMap, loc);
+ if (!locInfo) return;
+
+ ret.type = locInfo->info.info.elemType;
+ switch (ret.type) {
+ case LOCAL_GL_FLOAT:
+ case LOCAL_GL_FLOAT_VEC2:
+ case LOCAL_GL_FLOAT_VEC3:
+ case LOCAL_GL_FLOAT_VEC4:
+ case LOCAL_GL_FLOAT_MAT2:
+ case LOCAL_GL_FLOAT_MAT3:
+ case LOCAL_GL_FLOAT_MAT4:
+ case LOCAL_GL_FLOAT_MAT2x3:
+ case LOCAL_GL_FLOAT_MAT2x4:
+ case LOCAL_GL_FLOAT_MAT3x2:
+ case LOCAL_GL_FLOAT_MAT3x4:
+ case LOCAL_GL_FLOAT_MAT4x2:
+ case LOCAL_GL_FLOAT_MAT4x3:
+ gl->fGetUniformfv(prog.mGLName, loc,
+ reinterpret_cast<float*>(ret.data));
+ break;
+
+ case LOCAL_GL_INT:
+ case LOCAL_GL_INT_VEC2:
+ case LOCAL_GL_INT_VEC3:
+ case LOCAL_GL_INT_VEC4:
+ case LOCAL_GL_SAMPLER_2D:
+ case LOCAL_GL_SAMPLER_3D:
+ case LOCAL_GL_SAMPLER_CUBE:
+ case LOCAL_GL_SAMPLER_2D_SHADOW:
+ case LOCAL_GL_SAMPLER_2D_ARRAY:
+ case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+ case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+ case LOCAL_GL_INT_SAMPLER_2D:
+ case LOCAL_GL_INT_SAMPLER_3D:
+ case LOCAL_GL_INT_SAMPLER_CUBE:
+ case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+ case LOCAL_GL_BOOL:
+ case LOCAL_GL_BOOL_VEC2:
+ case LOCAL_GL_BOOL_VEC3:
+ case LOCAL_GL_BOOL_VEC4:
+ gl->fGetUniformiv(prog.mGLName, loc,
+ reinterpret_cast<int32_t*>(ret.data));
+ break;
+
+ case LOCAL_GL_UNSIGNED_INT:
+ case LOCAL_GL_UNSIGNED_INT_VEC2:
+ case LOCAL_GL_UNSIGNED_INT_VEC3:
+ case LOCAL_GL_UNSIGNED_INT_VEC4:
+ gl->fGetUniformuiv(prog.mGLName, loc,
+ reinterpret_cast<uint32_t*>(ret.data));
+ break;
+
+ default:
+ MOZ_CRASH("GFX: Invalid elemType.");
+ }
+ }();
+ return ret;
+}
+
+void WebGLContext::Hint(GLenum target, GLenum mode) {
+ const FuncScope funcScope(*this, "hint");
+ if (IsContextLost()) return;
+
+ switch (mode) {
+ case LOCAL_GL_FASTEST:
+ case LOCAL_GL_NICEST:
+ case LOCAL_GL_DONT_CARE:
+ break;
+ default:
+ return ErrorInvalidEnumArg("mode", mode);
+ }
+
+ // -
+
+ bool isValid = false;
+
+ switch (target) {
+ case LOCAL_GL_GENERATE_MIPMAP_HINT:
+ mGenerateMipmapHint = mode;
+ isValid = true;
+
+ // Deprecated and removed in desktop GL Core profiles.
+ if (gl->IsCoreProfile()) return;
+
+ break;
+
+ case LOCAL_GL_FRAGMENT_SHADER_DERIVATIVE_HINT:
+ if (IsWebGL2() ||
+ IsExtensionEnabled(WebGLExtensionID::OES_standard_derivatives)) {
+ isValid = true;
+ }
+ break;
+ }
+ if (!isValid) return ErrorInvalidEnumInfo("target", target);
+
+ // -
+
+ gl->fHint(target, mode);
+}
+
+// -
+
+void WebGLContext::LinkProgram(WebGLProgram& prog) {
+ const FuncScope funcScope(*this, "linkProgram");
+ if (IsContextLost()) return;
+
+ prog.LinkProgram();
+
+ if (&prog == mCurrentProgram) {
+ if (!prog.IsLinked()) {
+ // We use to simply early-out here, and preserve the GL behavior that
+ // failed relink doesn't invalidate the current active program link info.
+ // The new behavior was changed for WebGL here:
+ // https://github.com/KhronosGroup/WebGL/pull/3371
+ mActiveProgramLinkInfo = nullptr;
+ gl->fUseProgram(0); // Shouldn't be needed, but let's be safe.
+ return;
+ }
+ mActiveProgramLinkInfo = prog.LinkInfo();
+ gl->fUseProgram(prog.mGLName); // Uncontionally re-use.
+ // Previously, we needed this re-use on nvidia as a driver workaround,
+ // but we might as well do it unconditionally.
+ }
+}
+
+Maybe<webgl::ErrorInfo> SetPixelUnpack(
+ const bool isWebgl2, webgl::PixelUnpackStateWebgl* const unpacking,
+ const GLenum pname, const GLint param) {
+ if (isWebgl2) {
+ uint32_t* pValueSlot = nullptr;
+ switch (pname) {
+ case LOCAL_GL_UNPACK_IMAGE_HEIGHT:
+ pValueSlot = &unpacking->imageHeight;
+ break;
+
+ case LOCAL_GL_UNPACK_SKIP_IMAGES:
+ pValueSlot = &unpacking->skipImages;
+ break;
+
+ case LOCAL_GL_UNPACK_ROW_LENGTH:
+ pValueSlot = &unpacking->rowLength;
+ break;
+
+ case LOCAL_GL_UNPACK_SKIP_ROWS:
+ pValueSlot = &unpacking->skipRows;
+ break;
+
+ case LOCAL_GL_UNPACK_SKIP_PIXELS:
+ pValueSlot = &unpacking->skipPixels;
+ break;
+ }
+
+ if (pValueSlot) {
+ *pValueSlot = static_cast<uint32_t>(param);
+ return {};
+ }
+ }
+
+ switch (pname) {
+ case dom::WebGLRenderingContext_Binding::UNPACK_FLIP_Y_WEBGL:
+ unpacking->flipY = bool(param);
+ return {};
+
+ case dom::WebGLRenderingContext_Binding::UNPACK_PREMULTIPLY_ALPHA_WEBGL:
+ unpacking->premultiplyAlpha = bool(param);
+ return {};
+
+ case dom::WebGLRenderingContext_Binding::UNPACK_COLORSPACE_CONVERSION_WEBGL:
+ switch (param) {
+ case LOCAL_GL_NONE:
+ case dom::WebGLRenderingContext_Binding::BROWSER_DEFAULT_WEBGL:
+ break;
+
+ default: {
+ const nsPrintfCString text("Bad UNPACK_COLORSPACE_CONVERSION: %s",
+ EnumString(param).c_str());
+ return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE, ToString(text)});
+ }
+ }
+ unpacking->colorspaceConversion = param;
+ return {};
+
+ case dom::MOZ_debug_Binding::UNPACK_REQUIRE_FASTPATH:
+ unpacking->requireFastPath = bool(param);
+ return {};
+
+ case LOCAL_GL_UNPACK_ALIGNMENT:
+ switch (param) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ break;
+
+ default: {
+ const nsPrintfCString text(
+ "UNPACK_ALIGNMENT must be [1,2,4,8], was %i", param);
+ return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_VALUE, ToString(text)});
+ }
+ }
+ unpacking->alignmentInTypeElems = param;
+ return {};
+
+ default:
+ break;
+ }
+ const nsPrintfCString text("Bad `pname`: %s", EnumString(pname).c_str());
+ return Some(webgl::ErrorInfo{LOCAL_GL_INVALID_ENUM, ToString(text)});
+}
+
+bool WebGLContext::DoReadPixelsAndConvert(
+ const webgl::FormatInfo* const srcFormat, const webgl::ReadPixelsDesc& desc,
+ const uintptr_t dest, const uint64_t destSize, const uint32_t rowStride) {
+ const auto& x = desc.srcOffset.x;
+ const auto& y = desc.srcOffset.y;
+ const auto size = *ivec2::From(desc.size);
+ const auto& pi = desc.pi;
+
+ // On at least Win+NV, we'll get PBO errors if we don't have at least
+ // `rowStride * height` bytes available to read into.
+ const auto naiveBytesNeeded = CheckedInt<uint64_t>(rowStride) * size.y;
+ const bool isDangerCloseToEdge =
+ (!naiveBytesNeeded.isValid() || naiveBytesNeeded.value() > destSize);
+ const bool useParanoidHandling =
+ (gl->WorkAroundDriverBugs() && isDangerCloseToEdge &&
+ mBoundPixelPackBuffer);
+ if (!useParanoidHandling) {
+ gl->fReadPixels(x, y, size.x, size.y, pi.format, pi.type,
+ reinterpret_cast<void*>(dest));
+ return true;
+ }
+
+ // Read everything but the last row.
+ const auto bodyHeight = size.y - 1;
+ if (bodyHeight) {
+ gl->fReadPixels(x, y, size.x, bodyHeight, pi.format, pi.type,
+ reinterpret_cast<void*>(dest));
+ }
+
+ // Now read the last row.
+ gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
+ gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, 0);
+ gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, 0);
+
+ const auto tailRowOffset =
+ reinterpret_cast<uint8_t*>(dest) + rowStride * bodyHeight;
+ gl->fReadPixels(x, y + bodyHeight, size.x, 1, pi.format, pi.type,
+ tailRowOffset);
+
+ return true;
+}
+
+webgl::ReadPixelsResult WebGLContext::ReadPixelsInto(
+ const webgl::ReadPixelsDesc& desc, const Range<uint8_t>& dest) {
+ const FuncScope funcScope(*this, "readPixels");
+ if (IsContextLost()) return {};
+
+ if (mBoundPixelPackBuffer) {
+ ErrorInvalidOperation("PIXEL_PACK_BUFFER must be null.");
+ return {};
+ }
+
+ return ReadPixelsImpl(desc, reinterpret_cast<uintptr_t>(dest.begin().get()),
+ dest.length());
+}
+
+void WebGLContext::ReadPixelsPbo(const webgl::ReadPixelsDesc& desc,
+ const uint64_t offset) {
+ const FuncScope funcScope(*this, "readPixels");
+ if (IsContextLost()) return;
+
+ const auto& buffer = ValidateBufferSelection(LOCAL_GL_PIXEL_PACK_BUFFER);
+ if (!buffer) return;
+
+ //////
+
+ {
+ const auto pii = webgl::PackingInfoInfo::For(desc.pi);
+ if (!pii) {
+ GLenum err = LOCAL_GL_INVALID_OPERATION;
+ if (!desc.pi.format || !desc.pi.type) {
+ err = LOCAL_GL_INVALID_ENUM;
+ }
+ GenerateError(err, "`format` (%s) and/or `type` (%s) not acceptable.",
+ EnumString(desc.pi.format).c_str(),
+ EnumString(desc.pi.type).c_str());
+ return;
+ }
+
+ if (offset % pii->bytesPerElement != 0) {
+ ErrorInvalidOperation(
+ "`offset` must be divisible by the size of `type`"
+ " in bytes.");
+ return;
+ }
+ }
+
+ //////
+
+ auto bytesAvailable = buffer->ByteLength();
+ if (offset > bytesAvailable) {
+ ErrorInvalidOperation("`offset` too large for bound PIXEL_PACK_BUFFER.");
+ return;
+ }
+ bytesAvailable -= offset;
+
+ // -
+
+ const ScopedLazyBind lazyBind(gl, LOCAL_GL_PIXEL_PACK_BUFFER, buffer);
+
+ ReadPixelsImpl(desc, offset, bytesAvailable);
+
+ buffer->ResetLastUpdateFenceId();
+}
+
+static webgl::PackingInfo DefaultReadPixelPI(
+ const webgl::FormatUsageInfo* usage) {
+ MOZ_ASSERT(usage->IsRenderable());
+ const auto& format = *usage->format;
+ switch (format.componentType) {
+ case webgl::ComponentType::NormUInt:
+ if (format.r == 16) {
+ return {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_SHORT};
+ }
+ return {LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE};
+
+ case webgl::ComponentType::Int:
+ return {LOCAL_GL_RGBA_INTEGER, LOCAL_GL_INT};
+
+ case webgl::ComponentType::UInt:
+ return {LOCAL_GL_RGBA_INTEGER, LOCAL_GL_UNSIGNED_INT};
+
+ case webgl::ComponentType::Float:
+ return {LOCAL_GL_RGBA, LOCAL_GL_FLOAT};
+
+ default:
+ MOZ_CRASH();
+ }
+}
+
+static bool ArePossiblePackEnums(const WebGLContext* webgl,
+ const webgl::PackingInfo& pi) {
+ // OpenGL ES 2.0 $4.3.1 - IMPLEMENTATION_COLOR_READ_{TYPE/FORMAT} is a valid
+ // combination for glReadPixels()...
+
+ // Only valid when pulled from:
+ // * GLES 2.0.25 p105:
+ // "table 3.4, excluding formats LUMINANCE and LUMINANCE_ALPHA."
+ // * GLES 3.0.4 p193:
+ // "table 3.2, excluding formats DEPTH_COMPONENT and DEPTH_STENCIL."
+ switch (pi.format) {
+ case LOCAL_GL_LUMINANCE:
+ case LOCAL_GL_LUMINANCE_ALPHA:
+ case LOCAL_GL_DEPTH_COMPONENT:
+ case LOCAL_GL_DEPTH_STENCIL:
+ return false;
+ }
+
+ if (pi.type == LOCAL_GL_UNSIGNED_INT_24_8) return false;
+
+ const auto pii = webgl::PackingInfoInfo::For(pi);
+ if (!pii) return false;
+
+ return true;
+}
+
+webgl::PackingInfo WebGLContext::ValidImplementationColorReadPI(
+ const webgl::FormatUsageInfo* usage) const {
+ const auto defaultPI = DefaultReadPixelPI(usage);
+
+ // ES2_compatibility always returns RGBA/UNSIGNED_BYTE, so branch on actual
+ // IsGLES(). Also OSX+NV generates an error here.
+ if (!gl->IsGLES()) return defaultPI;
+
+ webgl::PackingInfo implPI;
+ gl->fGetIntegerv(LOCAL_GL_IMPLEMENTATION_COLOR_READ_FORMAT,
+ (GLint*)&implPI.format);
+ gl->fGetIntegerv(LOCAL_GL_IMPLEMENTATION_COLOR_READ_TYPE,
+ (GLint*)&implPI.type);
+
+ if (!ArePossiblePackEnums(this, implPI)) return defaultPI;
+
+ return implPI;
+}
+
+static bool ValidateReadPixelsFormatAndType(
+ const webgl::FormatUsageInfo* srcUsage, const webgl::PackingInfo& pi,
+ gl::GLContext* gl, WebGLContext* webgl) {
+ if (!ArePossiblePackEnums(webgl, pi)) {
+ webgl->ErrorInvalidEnum("Unexpected format or type.");
+ return false;
+ }
+
+ const auto defaultPI = DefaultReadPixelPI(srcUsage);
+ if (pi == defaultPI) return true;
+
+ ////
+
+ // OpenGL ES 3.0.4 p194 - When the internal format of the rendering surface is
+ // RGB10_A2, a third combination of format RGBA and type
+ // UNSIGNED_INT_2_10_10_10_REV is accepted.
+
+ if (webgl->IsWebGL2() &&
+ srcUsage->format->effectiveFormat == webgl::EffectiveFormat::RGB10_A2 &&
+ pi.format == LOCAL_GL_RGBA &&
+ pi.type == LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV) {
+ return true;
+ }
+
+ ////
+
+ MOZ_ASSERT(gl->IsCurrent());
+ const auto implPI = webgl->ValidImplementationColorReadPI(srcUsage);
+ if (pi == implPI) return true;
+
+ ////
+
+ // clang-format off
+ webgl->ErrorInvalidOperation(
+ "Format and type %s/%s incompatible with this %s attachment."
+ " This framebuffer requires either %s/%s or"
+ " getParameter(IMPLEMENTATION_COLOR_READ_FORMAT/_TYPE) %s/%s.",
+ EnumString(pi.format).c_str(), EnumString(pi.type).c_str(),
+ srcUsage->format->name,
+ EnumString(defaultPI.format).c_str(), EnumString(defaultPI.type).c_str(),
+ EnumString(implPI.format).c_str(), EnumString(implPI.type).c_str());
+ // clang-format on
+
+ return false;
+}
+
+webgl::ReadPixelsResult WebGLContext::ReadPixelsImpl(
+ const webgl::ReadPixelsDesc& desc, const uintptr_t dest,
+ const uint64_t availBytes) {
+ const webgl::FormatUsageInfo* srcFormat;
+ uint32_t srcWidth;
+ uint32_t srcHeight;
+ if (!BindCurFBForColorRead(&srcFormat, &srcWidth, &srcHeight)) return {};
+
+ //////
+
+ if (!ValidateReadPixelsFormatAndType(srcFormat, desc.pi, gl, this)) return {};
+
+ //////
+
+ const auto& srcOffset = desc.srcOffset;
+ const auto& size = desc.size;
+
+ if (!ivec2::From(size)) {
+ ErrorInvalidValue("width and height must be non-negative.");
+ return {};
+ }
+
+ const auto& packing = desc.packState;
+ const auto explicitPackingRes = webgl::ExplicitPixelPackingState::ForUseWith(
+ packing, LOCAL_GL_TEXTURE_2D, {size.x, size.y, 1}, desc.pi, {});
+ if (!explicitPackingRes.isOk()) {
+ ErrorInvalidOperation("%s", explicitPackingRes.inspectErr().c_str());
+ return {};
+ }
+ const auto& explicitPacking = explicitPackingRes.inspect();
+ const auto& rowStride = explicitPacking.metrics.bytesPerRowStride;
+ const auto& bytesNeeded = explicitPacking.metrics.totalBytesUsed;
+ if (bytesNeeded > availBytes) {
+ ErrorInvalidOperation("buffer too small");
+ return {};
+ }
+
+ ////
+
+ int32_t readX, readY;
+ int32_t writeX, writeY;
+ int32_t rwWidth, rwHeight;
+ if (!Intersect(srcWidth, srcOffset.x, size.x, &readX, &writeX, &rwWidth) ||
+ !Intersect(srcHeight, srcOffset.y, size.y, &readY, &writeY, &rwHeight)) {
+ ErrorOutOfMemory("Bad subrect selection.");
+ return {};
+ }
+
+ ////////////////
+ // Now that the errors are out of the way, on to actually reading!
+
+ gl->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, packing.alignmentInTypeElems);
+ if (IsWebGL2()) {
+ gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, packing.rowLength);
+ gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, packing.skipPixels);
+ gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, packing.skipRows);
+ }
+
+ if (!rwWidth || !rwHeight) {
+ // Disjoint rects, so we're done already.
+ DummyReadFramebufferOperation();
+ return {};
+ }
+ const auto rwSize = *uvec2::From(rwWidth, rwHeight);
+
+ const auto res = webgl::ReadPixelsResult{
+ {{writeX, writeY}, {rwSize.x, rwSize.y}}, rowStride};
+
+ if (rwSize == size) {
+ DoReadPixelsAndConvert(srcFormat->format, desc, dest, bytesNeeded,
+ rowStride);
+ return res;
+ }
+
+ // Read request contains out-of-bounds pixels. Unfortunately:
+ // GLES 3.0.4 p194 "Obtaining Pixels from the Framebuffer":
+ // "If any of these pixels lies outside of the window allocated to the current
+ // GL context, or outside of the image attached to the currently bound
+ // framebuffer object, then the values obtained for those pixels are
+ // undefined."
+
+ // This is a slow-path, so warn people away!
+ GenerateWarning(
+ "Out-of-bounds reads with readPixels are deprecated, and"
+ " may be slow.");
+
+ ////////////////////////////////////
+ // Read only the in-bounds pixels.
+
+ if (IsWebGL2()) {
+ if (!packing.rowLength) {
+ gl->fPixelStorei(LOCAL_GL_PACK_ROW_LENGTH, packing.skipPixels + size.x);
+ }
+ gl->fPixelStorei(LOCAL_GL_PACK_SKIP_PIXELS, packing.skipPixels + writeX);
+ gl->fPixelStorei(LOCAL_GL_PACK_SKIP_ROWS, packing.skipRows + writeY);
+
+ auto desc2 = desc;
+ desc2.srcOffset = {readX, readY};
+ desc2.size = rwSize;
+
+ DoReadPixelsAndConvert(srcFormat->format, desc2, dest, bytesNeeded,
+ rowStride);
+ } else {
+ // I *did* say "hilariously slow".
+
+ auto desc2 = desc;
+ desc2.srcOffset = {readX, readY};
+ desc2.size = {rwSize.x, 1};
+
+ const auto skipBytes = writeX * explicitPacking.metrics.bytesPerPixel;
+ const auto usedRowBytes = rwSize.x * explicitPacking.metrics.bytesPerPixel;
+ for (const auto j : IntegerRange(rwSize.y)) {
+ desc2.srcOffset.y = readY + j;
+ const auto destWriteBegin = dest + skipBytes + (writeY + j) * rowStride;
+ MOZ_RELEASE_ASSERT(dest <= destWriteBegin);
+ MOZ_RELEASE_ASSERT(destWriteBegin <= dest + availBytes);
+
+ const auto destWriteEnd = destWriteBegin + usedRowBytes;
+ MOZ_RELEASE_ASSERT(dest <= destWriteEnd);
+ MOZ_RELEASE_ASSERT(destWriteEnd <= dest + availBytes);
+
+ DoReadPixelsAndConvert(srcFormat->format, desc2, destWriteBegin,
+ destWriteEnd - destWriteBegin, rowStride);
+ }
+ }
+
+ return res;
+}
+
+void WebGLContext::RenderbufferStorageMultisample(WebGLRenderbuffer& rb,
+ uint32_t samples,
+ GLenum internalFormat,
+ uint32_t width,
+ uint32_t height) const {
+ const FuncScope funcScope(*this, "renderbufferStorage(Multisample)?");
+ if (IsContextLost()) return;
+
+ rb.RenderbufferStorage(samples, internalFormat, width, height);
+}
+
+void WebGLContext::Scissor(GLint x, GLint y, GLsizei width, GLsizei height) {
+ const FuncScope funcScope(*this, "scissor");
+ if (IsContextLost()) return;
+
+ if (!ValidateNonNegative("width", width) ||
+ !ValidateNonNegative("height", height)) {
+ return;
+ }
+
+ mScissorRect = {x, y, width, height};
+ mScissorRect.Apply(*gl);
+}
+
+void WebGLContext::StencilFuncSeparate(GLenum face, GLenum func, GLint ref,
+ GLuint mask) {
+ const FuncScope funcScope(*this, "stencilFuncSeparate");
+ if (IsContextLost()) return;
+
+ if (!ValidateFaceEnum(face) || !ValidateComparisonEnum(*this, func)) {
+ return;
+ }
+
+ switch (face) {
+ case LOCAL_GL_FRONT_AND_BACK:
+ mStencilRefFront = ref;
+ mStencilRefBack = ref;
+ mStencilValueMaskFront = mask;
+ mStencilValueMaskBack = mask;
+ break;
+ case LOCAL_GL_FRONT:
+ mStencilRefFront = ref;
+ mStencilValueMaskFront = mask;
+ break;
+ case LOCAL_GL_BACK:
+ mStencilRefBack = ref;
+ mStencilValueMaskBack = mask;
+ break;
+ }
+
+ gl->fStencilFuncSeparate(face, func, ref, mask);
+}
+
+void WebGLContext::StencilOpSeparate(GLenum face, GLenum sfail, GLenum dpfail,
+ GLenum dppass) {
+ const FuncScope funcScope(*this, "stencilOpSeparate");
+ if (IsContextLost()) return;
+
+ if (!ValidateFaceEnum(face) || !ValidateStencilOpEnum(sfail, "sfail") ||
+ !ValidateStencilOpEnum(dpfail, "dpfail") ||
+ !ValidateStencilOpEnum(dppass, "dppass"))
+ return;
+
+ gl->fStencilOpSeparate(face, sfail, dpfail, dppass);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Uniform setters.
+
+void WebGLContext::UniformData(
+ const uint32_t loc, const bool transpose,
+ const Range<const webgl::UniformDataVal>& data) const {
+ const FuncScope funcScope(*this, "uniform setter");
+
+ if (!IsWebGL2() && transpose) {
+ GenerateError(LOCAL_GL_INVALID_VALUE, "`transpose`:true requires WebGL 2.");
+ return;
+ }
+
+ // -
+
+ const auto& link = mActiveProgramLinkInfo;
+ if (!link) return;
+
+ const auto locInfo = MaybeFind(link->locationMap, loc);
+ if (!locInfo) {
+ // Null WebGLUniformLocations become -1, which will end up here.
+ return;
+ }
+
+ const auto& validationInfo = locInfo->info;
+ const auto& activeInfo = validationInfo.info;
+ const auto& channels = validationInfo.channelsPerElem;
+ const auto& pfn = validationInfo.pfn;
+
+ // -
+
+ const auto lengthInType = data.length();
+ const auto elemCount = lengthInType / channels;
+ if (elemCount > 1 && !validationInfo.isArray) {
+ GenerateError(
+ LOCAL_GL_INVALID_OPERATION,
+ "(uniform %s) `values` length (%u) must exactly match size of %s.",
+ activeInfo.name.c_str(), lengthInType,
+ EnumString(activeInfo.elemType).c_str());
+ return;
+ }
+
+ // -
+
+ const auto& samplerInfo = locInfo->samplerInfo;
+ if (samplerInfo) {
+ const auto idata = reinterpret_cast<const uint32_t*>(data.begin().get());
+ const auto maxTexUnits = GLMaxTextureUnits();
+ for (const auto& val : Range<const uint32_t>(idata, elemCount)) {
+ if (val >= maxTexUnits) {
+ ErrorInvalidValue(
+ "This uniform location is a sampler, but %d"
+ " is not a valid texture unit.",
+ val);
+ return;
+ }
+ }
+ }
+
+ // -
+
+ // This is a little galaxy-brain, sorry!
+ const auto ptr = static_cast<const void*>(data.begin().get());
+ (*pfn)(*gl, static_cast<GLint>(loc), elemCount, transpose, ptr);
+
+ // -
+
+ if (samplerInfo) {
+ auto& texUnits = samplerInfo->texUnits;
+
+ const auto srcBegin = reinterpret_cast<const uint32_t*>(data.begin().get());
+ auto destIndex = locInfo->indexIntoUniform;
+ if (destIndex < texUnits.length()) {
+ // Only sample as many indexes as available tex units allow.
+ const auto destCount = std::min(elemCount, texUnits.length() - destIndex);
+ for (const auto& val : Range<const uint32_t>(srcBegin, destCount)) {
+ texUnits[destIndex] = AssertedCast<uint8_t>(val);
+ destIndex += 1;
+ }
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void WebGLContext::UseProgram(WebGLProgram* prog) {
+ FuncScope funcScope(*this, "useProgram");
+ if (IsContextLost()) return;
+ funcScope.mBindFailureGuard = true;
+
+ if (!prog) {
+ mCurrentProgram = nullptr;
+ mActiveProgramLinkInfo = nullptr;
+ funcScope.mBindFailureGuard = false;
+ return;
+ }
+
+ if (!ValidateObject("prog", *prog)) return;
+
+ if (!prog->UseProgram()) return;
+
+ mCurrentProgram = prog;
+ mActiveProgramLinkInfo = mCurrentProgram->LinkInfo();
+
+ funcScope.mBindFailureGuard = false;
+}
+
+bool WebGLContext::ValidateProgram(const WebGLProgram& prog) const {
+ const FuncScope funcScope(*this, "validateProgram");
+ if (IsContextLost()) return false;
+
+ return prog.ValidateProgram();
+}
+
+RefPtr<WebGLFramebuffer> WebGLContext::CreateFramebuffer() {
+ const FuncScope funcScope(*this, "createFramebuffer");
+ if (IsContextLost()) return nullptr;
+
+ GLuint fbo = 0;
+ gl->fGenFramebuffers(1, &fbo);
+
+ return new WebGLFramebuffer(this, fbo);
+}
+
+RefPtr<WebGLFramebuffer> WebGLContext::CreateOpaqueFramebuffer(
+ const webgl::OpaqueFramebufferOptions& options) {
+ const FuncScope funcScope(*this, "createOpaqueFramebuffer");
+ if (IsContextLost()) return nullptr;
+
+ uint32_t samples = options.antialias ? StaticPrefs::webgl_msaa_samples() : 0;
+ samples = std::min(samples, gl->MaxSamples());
+ const gfx::IntSize size = {options.width, options.height};
+
+ auto fbo =
+ gl::MozFramebuffer::Create(gl, size, samples, options.depthStencil);
+ if (!fbo) {
+ return nullptr;
+ }
+
+ return new WebGLFramebuffer(this, std::move(fbo));
+}
+
+RefPtr<WebGLRenderbuffer> WebGLContext::CreateRenderbuffer() {
+ const FuncScope funcScope(*this, "createRenderbuffer");
+ if (IsContextLost()) return nullptr;
+
+ return new WebGLRenderbuffer(this);
+}
+
+void WebGLContext::Viewport(GLint x, GLint y, GLsizei width, GLsizei height) {
+ const FuncScope funcScope(*this, "viewport");
+ if (IsContextLost()) return;
+
+ if (!ValidateNonNegative("width", width) ||
+ !ValidateNonNegative("height", height)) {
+ return;
+ }
+
+ const auto& limits = Limits();
+ width = std::min(width, static_cast<GLsizei>(limits.maxViewportDim));
+ height = std::min(height, static_cast<GLsizei>(limits.maxViewportDim));
+
+ gl->fViewport(x, y, width, height);
+
+ mViewportX = x;
+ mViewportY = y;
+ mViewportWidth = width;
+ mViewportHeight = height;
+}
+
+void WebGLContext::CompileShader(WebGLShader& shader) {
+ const FuncScope funcScope(*this, "compileShader");
+ if (IsContextLost()) return;
+
+ if (!ValidateObject("shader", shader)) return;
+
+ shader.CompileShader();
+}
+
+Maybe<webgl::ShaderPrecisionFormat> WebGLContext::GetShaderPrecisionFormat(
+ GLenum shadertype, GLenum precisiontype) const {
+ const FuncScope funcScope(*this, "getShaderPrecisionFormat");
+ if (IsContextLost()) return Nothing();
+
+ switch (shadertype) {
+ case LOCAL_GL_FRAGMENT_SHADER:
+ case LOCAL_GL_VERTEX_SHADER:
+ break;
+ default:
+ ErrorInvalidEnumInfo("shadertype", shadertype);
+ return Nothing();
+ }
+
+ switch (precisiontype) {
+ case LOCAL_GL_LOW_FLOAT:
+ case LOCAL_GL_MEDIUM_FLOAT:
+ case LOCAL_GL_HIGH_FLOAT:
+ case LOCAL_GL_LOW_INT:
+ case LOCAL_GL_MEDIUM_INT:
+ case LOCAL_GL_HIGH_INT:
+ break;
+ default:
+ ErrorInvalidEnumInfo("precisiontype", precisiontype);
+ return Nothing();
+ }
+
+ GLint range[2], precision;
+
+ if (mDisableFragHighP && shadertype == LOCAL_GL_FRAGMENT_SHADER &&
+ (precisiontype == LOCAL_GL_HIGH_FLOAT ||
+ precisiontype == LOCAL_GL_HIGH_INT)) {
+ precision = 0;
+ range[0] = 0;
+ range[1] = 0;
+ } else {
+ gl->fGetShaderPrecisionFormat(shadertype, precisiontype, range, &precision);
+ }
+
+ return Some(webgl::ShaderPrecisionFormat{range[0], range[1], precision});
+}
+
+void WebGLContext::ShaderSource(WebGLShader& shader,
+ const std::string& source) const {
+ const FuncScope funcScope(*this, "shaderSource");
+ if (IsContextLost()) return;
+
+ shader.ShaderSource(source);
+}
+
+void WebGLContext::BlendColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) {
+ const FuncScope funcScope(*this, "blendColor");
+ if (IsContextLost()) return;
+
+ gl->fBlendColor(r, g, b, a);
+}
+
+void WebGLContext::Flush() {
+ const FuncScope funcScope(*this, "flush");
+ if (IsContextLost()) return;
+
+ gl->fFlush();
+}
+
+void WebGLContext::Finish() {
+ const FuncScope funcScope(*this, "finish");
+ if (IsContextLost()) return;
+
+ gl->fFinish();
+
+ mCompletedFenceId = mNextFenceId;
+ mNextFenceId += 1;
+}
+
+void WebGLContext::LineWidth(GLfloat width) {
+ const FuncScope funcScope(*this, "lineWidth");
+ if (IsContextLost()) return;
+
+ // Doing it this way instead of `if (width <= 0.0)` handles NaNs.
+ const bool isValid = width > 0.0;
+ if (!isValid) {
+ ErrorInvalidValue("`width` must be positive and non-zero.");
+ return;
+ }
+
+ mLineWidth = width;
+
+ if (gl->IsCoreProfile() && width > 1.0) {
+ width = 1.0;
+ }
+
+ gl->fLineWidth(width);
+}
+
+void WebGLContext::PolygonOffset(GLfloat factor, GLfloat units) {
+ const FuncScope funcScope(*this, "polygonOffset");
+ if (IsContextLost()) return;
+
+ gl->fPolygonOffset(factor, units);
+}
+
+void WebGLContext::ProvokingVertex(const webgl::ProvokingVertex mode) const {
+ const FuncScope funcScope(*this, "provokingVertex");
+ if (IsContextLost()) return;
+ MOZ_RELEASE_ASSERT(
+ IsExtensionEnabled(WebGLExtensionID::WEBGL_provoking_vertex));
+
+ gl->fProvokingVertex(UnderlyingValue(mode));
+}
+
+void WebGLContext::SampleCoverage(GLclampf value, WebGLboolean invert) {
+ const FuncScope funcScope(*this, "sampleCoverage");
+ if (IsContextLost()) return;
+
+ gl->fSampleCoverage(value, invert);
+}
+
+} // namespace mozilla