diff options
Diffstat (limited to 'dom/canvas/WebGLShaderValidator.cpp')
-rw-r--r-- | dom/canvas/WebGLShaderValidator.cpp | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/dom/canvas/WebGLShaderValidator.cpp b/dom/canvas/WebGLShaderValidator.cpp new file mode 100644 index 000000000..80ba359a3 --- /dev/null +++ b/dom/canvas/WebGLShaderValidator.cpp @@ -0,0 +1,604 @@ +/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "angle/ShaderLang.h" +#include "gfxPrefs.h" +#include "GLContext.h" +#include "mozilla/Preferences.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 = SH_VARIABLES | + SH_ENFORCE_PACKING_RESTRICTIONS | + SH_OBJECT_CODE | + SH_INIT_GL_POSITION; + + // Sampler arrays indexed with non-constant expressions are forbidden in + // GLSL 1.30 and later. + // ESSL 3 requires constant-integral-expressions for this as well. + // Just do it universally. + options |= SH_UNROLL_FOR_LOOP_WITH_SAMPLER_ARRAY_INDEX; + +#ifndef XP_MACOSX + // 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 |= SH_CLAMP_INDIRECT_ARRAY_BOUNDS; +#endif + +#ifdef XP_MACOSX + if (gl->WorkAroundDriverBugs()) { + // Work around https://bugs.webkit.org/show_bug.cgi?id=124684, + // https://chromium.googlesource.com/angle/angle/+/5e70cf9d0b1bb + options |= SH_UNFOLD_SHORT_CIRCUIT; + + // Work around that Mac drivers handle struct scopes incorrectly. + options |= SH_REGENERATE_STRUCT_NAMES; + options |= SH_INIT_OUTPUT_VARIABLES; + } +#endif + + if (gfxPrefs::WebGLAllANGLEOptions()) { + options = -1; + + options ^= SH_INTERMEDIATE_TREE; + options ^= SH_LINE_DIRECTIVES; + options ^= SH_SOURCE_PATH; + + options ^= SH_LIMIT_EXPRESSION_COMPLEXITY; + options ^= SH_LIMIT_CALL_STACK_DEPTH; + + options ^= SH_EXPAND_SELECT_HLSL_INTEGER_POW_EXPRESSIONS; + options ^= SH_HLSL_GET_DIMENSIONS_IGNORES_BASE_LEVEL; + + options ^= SH_DONT_REMOVE_INVARIANT_FOR_FRAGMENT_INPUT; + options ^= SH_REMOVE_INVARIANT_AND_CENTROID_FOR_ESSL3; + } + + if (resources.MaxExpressionComplexity > 0) { + options |= SH_LIMIT_EXPRESSION_COMPLEXITY; + } + if (resources.MaxCallStackDepth > 0) { + options |= SH_LIMIT_CALL_STACK_DEPTH; + } + + return options; +} + +} // namespace webgl + +//////////////////////////////////////// + +static ShShaderOutput +ShaderOutput(gl::GLContext* gl) +{ + if (gl->IsGLES()) { + return SH_ESSL_OUTPUT; + } else { + 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; + case 450: return SH_GLSL_450_CORE_OUTPUT; + default: + MOZ_ASSERT(false, "GFX: Unexpected GLSL version."); + } + } + + return SH_GLSL_COMPATIBILITY_OUTPUT; +} + +webgl::ShaderValidator* +WebGLContext::CreateShaderValidator(GLenum shaderType) const +{ + if (mBypassShaderValidation) + return nullptr; + + const auto spec = (IsWebGL2() ? SH_WEBGL2_SPEC : SH_WEBGL_SPEC); + const auto outputLanguage = ShaderOutput(gl); + + ShBuiltInResources resources; + memset(&resources, 0, sizeof(resources)); + ShInitBuiltInResources(&resources); + + resources.HashFunction = webgl::IdentifierHashFunc; + + resources.MaxVertexAttribs = mGLMaxVertexAttribs; + resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors; + resources.MaxVaryingVectors = mGLMaxVaryingVectors; + resources.MaxVertexTextureImageUnits = mGLMaxVertexTextureImageUnits; + resources.MaxCombinedTextureImageUnits = mGLMaxTextureUnits; + resources.MaxTextureImageUnits = mGLMaxTextureImageUnits; + resources.MaxFragmentUniformVectors = mGLMaxFragmentUniformVectors; + + const bool hasMRTs = (IsWebGL2() || + IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers)); + resources.MaxDrawBuffers = (hasMRTs ? mGLMaxDrawBuffers : 1); + + 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; + + // 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); + return webgl::ShaderValidator::Create(shaderType, spec, outputLanguage, resources, + compileOptions); +} + +//////////////////////////////////////// + +namespace webgl { + +/*static*/ ShaderValidator* +ShaderValidator::Create(GLenum shaderType, ShShaderSpec spec, + ShShaderOutput outputLanguage, + const ShBuiltInResources& resources, + ShCompileOptions compileOptions) +{ + ShHandle handle = ShConstructCompiler(shaderType, spec, outputLanguage, &resources); + if (!handle) + return nullptr; + + return new ShaderValidator(handle, compileOptions, resources.MaxVaryingVectors); +} + +ShaderValidator::~ShaderValidator() +{ + ShDestruct(mHandle); +} + +bool +ShaderValidator::ValidateAndTranslate(const char* source) +{ + MOZ_ASSERT(!mHasRun); + mHasRun = true; + + const char* const parts[] = { + source + }; + return ShCompile(mHandle, parts, ArrayLength(parts), mCompileOptions); +} + +void +ShaderValidator::GetInfoLog(nsACString* out) const +{ + MOZ_ASSERT(mHasRun); + + const std::string &log = ShGetInfoLog(mHandle); + out->Assign(log.data(), log.length()); +} + +void +ShaderValidator::GetOutput(nsACString* out) const +{ + MOZ_ASSERT(mHasRun); + + const std::string &output = ShGetObjectCode(mHandle); + out->Assign(output.data(), output.length()); +} + +template<size_t N> +static bool +StartsWith(const std::string& haystack, const char (&needle)[N]) +{ + return haystack.compare(0, N - 1, needle) == 0; +} + +bool +ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log) const +{ + if (!prev) { + nsPrintfCString error("Passed in NULL prev ShaderValidator."); + *out_log = error; + return false; + } + + const auto shaderVersion = ShGetShaderVersion(mHandle); + if (ShGetShaderVersion(prev->mHandle) != shaderVersion) { + nsPrintfCString error("Vertex shader version %d does not match" + " fragment shader version %d.", + ShGetShaderVersion(prev->mHandle), + ShGetShaderVersion(mHandle)); + *out_log = error; + return false; + } + + { + const std::vector<sh::Uniform>* vertPtr = ShGetUniforms(prev->mHandle); + const std::vector<sh::Uniform>* fragPtr = ShGetUniforms(mHandle); + if (!vertPtr || !fragPtr) { + nsPrintfCString error("Could not create uniform list."); + *out_log = error; + return false; + } + + for (auto itrFrag = fragPtr->begin(); itrFrag != fragPtr->end(); ++itrFrag) { + for (auto itrVert = vertPtr->begin(); itrVert != vertPtr->end(); ++itrVert) { + 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; + } + } + } + { + const auto vertVars = sh::GetInterfaceBlocks(prev->mHandle); + const auto fragVars = sh::GetInterfaceBlocks(mHandle); + if (!vertVars || !fragVars) { + nsPrintfCString error("Could not create uniform block list."); + *out_log = error; + return false; + } + + for (const auto& fragVar : *fragVars) { + for (const auto& vertVar : *vertVars) { + 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; + } + } + } + + const auto& vertVaryings = ShGetVaryings(prev->mHandle); + const auto& fragVaryings = ShGetVaryings(mHandle); + if (!vertVaryings || !fragVaryings) { + nsPrintfCString error("Could not create varying list."); + *out_log = error; + return false; + } + + { + std::vector<sh::ShaderVariable> staticUseVaryingList; + + for (const auto& fragVarying : *fragVaryings) { + 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 : *vertVaryings) { + if (vertVarying.name != fragVarying.name) + continue; + + if (!vertVarying.isSameVaryingAtLinkTime(fragVarying, shaderVersion)) { + 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 (!ShCheckVariablesWithinPackingLimits(mMaxVaryingVectors, + staticUseVaryingList)) + { + *out_log = "Statically used varyings do not fit within packing limits. (see" + " GLSL ES Specification 1.0.17, p111)"; + return false; + } + } + + if (shaderVersion == 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 : *vertVaryings) { + if (varying.name == "gl_Position") { + isInvariant_Position = varying.isInvariant; + } else if (varying.name == "gl_PointSize") { + isInvariant_PointSize = varying.isInvariant; + } + } + + for (const auto& varying : *fragVaryings) { + 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 +ShaderValidator::CalcNumSamplerUniforms() const +{ + size_t accum = 0; + + const std::vector<sh::Uniform>& uniforms = *ShGetUniforms(mHandle); + + for (auto itr = uniforms.begin(); itr != uniforms.end(); ++itr) { + GLenum type = itr->type; + if (type == LOCAL_GL_SAMPLER_2D || + type == LOCAL_GL_SAMPLER_CUBE) + { + accum += itr->arraySize; + } + } + + return accum; +} + +size_t +ShaderValidator::NumAttributes() const +{ + return ShGetAttributes(mHandle)->size(); +} + +// Attribs cannot be structs or arrays, and neither can vertex inputs in ES3. +// Therefore, attrib names are always simple. +bool +ShaderValidator::FindAttribUserNameByMappedName(const std::string& mappedName, + const std::string** const out_userName) const +{ + const std::vector<sh::Attribute>& attribs = *ShGetAttributes(mHandle); + for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) { + if (itr->mappedName == mappedName) { + *out_userName = &(itr->name); + return true; + } + } + + return false; +} + +bool +ShaderValidator::FindAttribMappedNameByUserName(const std::string& userName, + const std::string** const out_mappedName) const +{ + const std::vector<sh::Attribute>& attribs = *ShGetAttributes(mHandle); + for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) { + if (itr->name == userName) { + *out_mappedName = &(itr->mappedName); + return true; + } + } + + return false; +} + +bool +ShaderValidator::FindVaryingByMappedName(const std::string& mappedName, + std::string* const out_userName, + bool* const out_isArray) const +{ + const std::vector<sh::Varying>& varyings = *ShGetVaryings(mHandle); + for (auto itr = varyings.begin(); itr != varyings.end(); ++itr) { + const sh::ShaderVariable* found; + if (!itr->findInfoByMappedName(mappedName, &found, out_userName)) + continue; + + *out_isArray = found->isArray(); + return true; + } + + return false; +} + +bool +ShaderValidator::FindVaryingMappedNameByUserName(const std::string& userName, + const std::string** const out_mappedName) const +{ + const std::vector<sh::Varying>& attribs = *ShGetVaryings(mHandle); + for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) { + if (itr->name == userName) { + *out_mappedName = &(itr->mappedName); + return true; + } + } + + return false; +} +// This must handle names like "foo.bar[0]". +bool +ShaderValidator::FindUniformByMappedName(const std::string& mappedName, + std::string* const out_userName, + bool* const out_isArray) const +{ + const std::vector<sh::Uniform>& uniforms = *ShGetUniforms(mHandle); + for (auto itr = uniforms.begin(); itr != uniforms.end(); ++itr) { + const sh::ShaderVariable* found; + if (!itr->findInfoByMappedName(mappedName, &found, out_userName)) + continue; + + *out_isArray = found->isArray(); + return true; + } + + const size_t dotPos = mappedName.find("."); + + const std::vector<sh::InterfaceBlock>& interfaces = *ShGetInterfaceBlocks(mHandle); + for (const auto& interface : interfaces) { + + std::string mappedFieldName; + const bool hasInstanceName = !interface.instanceName.empty(); + + // If the InterfaceBlock has an instanceName, all variables defined + // within the block are qualified with the block name, as opposed + // to being placed in the global scope. + if (hasInstanceName) { + + // If mappedName has no block name prefix, skip + if (std::string::npos == dotPos) + continue; + + // If mappedName has a block name prefix that doesn't match, skip + const std::string mappedInterfaceBlockName = mappedName.substr(0, dotPos); + if (interface.mappedName != mappedInterfaceBlockName) + continue; + + mappedFieldName = mappedName.substr(dotPos + 1); + } else { + mappedFieldName = mappedName; + } + + for (const auto& field : interface.fields) { + const sh::ShaderVariable* found; + + if (!field.findInfoByMappedName(mappedFieldName, &found, out_userName)) + continue; + + if (hasInstanceName) { + // Prepend the user name of the interface that matched + *out_userName = interface.name + "." + *out_userName; + } + + *out_isArray = found->isArray(); + return true; + } + } + + return false; +} + +bool +ShaderValidator::UnmapUniformBlockName(const nsACString& baseMappedName, + nsCString* const out_baseUserName) const +{ + const std::vector<sh::InterfaceBlock>& interfaces = *ShGetInterfaceBlocks(mHandle); + for (const auto& interface : interfaces) { + const nsDependentCString interfaceMappedName(interface.mappedName.data(), + interface.mappedName.size()); + if (baseMappedName == interfaceMappedName) { + *out_baseUserName = interface.name.data(); + return true; + } + } + + return false; +} + +void +ShaderValidator::EnumerateFragOutputs(std::map<nsCString, const nsCString> &out_FragOutputs) const +{ + const auto* fragOutputs = ShGetOutputVariables(mHandle); + + if (fragOutputs) { + for (const auto& fragOutput : *fragOutputs) { + out_FragOutputs.insert({nsCString(fragOutput.name.c_str()), + nsCString(fragOutput.mappedName.c_str())}); + } + } +} + +} // namespace webgl +} // namespace mozilla |