diff options
Diffstat (limited to 'dom/canvas/WebGLShaderValidator.cpp')
-rw-r--r-- | dom/canvas/WebGLShaderValidator.cpp | 492 |
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 |