summaryrefslogtreecommitdiffstats
path: root/dom/canvas/WebGLShaderValidator.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/canvas/WebGLShaderValidator.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas/WebGLShaderValidator.cpp')
-rw-r--r--dom/canvas/WebGLShaderValidator.cpp492
1 files changed, 492 insertions, 0 deletions
diff --git a/dom/canvas/WebGLShaderValidator.cpp b/dom/canvas/WebGLShaderValidator.cpp
new file mode 100644
index 0000000000..0921da6e25
--- /dev/null
+++ b/dom/canvas/WebGLShaderValidator.cpp
@@ -0,0 +1,492 @@
+/* -*- 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 "WebGLShaderValidator.h"
+
+#include "GLContext.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_webgl.h"
+#include "MurmurHash3.h"
+#include "nsPrintfCString.h"
+#include <string>
+#include <vector>
+#include "WebGLContext.h"
+
+namespace mozilla {
+namespace webgl {
+
+uint64_t IdentifierHashFunc(const char* name, size_t len) {
+ // NB: we use the x86 function everywhere, even though it's suboptimal perf
+ // on x64. They return different results; not sure if that's a requirement.
+ uint64_t hash[2];
+ MurmurHash3_x86_128(name, len, 0, hash);
+ return hash[0];
+}
+
+static ShCompileOptions ChooseValidatorCompileOptions(
+ const ShBuiltInResources& resources, const mozilla::gl::GLContext* gl) {
+ ShCompileOptions options = {};
+ options.variables = true;
+ options.enforcePackingRestrictions = true;
+ options.objectCode = true;
+ options.initGLPosition = true;
+ options.initializeUninitializedLocals = true;
+ options.initOutputVariables = true;
+
+#ifdef XP_MACOSX
+ options.removeInvariantAndCentroidForESSL3 = true;
+#else
+ // We want to do this everywhere, but to do this on Mac, we need
+ // to do it only on Mac OSX > 10.6 as this causes the shader
+ // compiler in 10.6 to crash
+ options.clampIndirectArrayBounds = true;
+#endif
+
+ if (gl->WorkAroundDriverBugs()) {
+ if (kIsMacOS) {
+ // Work around https://bugs.webkit.org/show_bug.cgi?id=124684,
+ // https://chromium.googlesource.com/angle/angle/+/5e70cf9d0b1bb
+ options.unfoldShortCircuit = true;
+
+ // Work around that Mac drivers handle struct scopes incorrectly.
+ options.regenerateStructNames = true;
+ options.initOutputVariables = true;
+ options.initGLPointSize = true;
+
+ if (gl->Vendor() == gl::GLVendor::Intel) {
+ // Work around that Intel drivers on Mac OSX handle for-loop
+ // incorrectly.
+ options.addAndTrueToLoopCondition = true;
+
+ options.rewriteTexelFetchOffsetToTexelFetch = true;
+ }
+ }
+
+ if (!gl->IsANGLE() && gl->Vendor() == gl::GLVendor::Intel) {
+ // Failures on at least Windows+Intel+OGL on:
+ // conformance/glsl/constructors/glsl-construct-mat2.html
+ options.scalarizeVecAndMatConstructorArgs = true;
+ }
+ }
+
+ // -
+
+ if (resources.MaxExpressionComplexity > 0) {
+ options.limitExpressionComplexity = true;
+ }
+ if (resources.MaxCallStackDepth > 0) {
+ options.limitCallStackDepth = true;
+ }
+
+ return options;
+}
+
+} // namespace webgl
+
+////////////////////////////////////////
+
+static ShShaderOutput ShaderOutput(gl::GLContext* gl) {
+ if (gl->IsGLES()) {
+ return SH_ESSL_OUTPUT;
+ }
+ uint32_t version = gl->ShadingLanguageVersion();
+ switch (version) {
+ case 100:
+ return SH_GLSL_COMPATIBILITY_OUTPUT;
+ case 120:
+ return SH_GLSL_COMPATIBILITY_OUTPUT;
+ case 130:
+ return SH_GLSL_130_OUTPUT;
+ case 140:
+ return SH_GLSL_140_OUTPUT;
+ case 150:
+ return SH_GLSL_150_CORE_OUTPUT;
+ case 330:
+ return SH_GLSL_330_CORE_OUTPUT;
+ case 400:
+ return SH_GLSL_400_CORE_OUTPUT;
+ case 410:
+ return SH_GLSL_410_CORE_OUTPUT;
+ case 420:
+ return SH_GLSL_420_CORE_OUTPUT;
+ case 430:
+ return SH_GLSL_430_CORE_OUTPUT;
+ case 440:
+ return SH_GLSL_440_CORE_OUTPUT;
+ default:
+ if (version >= 450) {
+ // "OpenGL 4.6 is also guaranteed to support all previous versions of
+ // the OpenGL Shading Language back to version 1.10."
+ return SH_GLSL_450_CORE_OUTPUT;
+ }
+ gfxCriticalNote << "Unexpected GLSL version: " << version;
+ }
+
+ return SH_GLSL_COMPATIBILITY_OUTPUT;
+}
+
+std::unique_ptr<webgl::ShaderValidator> WebGLContext::CreateShaderValidator(
+ GLenum shaderType) const {
+ const auto spec = (IsWebGL2() ? SH_WEBGL2_SPEC : SH_WEBGL_SPEC);
+ const auto outputLanguage = ShaderOutput(gl);
+
+ ShBuiltInResources resources;
+ memset(&resources, 0, sizeof(resources));
+ sh::InitBuiltInResources(&resources);
+
+ resources.HashFunction = webgl::IdentifierHashFunc;
+
+ const auto& limits = Limits();
+
+ resources.MaxVertexAttribs = limits.maxVertexAttribs;
+ resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors;
+ resources.MaxVertexTextureImageUnits = mGLMaxVertexTextureImageUnits;
+ resources.MaxCombinedTextureImageUnits = limits.maxTexUnits;
+ resources.MaxTextureImageUnits = mGLMaxFragmentTextureImageUnits;
+ resources.MaxFragmentUniformVectors = mGLMaxFragmentUniformVectors;
+
+ resources.MaxVertexOutputVectors = mGLMaxVertexOutputVectors;
+ resources.MaxFragmentInputVectors = mGLMaxFragmentInputVectors;
+ resources.MaxVaryingVectors = mGLMaxFragmentInputVectors;
+
+ if (IsWebGL2()) {
+ resources.MinProgramTexelOffset = mGLMinProgramTexelOffset;
+ resources.MaxProgramTexelOffset = mGLMaxProgramTexelOffset;
+ }
+
+ resources.MaxDrawBuffers = MaxValidDrawBuffers();
+
+ if (IsExtensionEnabled(WebGLExtensionID::EXT_frag_depth))
+ resources.EXT_frag_depth = 1;
+
+ if (IsExtensionEnabled(WebGLExtensionID::OES_standard_derivatives))
+ resources.OES_standard_derivatives = 1;
+
+ if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers))
+ resources.EXT_draw_buffers = 1;
+
+ if (IsExtensionEnabled(WebGLExtensionID::EXT_shader_texture_lod))
+ resources.EXT_shader_texture_lod = 1;
+
+ if (IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) {
+ resources.OVR_multiview = 1;
+ resources.OVR_multiview2 = 1;
+ resources.MaxViewsOVR = limits.maxMultiviewLayers;
+ }
+
+ // Tell ANGLE to allow highp in frag shaders. (unless disabled)
+ // If underlying GLES doesn't have highp in frag shaders, it should complain
+ // anyways.
+ resources.FragmentPrecisionHigh = mDisableFragHighP ? 0 : 1;
+
+ if (gl->WorkAroundDriverBugs()) {
+#ifdef XP_MACOSX
+ if (gl->Vendor() == gl::GLVendor::NVIDIA) {
+ // Work around bug 890432
+ resources.MaxExpressionComplexity = 1000;
+ }
+#endif
+ }
+
+ const auto compileOptions =
+ webgl::ChooseValidatorCompileOptions(resources, gl);
+ auto ret = webgl::ShaderValidator::Create(shaderType, spec, outputLanguage,
+ resources, compileOptions);
+ if (!ret) return ret;
+
+ ret->mIfNeeded_webgl_gl_VertexID_Offset |=
+ mBug_DrawArraysInstancedUserAttribFetchAffectedByFirst;
+
+ return ret;
+}
+
+////////////////////////////////////////
+
+namespace webgl {
+
+/*static*/
+std::unique_ptr<ShaderValidator> ShaderValidator::Create(
+ GLenum shaderType, ShShaderSpec spec, ShShaderOutput outputLanguage,
+ const ShBuiltInResources& resources, ShCompileOptions compileOptions) {
+ ShHandle handle =
+ sh::ConstructCompiler(shaderType, spec, outputLanguage, &resources);
+ MOZ_RELEASE_ASSERT(handle);
+ if (!handle) return nullptr;
+
+ return std::unique_ptr<ShaderValidator>(
+ new ShaderValidator(handle, compileOptions, resources.MaxVaryingVectors));
+}
+
+ShaderValidator::~ShaderValidator() { sh::Destruct(mHandle); }
+
+inline bool StartsWith(const std::string_view& str,
+ const std::string_view& part) {
+ return str.find(part) == 0;
+}
+
+inline std::vector<std::string_view> Split(std::string_view src,
+ const std::string_view& delim,
+ const size_t maxSplits = -1) {
+ std::vector<std::string_view> ret;
+ for (const auto i : IntegerRange(maxSplits)) {
+ (void)i;
+ const auto end = src.find(delim);
+ if (end == size_t(-1)) {
+ break;
+ }
+ ret.push_back(src.substr(0, end));
+ src = src.substr(end + delim.size());
+ }
+ ret.push_back(src);
+ return ret;
+}
+
+std::unique_ptr<const ShaderValidatorResults>
+ShaderValidator::ValidateAndTranslate(const char* const source) const {
+ auto ret = std::make_unique<ShaderValidatorResults>();
+
+ const std::array<const char*, 1> parts = {source};
+ ret->mValid =
+ sh::Compile(mHandle, parts.data(), parts.size(), mCompileOptions);
+
+ ret->mInfoLog = sh::GetInfoLog(mHandle);
+
+ if (ret->mValid) {
+ ret->mObjectCode = sh::GetObjectCode(mHandle);
+ ret->mShaderVersion = sh::GetShaderVersion(mHandle);
+ ret->mVertexShaderNumViews = sh::GetVertexShaderNumViews(mHandle);
+
+ ret->mAttributes = *sh::GetAttributes(mHandle);
+ ret->mInterfaceBlocks = *sh::GetInterfaceBlocks(mHandle);
+ ret->mOutputVariables = *sh::GetOutputVariables(mHandle);
+ ret->mUniforms = *sh::GetUniforms(mHandle);
+ ret->mVaryings = *sh::GetVaryings(mHandle);
+
+ ret->mMaxVaryingVectors = mMaxVaryingVectors;
+
+ const auto& nameMap = *sh::GetNameHashingMap(mHandle);
+ for (const auto& pair : nameMap) {
+ ret->mNameMap.insert(pair);
+ }
+
+ // -
+ // Custom translation steps
+ auto* const translatedSource = &ret->mObjectCode;
+
+ // gl_VertexID -> webgl_gl_VertexID
+ // gl_InstanceID -> webgl_gl_InstanceID
+
+ std::string header;
+ std::string_view body = *translatedSource;
+ if (StartsWith(body, "#version")) {
+ const auto parts = Split(body, "\n", 1);
+ header = parts.at(0);
+ header += "\n";
+ body = parts.at(1);
+ }
+
+ for (const auto& attrib : ret->mAttributes) {
+ if (mIfNeeded_webgl_gl_VertexID_Offset && attrib.name == "gl_VertexID" &&
+ attrib.staticUse) {
+ header += "uniform int webgl_gl_VertexID_Offset;\n";
+ header +=
+ "#define gl_VertexID (gl_VertexID + webgl_gl_VertexID_Offset)\n";
+ ret->mNeeds_webgl_gl_VertexID_Offset = true;
+ }
+ }
+
+ if (header.size()) {
+ auto combined = header;
+ combined += body;
+ *translatedSource = combined;
+ }
+ }
+
+ sh::ClearResults(mHandle);
+ return ret;
+}
+
+bool ShaderValidatorResults::CanLinkTo(const ShaderValidatorResults& vert,
+ nsCString* const out_log) const {
+ MOZ_ASSERT(mValid);
+ MOZ_ASSERT(vert.mValid);
+
+ if (vert.mShaderVersion != mShaderVersion) {
+ nsPrintfCString error(
+ "Vertex shader version %d does not match"
+ " fragment shader version %d.",
+ vert.mShaderVersion, mShaderVersion);
+ *out_log = error;
+ return false;
+ }
+
+ for (const auto& itrFrag : mUniforms) {
+ for (const auto& itrVert : vert.mUniforms) {
+ if (itrVert.name != itrFrag.name) continue;
+
+ if (!itrVert.isSameUniformAtLinkTime(itrFrag)) {
+ nsPrintfCString error(
+ "Uniform `%s` is not linkable between"
+ " attached shaders.",
+ itrFrag.name.c_str());
+ *out_log = error;
+ return false;
+ }
+
+ break;
+ }
+ }
+
+ for (const auto& fragVar : mInterfaceBlocks) {
+ for (const auto& vertVar : vert.mInterfaceBlocks) {
+ if (vertVar.name != fragVar.name) continue;
+
+ if (!vertVar.isSameInterfaceBlockAtLinkTime(fragVar)) {
+ nsPrintfCString error(
+ "Interface block `%s` is not linkable between"
+ " attached shaders.",
+ fragVar.name.c_str());
+ *out_log = error;
+ return false;
+ }
+
+ break;
+ }
+ }
+
+ {
+ std::vector<sh::ShaderVariable> staticUseVaryingList;
+
+ for (const auto& fragVarying : mVaryings) {
+ static const char prefix[] = "gl_";
+ if (StartsWith(fragVarying.name, prefix)) {
+ if (fragVarying.staticUse) {
+ staticUseVaryingList.push_back(fragVarying);
+ }
+ continue;
+ }
+
+ bool definedInVertShader = false;
+ bool staticVertUse = false;
+
+ for (const auto& vertVarying : vert.mVaryings) {
+ if (vertVarying.name != fragVarying.name) continue;
+
+ if (!vertVarying.isSameVaryingAtLinkTime(fragVarying, mShaderVersion)) {
+ nsPrintfCString error(
+ "Varying `%s`is not linkable between"
+ " attached shaders.",
+ fragVarying.name.c_str());
+ *out_log = error;
+ return false;
+ }
+
+ definedInVertShader = true;
+ staticVertUse = vertVarying.staticUse;
+ break;
+ }
+
+ if (!definedInVertShader && fragVarying.staticUse) {
+ nsPrintfCString error(
+ "Varying `%s` has static-use in the frag"
+ " shader, but is undeclared in the vert"
+ " shader.",
+ fragVarying.name.c_str());
+ *out_log = error;
+ return false;
+ }
+
+ if (staticVertUse && fragVarying.staticUse) {
+ staticUseVaryingList.push_back(fragVarying);
+ }
+ }
+
+ if (!sh::CheckVariablesWithinPackingLimits(mMaxVaryingVectors,
+ staticUseVaryingList)) {
+ *out_log =
+ "Statically used varyings do not fit within packing limits. (see"
+ " GLSL ES Specification 1.0.17, p111)";
+ return false;
+ }
+ }
+
+ if (mShaderVersion == 100) {
+ // Enforce ESSL1 invariant linking rules.
+ bool isInvariant_Position = false;
+ bool isInvariant_PointSize = false;
+ bool isInvariant_FragCoord = false;
+ bool isInvariant_PointCoord = false;
+
+ for (const auto& varying : vert.mVaryings) {
+ if (varying.name == "gl_Position") {
+ isInvariant_Position = varying.isInvariant;
+ } else if (varying.name == "gl_PointSize") {
+ isInvariant_PointSize = varying.isInvariant;
+ }
+ }
+
+ for (const auto& varying : mVaryings) {
+ if (varying.name == "gl_FragCoord") {
+ isInvariant_FragCoord = varying.isInvariant;
+ } else if (varying.name == "gl_PointCoord") {
+ isInvariant_PointCoord = varying.isInvariant;
+ }
+ }
+
+ ////
+
+ const auto fnCanBuiltInsLink = [](bool vertIsInvariant,
+ bool fragIsInvariant) {
+ if (vertIsInvariant) return true;
+
+ return !fragIsInvariant;
+ };
+
+ if (!fnCanBuiltInsLink(isInvariant_Position, isInvariant_FragCoord)) {
+ *out_log =
+ "gl_Position must be invariant if gl_FragCoord is. (see GLSL ES"
+ " Specification 1.0.17, p39)";
+ return false;
+ }
+
+ if (!fnCanBuiltInsLink(isInvariant_PointSize, isInvariant_PointCoord)) {
+ *out_log =
+ "gl_PointSize must be invariant if gl_PointCoord is. (see GLSL ES"
+ " Specification 1.0.17, p39)";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+size_t ShaderValidatorResults::SizeOfIncludingThis(
+ const MallocSizeOf fnSizeOf) const {
+ auto ret = fnSizeOf(this);
+ ret += mInfoLog.size();
+ ret += mObjectCode.size();
+
+ for (const auto& cur : mAttributes) {
+ ret += fnSizeOf(&cur);
+ }
+ for (const auto& cur : mInterfaceBlocks) {
+ ret += fnSizeOf(&cur);
+ }
+ for (const auto& cur : mOutputVariables) {
+ ret += fnSizeOf(&cur);
+ }
+ for (const auto& cur : mUniforms) {
+ ret += fnSizeOf(&cur);
+ }
+ for (const auto& cur : mVaryings) {
+ ret += fnSizeOf(&cur);
+ }
+
+ return ret;
+}
+
+} // namespace webgl
+} // namespace mozilla