summaryrefslogtreecommitdiffstats
path: root/dom/canvas/WebGLProgram.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/WebGLProgram.cpp
parentInitial commit. (diff)
downloadfirefox-esr-37a0381f8351b370577b65028ba1f6563ae23fdf.tar.xz
firefox-esr-37a0381f8351b370577b65028ba1f6563ae23fdf.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/WebGLProgram.cpp')
-rw-r--r--dom/canvas/WebGLProgram.cpp1213
1 files changed, 1213 insertions, 0 deletions
diff --git a/dom/canvas/WebGLProgram.cpp b/dom/canvas/WebGLProgram.cpp
new file mode 100644
index 0000000000..f29d3d1c14
--- /dev/null
+++ b/dom/canvas/WebGLProgram.cpp
@@ -0,0 +1,1213 @@
+/* -*- Mode: C++; tab-width: 20; 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 "WebGLProgram.h"
+
+#include "GLContext.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/dom/WebGL2RenderingContextBinding.h"
+#include "mozilla/dom/WebGLRenderingContextBinding.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "nsPrintfCString.h"
+#include "WebGLBuffer.h"
+#include "WebGLContext.h"
+#include "WebGLShader.h"
+#include "WebGLShaderValidator.h"
+#include "WebGLTransformFeedback.h"
+#include "WebGLValidateStrings.h"
+#include "WebGLVertexArray.h"
+
+namespace mozilla {
+
+static bool IsShadowSampler(const GLenum elemType) {
+ switch (elemType) {
+ case LOCAL_GL_SAMPLER_2D_SHADOW:
+ case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+ case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static Maybe<webgl::TextureBaseType> SamplerBaseType(const GLenum elemType) {
+ switch (elemType) {
+ case LOCAL_GL_SAMPLER_2D:
+ case LOCAL_GL_SAMPLER_3D:
+ case LOCAL_GL_SAMPLER_CUBE:
+ case LOCAL_GL_SAMPLER_2D_ARRAY:
+ case LOCAL_GL_SAMPLER_2D_SHADOW:
+ case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+ case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+ return Some(webgl::TextureBaseType::Float);
+
+ 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:
+ return Some(webgl::TextureBaseType::Int);
+
+ 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:
+ return Some(webgl::TextureBaseType::UInt);
+
+ default:
+ return {};
+ }
+}
+
+//////////
+
+static webgl::TextureBaseType FragOutputBaseType(const GLenum type) {
+ switch (type) {
+ case LOCAL_GL_FLOAT:
+ case LOCAL_GL_FLOAT_VEC2:
+ case LOCAL_GL_FLOAT_VEC3:
+ case LOCAL_GL_FLOAT_VEC4:
+ return webgl::TextureBaseType::Float;
+
+ case LOCAL_GL_INT:
+ case LOCAL_GL_INT_VEC2:
+ case LOCAL_GL_INT_VEC3:
+ case LOCAL_GL_INT_VEC4:
+ return webgl::TextureBaseType::Int;
+
+ case LOCAL_GL_UNSIGNED_INT:
+ case LOCAL_GL_UNSIGNED_INT_VEC2:
+ case LOCAL_GL_UNSIGNED_INT_VEC3:
+ case LOCAL_GL_UNSIGNED_INT_VEC4:
+ return webgl::TextureBaseType::UInt;
+
+ default:
+ break;
+ }
+
+ const auto& str = EnumString(type);
+ gfxCriticalError() << "Unhandled enum for FragOutputBaseType: "
+ << str.c_str();
+ return webgl::TextureBaseType::Float;
+}
+
+// -----------------------------------------
+
+namespace webgl {
+
+void UniformAs1fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform1fv(location, count, static_cast<const float*>(any));
+}
+void UniformAs2fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform2fv(location, count, static_cast<const float*>(any));
+}
+void UniformAs3fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform3fv(location, count, static_cast<const float*>(any));
+}
+void UniformAs4fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform4fv(location, count, static_cast<const float*>(any));
+}
+
+void UniformAs1iv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform1iv(location, count, static_cast<const int32_t*>(any));
+}
+void UniformAs2iv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform2iv(location, count, static_cast<const int32_t*>(any));
+}
+void UniformAs3iv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform3iv(location, count, static_cast<const int32_t*>(any));
+}
+void UniformAs4iv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform4iv(location, count, static_cast<const int32_t*>(any));
+}
+
+void UniformAs1uiv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform1uiv(location, count, static_cast<const uint32_t*>(any));
+}
+void UniformAs2uiv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform2uiv(location, count, static_cast<const uint32_t*>(any));
+}
+void UniformAs3uiv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform3uiv(location, count, static_cast<const uint32_t*>(any));
+}
+void UniformAs4uiv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniform4uiv(location, count, static_cast<const uint32_t*>(any));
+}
+
+void UniformAsMatrix2x2fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniformMatrix2fv(location, count, transpose,
+ static_cast<const float*>(any));
+}
+void UniformAsMatrix2x3fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniformMatrix2x3fv(location, count, transpose,
+ static_cast<const float*>(any));
+}
+void UniformAsMatrix2x4fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniformMatrix2x4fv(location, count, transpose,
+ static_cast<const float*>(any));
+}
+
+void UniformAsMatrix3x2fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniformMatrix3x2fv(location, count, transpose,
+ static_cast<const float*>(any));
+}
+void UniformAsMatrix3x3fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniformMatrix3fv(location, count, transpose,
+ static_cast<const float*>(any));
+}
+void UniformAsMatrix3x4fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniformMatrix3x4fv(location, count, transpose,
+ static_cast<const float*>(any));
+}
+
+void UniformAsMatrix4x2fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniformMatrix4x2fv(location, count, transpose,
+ static_cast<const float*>(any));
+}
+void UniformAsMatrix4x3fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniformMatrix4x3fv(location, count, transpose,
+ static_cast<const float*>(any));
+}
+void UniformAsMatrix4x4fv(gl::GLContext& gl, GLint location, GLsizei count,
+ bool transpose, const void* any) {
+ gl.fUniformMatrix4fv(location, count, transpose,
+ static_cast<const float*>(any));
+}
+
+// -
+
+static bool EndsWith(const std::string& str, const std::string& needle) {
+ if (str.length() < needle.length()) return false;
+ return str.compare(str.length() - needle.length(), needle.length(), needle) ==
+ 0;
+}
+
+webgl::ActiveUniformValidationInfo webgl::ActiveUniformValidationInfo::Make(
+ const webgl::ActiveUniformInfo& info) {
+ auto ret = webgl::ActiveUniformValidationInfo{info};
+ ret.isArray = EndsWith(info.name, "[0]");
+
+ switch (info.elemType) {
+ case LOCAL_GL_FLOAT:
+ ret.channelsPerElem = 1;
+ ret.pfn = &UniformAs1fv;
+ break;
+ case LOCAL_GL_FLOAT_VEC2:
+ ret.channelsPerElem = 2;
+ ret.pfn = &UniformAs2fv;
+ break;
+ case LOCAL_GL_FLOAT_VEC3:
+ ret.channelsPerElem = 3;
+ ret.pfn = &UniformAs3fv;
+ break;
+ case LOCAL_GL_FLOAT_VEC4:
+ ret.channelsPerElem = 4;
+ ret.pfn = &UniformAs4fv;
+ break;
+
+ 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_INT:
+ ret.channelsPerElem = 1;
+ ret.pfn = &UniformAs1iv;
+ break;
+ case LOCAL_GL_BOOL_VEC2:
+ case LOCAL_GL_INT_VEC2:
+ ret.channelsPerElem = 2;
+ ret.pfn = &UniformAs2iv;
+ break;
+ case LOCAL_GL_BOOL_VEC3:
+ case LOCAL_GL_INT_VEC3:
+ ret.channelsPerElem = 3;
+ ret.pfn = &UniformAs3iv;
+ break;
+ case LOCAL_GL_BOOL_VEC4:
+ case LOCAL_GL_INT_VEC4:
+ ret.channelsPerElem = 4;
+ ret.pfn = &UniformAs4iv;
+ break;
+
+ case LOCAL_GL_UNSIGNED_INT:
+ ret.channelsPerElem = 1;
+ ret.pfn = &UniformAs1uiv;
+ break;
+ case LOCAL_GL_UNSIGNED_INT_VEC2:
+ ret.channelsPerElem = 2;
+ ret.pfn = &UniformAs2uiv;
+ break;
+ case LOCAL_GL_UNSIGNED_INT_VEC3:
+ ret.channelsPerElem = 3;
+ ret.pfn = &UniformAs3uiv;
+ break;
+ case LOCAL_GL_UNSIGNED_INT_VEC4:
+ ret.channelsPerElem = 4;
+ ret.pfn = &UniformAs4uiv;
+ break;
+
+ // -
+
+ case LOCAL_GL_FLOAT_MAT2:
+ ret.channelsPerElem = 2 * 2;
+ ret.pfn = &UniformAsMatrix2x2fv;
+ break;
+ case LOCAL_GL_FLOAT_MAT2x3:
+ ret.channelsPerElem = 2 * 3;
+ ret.pfn = &UniformAsMatrix2x3fv;
+ break;
+ case LOCAL_GL_FLOAT_MAT2x4:
+ ret.channelsPerElem = 2 * 4;
+ ret.pfn = &UniformAsMatrix2x4fv;
+ break;
+
+ case LOCAL_GL_FLOAT_MAT3x2:
+ ret.channelsPerElem = 3 * 2;
+ ret.pfn = &UniformAsMatrix3x2fv;
+ break;
+ case LOCAL_GL_FLOAT_MAT3:
+ ret.channelsPerElem = 3 * 3;
+ ret.pfn = &UniformAsMatrix3x3fv;
+ break;
+ case LOCAL_GL_FLOAT_MAT3x4:
+ ret.channelsPerElem = 3 * 4;
+ ret.pfn = &UniformAsMatrix3x4fv;
+ break;
+
+ case LOCAL_GL_FLOAT_MAT4x2:
+ ret.channelsPerElem = 4 * 2;
+ ret.pfn = &UniformAsMatrix4x2fv;
+ break;
+ case LOCAL_GL_FLOAT_MAT4x3:
+ ret.channelsPerElem = 4 * 3;
+ ret.pfn = &UniformAsMatrix4x3fv;
+ break;
+ case LOCAL_GL_FLOAT_MAT4:
+ ret.channelsPerElem = 4 * 4;
+ ret.pfn = &UniformAsMatrix4x4fv;
+ break;
+
+ default:
+ gfxCriticalError() << "Bad `elemType`: " << EnumString(info.elemType);
+ MOZ_CRASH("`elemType`");
+ }
+ return ret;
+}
+
+} // namespace webgl
+
+// -------------------------
+
+// #define DUMP_SHADERVAR_MAPPINGS
+
+RefPtr<const webgl::LinkedProgramInfo> QueryProgramInfo(WebGLProgram* prog,
+ gl::GLContext* gl) {
+ WebGLContext* const webgl = prog->mContext;
+
+ RefPtr<webgl::LinkedProgramInfo> info(new webgl::LinkedProgramInfo(prog));
+
+ // Frag outputs
+
+ {
+ const auto& fragShader = prog->FragShader();
+ const auto& compileResults = fragShader->CompileResults();
+ const auto version = compileResults->mShaderVersion;
+
+ const auto fnAddInfo = [&](const webgl::FragOutputInfo& x) {
+ info->hasOutput[x.loc] = true;
+ info->fragOutputs.insert({x.loc, x});
+ };
+
+ if (version == 300) {
+ for (const auto& cur : compileResults->mOutputVariables) {
+ auto loc = cur.location;
+ if (loc == -1) loc = 0;
+
+ const auto info =
+ webgl::FragOutputInfo{uint8_t(loc), cur.name, cur.mappedName,
+ FragOutputBaseType(cur.type)};
+ if (!cur.isArray()) {
+ fnAddInfo(info);
+ continue;
+ }
+ MOZ_ASSERT(cur.arraySizes.size() == 1);
+ for (uint32_t i = 0; i < cur.arraySizes[0]; ++i) {
+ const auto indexStr = std::string("[") + std::to_string(i) + "]";
+
+ const auto userName = info.userName + indexStr;
+ const auto mappedName = info.mappedName + indexStr;
+
+ const auto indexedInfo = webgl::FragOutputInfo{
+ uint8_t(info.loc + i), userName, mappedName, info.baseType};
+ fnAddInfo(indexedInfo);
+ }
+ }
+ } else {
+ // ANGLE's translator doesn't tell us about non-user frag outputs. :(
+
+ const auto& translatedSource = compileResults->mObjectCode;
+ uint32_t drawBuffers = 1;
+ if (translatedSource.find("(gl_FragData[1]") != std::string::npos ||
+ translatedSource.find("(webgl_FragData[1]") != std::string::npos) {
+ // The matching with the leading '(' prevents cleverly-named user vars
+ // breaking this. Since ANGLE initializes all outputs, if this is an MRT
+ // shader, FragData[1] will be present. FragData[0] is valid for non-MRT
+ // shaders.
+ drawBuffers = webgl->GLMaxDrawBuffers();
+ } else if (translatedSource.find("(gl_FragColor") == std::string::npos &&
+ translatedSource.find("(webgl_FragColor") ==
+ std::string::npos &&
+ translatedSource.find("(gl_FragData") == std::string::npos &&
+ translatedSource.find("(webgl_FragData") ==
+ std::string::npos) {
+ // We have to support no-color-output shaders?
+ drawBuffers = 0;
+ }
+
+ for (uint32_t i = 0; i < drawBuffers; ++i) {
+ const auto name = std::string("gl_FragData[") + std::to_string(i) + "]";
+ const auto info = webgl::FragOutputInfo{uint8_t(i), name, name,
+ webgl::TextureBaseType::Float};
+ fnAddInfo(info);
+ }
+ }
+ }
+
+ const auto& vertShader = prog->VertShader();
+ const auto& vertCompileResults = vertShader->CompileResults();
+ const auto numViews = vertCompileResults->mVertexShaderNumViews;
+ if (numViews != -1) {
+ info->zLayerCount = AssertedCast<uint8_t>(numViews);
+ }
+
+ // -
+
+ auto& nameMap = info->nameMap;
+
+ const auto fnAccum = [&](WebGLShader& shader) {
+ const auto& compRes = shader.CompileResults();
+ for (const auto& pair : compRes->mNameMap) {
+ nameMap.insert(pair);
+ }
+ };
+ fnAccum(*prog->VertShader());
+ fnAccum(*prog->FragShader());
+
+ // -
+
+ std::unordered_map<std::string, std::string> nameUnmap;
+ for (const auto& pair : nameMap) {
+ nameUnmap.insert({pair.second, pair.first});
+ }
+
+ info->active =
+ GetLinkActiveInfo(*gl, prog->mGLName, webgl->IsWebGL2(), nameUnmap);
+
+ // -
+
+ for (const auto& attrib : info->active.activeAttribs) {
+ if (attrib.location == 0) {
+ info->attrib0Active = true;
+ break;
+ }
+ }
+
+ info->webgl_gl_VertexID_Offset =
+ gl->fGetUniformLocation(prog->mGLName, "webgl_gl_VertexID_Offset");
+
+ // -
+
+ for (const auto& uniform : info->active.activeUniforms) {
+ const auto& elemType = uniform.elemType;
+ webgl::SamplerUniformInfo* samplerInfo = nullptr;
+ const auto baseType = SamplerBaseType(elemType);
+ if (baseType) {
+ const bool isShadowSampler = IsShadowSampler(elemType);
+
+ auto* texList = &webgl->mBound2DTextures;
+
+ switch (elemType) {
+ case LOCAL_GL_SAMPLER_2D:
+ case LOCAL_GL_SAMPLER_2D_SHADOW:
+ case LOCAL_GL_INT_SAMPLER_2D:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+ break;
+
+ case LOCAL_GL_SAMPLER_CUBE:
+ case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+ case LOCAL_GL_INT_SAMPLER_CUBE:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
+ texList = &webgl->mBoundCubeMapTextures;
+ break;
+
+ case LOCAL_GL_SAMPLER_3D:
+ case LOCAL_GL_INT_SAMPLER_3D:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+ texList = &webgl->mBound3DTextures;
+ break;
+
+ case LOCAL_GL_SAMPLER_2D_ARRAY:
+ case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+ case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+ case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+ texList = &webgl->mBound2DArrayTextures;
+ break;
+ }
+
+ auto curInfo = std::unique_ptr<webgl::SamplerUniformInfo>(
+ new webgl::SamplerUniformInfo{*texList, *baseType, isShadowSampler});
+ MOZ_RELEASE_ASSERT(curInfo->texUnits.resize(uniform.elemCount));
+ samplerInfo = curInfo.get();
+ info->samplerUniforms.push_back(std::move(curInfo));
+ }
+
+ const auto valInfo = webgl::ActiveUniformValidationInfo::Make(uniform);
+
+ for (const auto& pair : uniform.locByIndex) {
+ info->locationMap.insert(
+ {pair.second, {valInfo, pair.first, samplerInfo}});
+ }
+ }
+
+ // -
+
+ {
+ const auto& activeBlocks = info->active.activeUniformBlocks;
+ info->uniformBlocks.reserve(activeBlocks.size());
+ for (const auto& cur : activeBlocks) {
+ const auto curInfo = webgl::UniformBlockInfo{
+ cur, &webgl->mIndexedUniformBufferBindings[0]};
+ info->uniformBlocks.push_back(curInfo);
+ }
+ }
+
+ return info;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+webgl::LinkedProgramInfo::LinkedProgramInfo(WebGLProgram* prog)
+ : prog(prog),
+ transformFeedbackBufferMode(prog->mNextLink_TransformFeedbackBufferMode) {
+}
+
+webgl::LinkedProgramInfo::~LinkedProgramInfo() = default;
+
+webgl::AttribBaseType webgl::ToAttribBaseType(const GLenum elemType) {
+ switch (elemType) {
+ case LOCAL_GL_BOOL:
+ case LOCAL_GL_BOOL_VEC2:
+ case LOCAL_GL_BOOL_VEC3:
+ case LOCAL_GL_BOOL_VEC4:
+ return webgl::AttribBaseType::Boolean;
+
+ 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_MAT2x3:
+ case LOCAL_GL_FLOAT_MAT3x2:
+ case LOCAL_GL_FLOAT_MAT2x4:
+ case LOCAL_GL_FLOAT_MAT4x2:
+ case LOCAL_GL_FLOAT_MAT3:
+ case LOCAL_GL_FLOAT_MAT3x4:
+ case LOCAL_GL_FLOAT_MAT4x3:
+ case LOCAL_GL_FLOAT_MAT4:
+ return webgl::AttribBaseType::Float;
+
+ 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:
+ return webgl::AttribBaseType::Int;
+
+ case LOCAL_GL_UNSIGNED_INT:
+ case LOCAL_GL_UNSIGNED_INT_VEC2:
+ case LOCAL_GL_UNSIGNED_INT_VEC3:
+ case LOCAL_GL_UNSIGNED_INT_VEC4:
+ return webgl::AttribBaseType::Uint;
+
+ default:
+ gfxCriticalError() << "Bad `elemType`: " << EnumString(elemType);
+ MOZ_CRASH("`elemType`");
+ }
+}
+
+const char* webgl::ToString(const webgl::AttribBaseType x) {
+ switch (x) {
+ case webgl::AttribBaseType::Float:
+ return "FLOAT";
+ case webgl::AttribBaseType::Int:
+ return "INT";
+ case webgl::AttribBaseType::Uint:
+ return "UINT";
+ case webgl::AttribBaseType::Boolean:
+ return "BOOL";
+ }
+ MOZ_CRASH("pacify gcc6 warning");
+}
+
+const char* webgl::ToString(const webgl::UniformBaseType x) {
+ switch (x) {
+ case webgl::UniformBaseType::Float:
+ return "FLOAT";
+ case webgl::UniformBaseType::Int:
+ return "INT";
+ case webgl::UniformBaseType::Uint:
+ return "UINT";
+ }
+ MOZ_CRASH("pacify gcc6 warning");
+}
+
+const webgl::CachedDrawFetchLimits*
+webgl::LinkedProgramInfo::GetDrawFetchLimits() const {
+ const auto& webgl = prog->mContext;
+ const auto& vao = webgl->mBoundVertexArray;
+
+ {
+ // We have to ensure that every enabled attrib array (not just the active
+ // ones) has a non-null buffer.
+ const auto badIndex = vao->GetAttribIsArrayWithNullBuffer();
+ if (badIndex) {
+ webgl->ErrorInvalidOperation(
+ "Vertex attrib array %u is enabled but"
+ " has no buffer bound.",
+ *badIndex);
+ return nullptr;
+ }
+ }
+
+ const auto& activeAttribs = active.activeAttribs;
+
+ webgl::CachedDrawFetchLimits fetchLimits;
+ fetchLimits.usedBuffers =
+ std::move(mScratchFetchLimits.usedBuffers); // Avoid realloc.
+ fetchLimits.usedBuffers.clear();
+ fetchLimits.usedBuffers.reserve(activeAttribs.size());
+
+ bool hasActiveAttrib = false;
+ bool hasActiveDivisor0 = false;
+
+ for (const auto& progAttrib : activeAttribs) {
+ const auto& loc = progAttrib.location;
+ if (loc == -1) continue;
+ hasActiveAttrib |= true;
+
+ const auto& binding = vao->AttribBinding(loc);
+ const auto& buffer = binding.buffer;
+ const auto& layout = binding.layout;
+ hasActiveDivisor0 |= (layout.divisor == 0);
+
+ webgl::AttribBaseType attribDataBaseType;
+ if (layout.isArray) {
+ MOZ_ASSERT(buffer);
+ fetchLimits.usedBuffers.push_back(
+ {buffer.get(), static_cast<uint32_t>(loc)});
+
+ attribDataBaseType = layout.baseType;
+
+ const auto availBytes = buffer->ByteLength();
+ const auto availElems = AvailGroups(availBytes, layout.byteOffset,
+ layout.byteSize, layout.byteStride);
+ if (layout.divisor) {
+ const auto availInstances =
+ CheckedInt<uint64_t>(availElems) * layout.divisor;
+ if (availInstances.isValid()) {
+ fetchLimits.maxInstances =
+ std::min(fetchLimits.maxInstances, availInstances.value());
+ } // If not valid, it overflowed too large, so we're super safe.
+ } else {
+ fetchLimits.maxVerts = std::min(fetchLimits.maxVerts, availElems);
+ }
+ } else {
+ attribDataBaseType = webgl->mGenericVertexAttribTypes[loc];
+ }
+
+ const auto& progBaseType = progAttrib.baseType;
+ if ((attribDataBaseType != progBaseType) &&
+ (progBaseType != webgl::AttribBaseType::Boolean)) {
+ const auto& dataType = ToString(attribDataBaseType);
+ const auto& progType = ToString(progBaseType);
+ webgl->ErrorInvalidOperation(
+ "Vertex attrib %u requires data of type %s,"
+ " but is being supplied with type %s.",
+ loc, progType, dataType);
+ return nullptr;
+ }
+ }
+
+ if (!webgl->IsWebGL2() && hasActiveAttrib && !hasActiveDivisor0) {
+ webgl->ErrorInvalidOperation(
+ "One active vertex attrib (if any are active)"
+ " must have a divisor of 0.");
+ return nullptr;
+ }
+
+ // -
+
+ mScratchFetchLimits = std::move(fetchLimits);
+ return &mScratchFetchLimits;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// WebGLProgram
+
+WebGLProgram::WebGLProgram(WebGLContext* webgl)
+ : WebGLContextBoundObject(webgl),
+ mGLName(webgl->gl->fCreateProgram()),
+ mNumActiveTFOs(0),
+ mNextLink_TransformFeedbackBufferMode(LOCAL_GL_INTERLEAVED_ATTRIBS) {}
+
+WebGLProgram::~WebGLProgram() {
+ mVertShader = nullptr;
+ mFragShader = nullptr;
+
+ mMostRecentLinkInfo = nullptr;
+
+ if (!mContext) return;
+ mContext->gl->fDeleteProgram(mGLName);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// GL funcs
+
+void WebGLProgram::AttachShader(WebGLShader& shader) {
+ RefPtr<WebGLShader>* shaderSlot = nullptr;
+ switch (shader.mType) {
+ case LOCAL_GL_VERTEX_SHADER:
+ shaderSlot = &mVertShader;
+ break;
+ case LOCAL_GL_FRAGMENT_SHADER:
+ shaderSlot = &mFragShader;
+ break;
+ }
+ MOZ_ASSERT(shaderSlot);
+
+ *shaderSlot = &shader;
+
+ mContext->gl->fAttachShader(mGLName, shader.mGLName);
+}
+
+void WebGLProgram::BindAttribLocation(GLuint loc, const std::string& name) {
+ const auto err = CheckGLSLVariableName(mContext->IsWebGL2(), name);
+ if (err) {
+ mContext->GenerateError(err->type, "%s", err->info.c_str());
+ return;
+ }
+
+ if (loc >= mContext->MaxVertexAttribs()) {
+ mContext->ErrorInvalidValue(
+ "`location` must be less than"
+ " MAX_VERTEX_ATTRIBS.");
+ return;
+ }
+
+ if (name.find("gl_") == 0) {
+ mContext->ErrorInvalidOperation(
+ "Can't set the location of a"
+ " name that starts with 'gl_'.");
+ return;
+ }
+
+ auto res = mNextLink_BoundAttribLocs.insert({name, loc});
+
+ const auto& wasInserted = res.second;
+ if (!wasInserted) {
+ const auto& itr = res.first;
+ itr->second = loc;
+ }
+}
+
+void WebGLProgram::DetachShader(const WebGLShader& shader) {
+ RefPtr<WebGLShader>* shaderSlot = nullptr;
+ switch (shader.mType) {
+ case LOCAL_GL_VERTEX_SHADER:
+ shaderSlot = &mVertShader;
+ break;
+ case LOCAL_GL_FRAGMENT_SHADER:
+ shaderSlot = &mFragShader;
+ break;
+ }
+ MOZ_ASSERT(shaderSlot);
+
+ if (*shaderSlot != &shader) return;
+
+ *shaderSlot = nullptr;
+
+ mContext->gl->fDetachShader(mGLName, shader.mGLName);
+}
+
+void WebGLProgram::UniformBlockBinding(GLuint uniformBlockIndex,
+ GLuint uniformBlockBinding) const {
+ if (!IsLinked()) {
+ mContext->ErrorInvalidOperation("`program` must be linked.");
+ return;
+ }
+
+ auto& uniformBlocks = LinkInfo()->uniformBlocks;
+ if (uniformBlockIndex >= uniformBlocks.size()) {
+ mContext->ErrorInvalidValue("Index %u invalid.", uniformBlockIndex);
+ return;
+ }
+ auto& uniformBlock = uniformBlocks[uniformBlockIndex];
+
+ const auto& indexedBindings = mContext->mIndexedUniformBufferBindings;
+ if (uniformBlockBinding >= indexedBindings.size()) {
+ mContext->ErrorInvalidValue("Binding %u invalid.", uniformBlockBinding);
+ return;
+ }
+ const auto& indexedBinding = indexedBindings[uniformBlockBinding];
+
+ ////
+
+ gl::GLContext* gl = mContext->GL();
+ gl->fUniformBlockBinding(mGLName, uniformBlockIndex, uniformBlockBinding);
+
+ ////
+
+ uniformBlock.binding = &indexedBinding;
+}
+
+bool WebGLProgram::ValidateForLink() {
+ const auto AppendCompileLog = [&](const WebGLShader* const shader) {
+ if (!shader) {
+ mLinkLog += " Missing shader.";
+ return;
+ }
+ mLinkLog += "\nSHADER_INFO_LOG:\n";
+ mLinkLog += shader->CompileLog();
+ };
+
+ if (!mVertShader || !mVertShader->IsCompiled()) {
+ mLinkLog = "Must have a compiled vertex shader attached:";
+ AppendCompileLog(mVertShader);
+ return false;
+ }
+ const auto& vertInfo = *mVertShader->CompileResults();
+
+ if (!mFragShader || !mFragShader->IsCompiled()) {
+ mLinkLog = "Must have a compiled fragment shader attached:";
+ AppendCompileLog(mFragShader);
+ return false;
+ }
+ const auto& fragInfo = *mFragShader->CompileResults();
+
+ nsCString errInfo;
+ if (!fragInfo.CanLinkTo(vertInfo, &errInfo)) {
+ mLinkLog = errInfo.BeginReading();
+ return false;
+ }
+
+ const auto& gl = mContext->gl;
+
+ if (gl->WorkAroundDriverBugs() && mContext->mIsMesa) {
+ // Bug 1203135: Mesa crashes internally if we exceed the reported maximum
+ // attribute count.
+ if (mVertShader->NumAttributes() > mContext->MaxVertexAttribs()) {
+ mLinkLog =
+ "Number of attributes exceeds Mesa's reported max"
+ " attribute count.";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void WebGLProgram::LinkProgram() {
+ if (mNumActiveTFOs) {
+ mContext->ErrorInvalidOperation(
+ "Program is in-use by one or more active"
+ " transform feedback objects.");
+ return;
+ }
+
+ // as some of the validation changes program state
+
+ mLinkLog = {};
+ mMostRecentLinkInfo = nullptr;
+
+ if (!ValidateForLink()) {
+ mContext->GenerateWarning("%s", mLinkLog.c_str());
+ return;
+ }
+
+ // Bind the attrib locations.
+ // This can't be done trivially, because we have to deal with mapped attrib
+ // names.
+ for (const auto& pair : mNextLink_BoundAttribLocs) {
+ const auto& name = pair.first;
+ const auto& index = pair.second;
+
+ mVertShader->BindAttribLocation(mGLName, name, index);
+ }
+
+ // Storage for transform feedback varyings before link.
+ // (Work around for bug seen on nVidia drivers.)
+ std::vector<std::string> scopedMappedTFVaryings;
+
+ if (mContext->IsWebGL2()) {
+ mVertShader->MapTransformFeedbackVaryings(
+ mNextLink_TransformFeedbackVaryings, &scopedMappedTFVaryings);
+
+ std::vector<const char*> driverVaryings;
+ driverVaryings.reserve(scopedMappedTFVaryings.size());
+ for (const auto& cur : scopedMappedTFVaryings) {
+ driverVaryings.push_back(cur.c_str());
+ }
+
+ mContext->gl->fTransformFeedbackVaryings(
+ mGLName, driverVaryings.size(), driverVaryings.data(),
+ mNextLink_TransformFeedbackBufferMode);
+ }
+
+ LinkAndUpdate();
+
+ if (mMostRecentLinkInfo) {
+ std::string postLinkLog;
+ if (ValidateAfterTentativeLink(&postLinkLog)) return;
+
+ mMostRecentLinkInfo = nullptr;
+ mLinkLog = std::move(postLinkLog);
+ }
+
+ // Failed link.
+ if (mContext->ShouldGenerateWarnings()) {
+ // report shader/program infoLogs as warnings.
+ // note that shader compilation errors can be deferred to linkProgram,
+ // which is why we can't do anything in compileShader. In practice we could
+ // report in compileShader the translation errors generated by ANGLE,
+ // but it seems saner to keep a single way of obtaining shader infologs.
+ if (!mLinkLog.empty()) {
+ mContext->GenerateWarning(
+ "Failed to link, leaving the following"
+ " log:\n%s\n",
+ mLinkLog.c_str());
+ }
+ }
+}
+
+static uint8_t NumUsedLocationsByElemType(GLenum elemType) {
+ // GLES 3.0.4 p55
+
+ switch (elemType) {
+ case LOCAL_GL_FLOAT_MAT2:
+ case LOCAL_GL_FLOAT_MAT2x3:
+ case LOCAL_GL_FLOAT_MAT2x4:
+ return 2;
+
+ case LOCAL_GL_FLOAT_MAT3x2:
+ case LOCAL_GL_FLOAT_MAT3:
+ case LOCAL_GL_FLOAT_MAT3x4:
+ return 3;
+
+ case LOCAL_GL_FLOAT_MAT4x2:
+ case LOCAL_GL_FLOAT_MAT4x3:
+ case LOCAL_GL_FLOAT_MAT4:
+ return 4;
+
+ default:
+ return 1;
+ }
+}
+
+uint8_t ElemTypeComponents(const GLenum elemType) {
+ switch (elemType) {
+ case LOCAL_GL_BOOL:
+ case LOCAL_GL_FLOAT:
+ case LOCAL_GL_INT:
+ case LOCAL_GL_UNSIGNED_INT:
+ 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:
+ return 1;
+
+ case LOCAL_GL_BOOL_VEC2:
+ case LOCAL_GL_FLOAT_VEC2:
+ case LOCAL_GL_INT_VEC2:
+ case LOCAL_GL_UNSIGNED_INT_VEC2:
+ return 2;
+
+ case LOCAL_GL_BOOL_VEC3:
+ case LOCAL_GL_FLOAT_VEC3:
+ case LOCAL_GL_INT_VEC3:
+ case LOCAL_GL_UNSIGNED_INT_VEC3:
+ return 3;
+
+ case LOCAL_GL_BOOL_VEC4:
+ case LOCAL_GL_FLOAT_VEC4:
+ case LOCAL_GL_INT_VEC4:
+ case LOCAL_GL_UNSIGNED_INT_VEC4:
+ case LOCAL_GL_FLOAT_MAT2:
+ return 4;
+
+ case LOCAL_GL_FLOAT_MAT2x3:
+ case LOCAL_GL_FLOAT_MAT3x2:
+ return 2 * 3;
+
+ case LOCAL_GL_FLOAT_MAT2x4:
+ case LOCAL_GL_FLOAT_MAT4x2:
+ return 2 * 4;
+
+ case LOCAL_GL_FLOAT_MAT3:
+ return 3 * 3;
+
+ case LOCAL_GL_FLOAT_MAT3x4:
+ case LOCAL_GL_FLOAT_MAT4x3:
+ return 3 * 4;
+
+ case LOCAL_GL_FLOAT_MAT4:
+ return 4 * 4;
+
+ default:
+ return 0;
+ }
+}
+
+bool WebGLProgram::ValidateAfterTentativeLink(
+ std::string* const out_linkLog) const {
+ const auto& linkInfo = mMostRecentLinkInfo;
+ const auto& gl = mContext->gl;
+
+ // Check for overlapping attrib locations.
+ {
+ std::unordered_map<uint32_t, const std::string&> nameByLoc;
+ for (const auto& attrib : linkInfo->active.activeAttribs) {
+ if (attrib.location == -1) continue;
+
+ const auto& elemType = attrib.elemType;
+ const auto numUsedLocs = NumUsedLocationsByElemType(elemType);
+ for (uint32_t i = 0; i < numUsedLocs; i++) {
+ const uint32_t usedLoc = attrib.location + i;
+
+ const auto res = nameByLoc.insert({usedLoc, attrib.name});
+ const bool& didInsert = res.second;
+ if (!didInsert) {
+ const auto& aliasingName = attrib.name;
+ const auto& itrExisting = res.first;
+ const auto& existingName = itrExisting->second;
+ *out_linkLog = nsPrintfCString(
+ "Attrib \"%s\" aliases locations used by"
+ " attrib \"%s\".",
+ aliasingName.c_str(), existingName.c_str())
+ .BeginReading();
+ return false;
+ }
+ }
+ }
+ }
+
+ // Forbid too many components for specified buffer mode
+ const auto& activeTfVaryings = linkInfo->active.activeTfVaryings;
+ MOZ_ASSERT(mNextLink_TransformFeedbackVaryings.size() ==
+ activeTfVaryings.size());
+ if (!activeTfVaryings.empty()) {
+ GLuint maxComponentsPerIndex = 0;
+ switch (linkInfo->transformFeedbackBufferMode) {
+ case LOCAL_GL_INTERLEAVED_ATTRIBS:
+ gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS,
+ &maxComponentsPerIndex);
+ break;
+
+ case LOCAL_GL_SEPARATE_ATTRIBS:
+ gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS,
+ &maxComponentsPerIndex);
+ break;
+
+ default:
+ MOZ_CRASH("`bufferMode`");
+ }
+
+ std::vector<size_t> componentsPerVert;
+ for (const auto& cur : activeTfVaryings) {
+ if (componentsPerVert.empty() ||
+ linkInfo->transformFeedbackBufferMode == LOCAL_GL_SEPARATE_ATTRIBS) {
+ componentsPerVert.push_back(0);
+ }
+
+ size_t varyingComponents = ElemTypeComponents(cur.elemType);
+ MOZ_ASSERT(varyingComponents);
+ varyingComponents *= cur.elemCount;
+
+ auto& totalComponentsForIndex = *(componentsPerVert.rbegin());
+ totalComponentsForIndex += varyingComponents;
+
+ if (totalComponentsForIndex > maxComponentsPerIndex) {
+ *out_linkLog = nsPrintfCString(
+ "Transform feedback varying \"%s\""
+ " pushed `componentsForIndex` over the"
+ " limit of %u.",
+ cur.name.c_str(), maxComponentsPerIndex)
+ .BeginReading();
+ return false;
+ }
+ }
+
+ linkInfo->componentsPerTFVert = std::move(componentsPerVert);
+ }
+
+ return true;
+}
+
+bool WebGLProgram::UseProgram() const {
+ if (!mMostRecentLinkInfo) {
+ mContext->ErrorInvalidOperation(
+ "Program has not been successfully linked.");
+ return false;
+ }
+
+ if (mContext->mBoundTransformFeedback &&
+ mContext->mBoundTransformFeedback->mIsActive &&
+ !mContext->mBoundTransformFeedback->mIsPaused) {
+ mContext->ErrorInvalidOperation(
+ "Transform feedback active and not paused.");
+ return false;
+ }
+
+ mContext->gl->fUseProgram(mGLName);
+ return true;
+}
+
+bool WebGLProgram::ValidateProgram() const {
+ gl::GLContext* gl = mContext->gl;
+
+#ifdef XP_MACOSX
+ // See bug 593867 for NVIDIA and bug 657201 for ATI. The latter is confirmed
+ // with Mac OS 10.6.7.
+ if (gl->WorkAroundDriverBugs()) {
+ mContext->GenerateWarning(
+ "Implemented as a no-op on"
+ " Mac to work around crashes.");
+ return true;
+ }
+#endif
+
+ gl->fValidateProgram(mGLName);
+ GLint ok = 0;
+ gl->fGetProgramiv(mGLName, LOCAL_GL_VALIDATE_STATUS, &ok);
+ return bool(ok);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void WebGLProgram::LinkAndUpdate() {
+ mMostRecentLinkInfo = nullptr;
+
+ gl::GLContext* gl = mContext->gl;
+ gl->fLinkProgram(mGLName);
+
+ // Grab the program log.
+ {
+ GLuint logLenWithNull = 0;
+ gl->fGetProgramiv(mGLName, LOCAL_GL_INFO_LOG_LENGTH,
+ (GLint*)&logLenWithNull);
+ if (logLenWithNull > 1) {
+ std::vector<char> buffer(logLenWithNull);
+ gl->fGetProgramInfoLog(mGLName, buffer.size(), nullptr, buffer.data());
+ mLinkLog = buffer.data();
+ } else {
+ mLinkLog.clear();
+ }
+ }
+
+ GLint ok = 0;
+ gl->fGetProgramiv(mGLName, LOCAL_GL_LINK_STATUS, &ok);
+ if (!ok) return;
+
+ mMostRecentLinkInfo =
+ QueryProgramInfo(this, gl); // Fallible after context loss.
+}
+
+void WebGLProgram::TransformFeedbackVaryings(
+ const std::vector<std::string>& varyings, GLenum bufferMode) {
+ const auto& gl = mContext->gl;
+
+ switch (bufferMode) {
+ case LOCAL_GL_INTERLEAVED_ATTRIBS:
+ break;
+
+ case LOCAL_GL_SEPARATE_ATTRIBS: {
+ GLuint maxAttribs = 0;
+ gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS,
+ &maxAttribs);
+ if (varyings.size() > maxAttribs) {
+ mContext->ErrorInvalidValue("Length of `varyings` exceeds %s.",
+ "TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS");
+ return;
+ }
+ } break;
+
+ default:
+ mContext->ErrorInvalidEnumInfo("bufferMode", bufferMode);
+ return;
+ }
+
+ ////
+
+ mNextLink_TransformFeedbackVaryings = varyings;
+ mNextLink_TransformFeedbackBufferMode = bufferMode;
+}
+
+} // namespace mozilla