/* -*- 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 #include #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 | SH_INITIALIZE_UNINITIALIZED_LOCALS | SH_INIT_OUTPUT_VARIABLES; #ifdef XP_MACOSX options |= SH_REMOVE_INVARIANT_AND_CENTROID_FOR_ESSL3; #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 |= SH_CLAMP_INDIRECT_ARRAY_BOUNDS; #endif if (gl->WorkAroundDriverBugs()) { #ifdef XP_MACOSX // 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; if (gl->Vendor() == gl::GLVendor::Intel) { // Work around that Intel drivers on Mac OSX handle for-loop incorrectly. options |= SH_ADD_AND_TRUE_TO_LOOP_CONDITION; options |= SH_REWRITE_TEXELFETCHOFFSET_TO_TEXELFETCH; } #endif if (!gl->IsANGLE() && gl->Vendor() == gl::GLVendor::Intel) { // Failures on at least Windows+Intel+OGL on: // conformance/glsl/constructors/glsl-construct-mat2.html options |= SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS; } } if (StaticPrefs::webgl_all_angle_options()) { 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; 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 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; resources.MaxVertexAttribs = mGLMaxVertexAttribs; resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors; resources.MaxVertexTextureImageUnits = mGLMaxVertexTextureImageUnits; resources.MaxCombinedTextureImageUnits = mGLMaxCombinedTextureImageUnits; resources.MaxTextureImageUnits = mGLMaxFragmentTextureImageUnits; resources.MaxFragmentUniformVectors = mGLMaxFragmentUniformVectors; resources.MaxVertexOutputVectors = mGLMaxVertexOutputVectors; resources.MaxFragmentInputVectors = mGLMaxFragmentInputVectors; resources.MaxVaryingVectors = mGLMaxFragmentInputVectors; if (IsWebGL2()) { resources.MinProgramTexelOffset = mGLMinProgramTexelOffset; resources.MaxProgramTexelOffset = mGLMaxProgramTexelOffset; } 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; if (IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) { resources.OVR_multiview2 = 1; resources.MaxViewsOVR = mGLMaxMultiviewViews; } // 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*/ std::unique_ptr 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( new ShaderValidator(handle, compileOptions, resources.MaxVaryingVectors)); } ShaderValidator::~ShaderValidator() { sh::Destruct(mHandle); } std::unique_ptr ShaderValidator::ValidateAndTranslate(const char* const source) const { auto ret = std::make_unique(); const std::array 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; } sh::ClearResults(mHandle); return ret; } template static bool StartsWith(const std::string& haystack, const char (&needle)[N]) { return haystack.compare(0, N - 1, needle) == 0; } 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 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; } // This must handle names like "foo.bar[0]". bool ShaderValidatorResults::FindUniformByMappedName( const std::string& mappedName, std::string* const out_userName, bool* const out_isArray) const { for (const auto& cur : mUniforms) { const sh::ShaderVariable* found; if (!cur.findInfoByMappedName(mappedName, &found, out_userName)) continue; *out_isArray = found->isArray(); return true; } const size_t dotPos = mappedName.find("."); for (const auto& interface : mInterfaceBlocks) { 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; } 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