WebGLShaderValidator.cpp (15352B)
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "WebGLShaderValidator.h" 7 8 #include <string> 9 #include <vector> 10 11 #include "GLContext.h" 12 #include "MurmurHash3.h" 13 #include "WebGLContext.h" 14 #include "mozilla/Preferences.h" 15 #include "mozilla/StaticPrefs_webgl.h" 16 #include "mozilla/gfx/Logging.h" 17 #include "nsPrintfCString.h" 18 19 namespace mozilla { 20 namespace webgl { 21 22 uint64_t IdentifierHashFunc(const char* name, size_t len) { 23 // NB: we use the x86 function everywhere, even though it's suboptimal perf 24 // on x64. They return different results; not sure if that's a requirement. 25 uint64_t hash[2]; 26 MurmurHash3_x86_128(name, len, 0, hash); 27 return hash[0]; 28 } 29 30 static ShCompileOptions ChooseValidatorCompileOptions( 31 const ShBuiltInResources& resources, const mozilla::gl::GLContext* gl) { 32 ShCompileOptions options = {}; 33 options.variables = true; 34 options.enforcePackingRestrictions = true; 35 options.objectCode = true; 36 options.initGLPosition = true; 37 options.initializeUninitializedLocals = true; 38 options.initOutputVariables = true; 39 options.clampIndirectArrayBounds = true; 40 41 if (kIsMacOS) { 42 options.removeInvariantAndCentroidForESSL3 = true; 43 } 44 45 if (gl->WorkAroundDriverBugs()) { 46 if (kIsMacOS) { 47 // Work around https://bugs.webkit.org/show_bug.cgi?id=124684, 48 // https://chromium.googlesource.com/angle/angle/+/5e70cf9d0b1bb 49 options.unfoldShortCircuit = true; 50 51 // Work around that Mac drivers handle struct scopes incorrectly. 52 options.regenerateStructNames = true; 53 options.initOutputVariables = true; 54 options.initGLPointSize = true; 55 56 if (gl->Vendor() == gl::GLVendor::Intel) { 57 // Work around that Intel drivers on Mac OSX handle for-loop 58 // incorrectly. 59 options.addAndTrueToLoopCondition = true; 60 61 options.rewriteTexelFetchOffsetToTexelFetch = true; 62 } 63 } 64 65 if (!gl->IsANGLE() && gl->Vendor() == gl::GLVendor::Intel) { 66 // Failures on at least Windows+Intel+OGL on: 67 // conformance/glsl/constructors/glsl-construct-mat2.html 68 options.scalarizeVecAndMatConstructorArgs = true; 69 } 70 } 71 72 // - 73 74 if (resources.MaxExpressionComplexity > 0) { 75 options.limitExpressionComplexity = true; 76 } 77 if (resources.MaxCallStackDepth > 0) { 78 options.limitCallStackDepth = true; 79 } 80 81 return options; 82 } 83 84 } // namespace webgl 85 86 //////////////////////////////////////// 87 88 static ShShaderOutput ShaderOutput(gl::GLContext* gl) { 89 if (gl->IsGLES()) { 90 return SH_ESSL_OUTPUT; 91 } 92 uint32_t version = gl->ShadingLanguageVersion(); 93 switch (version) { 94 case 100: 95 return SH_GLSL_COMPATIBILITY_OUTPUT; 96 case 120: 97 return SH_GLSL_COMPATIBILITY_OUTPUT; 98 case 130: 99 return SH_GLSL_130_OUTPUT; 100 case 140: 101 return SH_GLSL_140_OUTPUT; 102 case 150: 103 return SH_GLSL_150_CORE_OUTPUT; 104 case 330: 105 return SH_GLSL_330_CORE_OUTPUT; 106 case 400: 107 return SH_GLSL_400_CORE_OUTPUT; 108 case 410: 109 return SH_GLSL_410_CORE_OUTPUT; 110 case 420: 111 return SH_GLSL_420_CORE_OUTPUT; 112 case 430: 113 return SH_GLSL_430_CORE_OUTPUT; 114 case 440: 115 return SH_GLSL_440_CORE_OUTPUT; 116 default: 117 if (version >= 450) { 118 // "OpenGL 4.6 is also guaranteed to support all previous versions of 119 // the OpenGL Shading Language back to version 1.10." 120 return SH_GLSL_450_CORE_OUTPUT; 121 } 122 gfxCriticalNote << "Unexpected GLSL version: " << version; 123 } 124 125 return SH_GLSL_COMPATIBILITY_OUTPUT; 126 } 127 128 std::unique_ptr<webgl::ShaderValidator> WebGLContext::CreateShaderValidator( 129 GLenum shaderType) const { 130 const auto spec = (IsWebGL2() ? SH_WEBGL2_SPEC : SH_WEBGL_SPEC); 131 const auto outputLanguage = ShaderOutput(gl); 132 133 ShBuiltInResources resources; 134 sh::InitBuiltInResources(&resources); 135 136 resources.HashFunction = webgl::IdentifierHashFunc; 137 138 const auto& limits = Limits(); 139 140 resources.MaxVertexAttribs = limits.maxVertexAttribs; 141 resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors; 142 resources.MaxVertexTextureImageUnits = mGLMaxVertexTextureImageUnits; 143 resources.MaxCombinedTextureImageUnits = limits.maxTexUnits; 144 resources.MaxTextureImageUnits = mGLMaxFragmentTextureImageUnits; 145 resources.MaxFragmentUniformVectors = mGLMaxFragmentUniformVectors; 146 147 resources.MaxVertexOutputVectors = mGLMaxVertexOutputVectors; 148 resources.MaxFragmentInputVectors = mGLMaxFragmentInputVectors; 149 resources.MaxVaryingVectors = mGLMaxFragmentInputVectors; 150 151 if (IsWebGL2()) { 152 resources.MinProgramTexelOffset = mGLMinProgramTexelOffset; 153 resources.MaxProgramTexelOffset = mGLMaxProgramTexelOffset; 154 } 155 156 resources.MaxDrawBuffers = MaxValidDrawBuffers(); 157 158 if (IsExtensionEnabled(WebGLExtensionID::EXT_frag_depth)) 159 resources.EXT_frag_depth = 1; 160 161 if (IsExtensionEnabled(WebGLExtensionID::OES_standard_derivatives)) 162 resources.OES_standard_derivatives = 1; 163 164 if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers)) 165 resources.EXT_draw_buffers = 1; 166 167 if (IsExtensionEnabled(WebGLExtensionID::EXT_shader_texture_lod)) 168 resources.EXT_shader_texture_lod = 1; 169 170 if (IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) { 171 resources.OVR_multiview = 1; 172 resources.OVR_multiview2 = 1; 173 resources.MaxViewsOVR = limits.maxMultiviewLayers; 174 } 175 176 // Tell ANGLE to allow highp in frag shaders. (unless disabled) 177 // If underlying GLES doesn't have highp in frag shaders, it should complain 178 // anyways. 179 resources.FragmentPrecisionHigh = mDisableFragHighP ? 0 : 1; 180 181 if (gl->WorkAroundDriverBugs()) { 182 #ifdef XP_MACOSX 183 if (gl->Vendor() == gl::GLVendor::NVIDIA) { 184 // Work around bug 890432 185 resources.MaxExpressionComplexity = 1000; 186 } 187 #endif 188 } 189 190 // - 191 192 resources.MaxVariableSizeInBytes = [&]() -> size_t { 193 const auto kibytes = StaticPrefs::webgl_glsl_max_var_size_in_kibytes(); 194 if (kibytes >= 0) { 195 return static_cast<size_t>(kibytes) * 1024; 196 } 197 198 return resources.MaxVariableSizeInBytes; 199 }(); 200 201 resources.MaxPrivateVariableSizeInBytes = [&]() -> size_t { 202 const auto bytes = StaticPrefs::webgl_glsl_max_private_var_size_in_bytes(); 203 if (bytes >= 0) { 204 return static_cast<size_t>(bytes); 205 } 206 207 if (kIsMacOS) { 208 return 128 * 1024; // 8k vec4s 209 } 210 211 return resources.MaxPrivateVariableSizeInBytes; 212 }(); 213 214 // - 215 216 const auto compileOptions = 217 webgl::ChooseValidatorCompileOptions(resources, gl); 218 auto ret = webgl::ShaderValidator::Create(shaderType, spec, outputLanguage, 219 resources, compileOptions); 220 if (!ret) return ret; 221 222 ret->mIfNeeded_webgl_gl_VertexID_Offset |= 223 mBug_DrawArraysInstancedUserAttribFetchAffectedByFirst; 224 225 return ret; 226 } 227 228 //////////////////////////////////////// 229 230 namespace webgl { 231 232 /*static*/ 233 std::unique_ptr<ShaderValidator> ShaderValidator::Create( 234 GLenum shaderType, ShShaderSpec spec, ShShaderOutput outputLanguage, 235 const ShBuiltInResources& resources, ShCompileOptions compileOptions) { 236 ShHandle handle = 237 sh::ConstructCompiler(shaderType, spec, outputLanguage, &resources); 238 MOZ_RELEASE_ASSERT(handle); 239 if (!handle) return nullptr; 240 241 return std::unique_ptr<ShaderValidator>( 242 new ShaderValidator(handle, compileOptions, resources.MaxVaryingVectors)); 243 } 244 245 ShaderValidator::~ShaderValidator() { sh::Destruct(mHandle); } 246 247 inline bool StartsWith(const std::string_view& str, 248 const std::string_view& part) { 249 return str.find(part) == 0; 250 } 251 252 inline std::vector<std::string_view> Split(std::string_view src, 253 const std::string_view& delim, 254 const size_t maxSplits = -1) { 255 std::vector<std::string_view> ret; 256 for (const auto i : IntegerRange(maxSplits)) { 257 (void)i; 258 const auto end = src.find(delim); 259 if (end == size_t(-1)) { 260 break; 261 } 262 ret.push_back(src.substr(0, end)); 263 src = src.substr(end + delim.size()); 264 } 265 ret.push_back(src); 266 return ret; 267 } 268 269 std::unique_ptr<const ShaderValidatorResults> 270 ShaderValidator::ValidateAndTranslate(const char* const source) const { 271 auto ret = std::make_unique<ShaderValidatorResults>(); 272 273 const std::array<const char*, 1> parts = {source}; 274 ret->mValid = 275 sh::Compile(mHandle, parts.data(), parts.size(), mCompileOptions); 276 277 ret->mInfoLog = sh::GetInfoLog(mHandle); 278 279 if (ret->mValid) { 280 ret->mObjectCode = sh::GetObjectCode(mHandle); 281 ret->mShaderVersion = sh::GetShaderVersion(mHandle); 282 ret->mVertexShaderNumViews = sh::GetVertexShaderNumViews(mHandle); 283 284 ret->mAttributes = *sh::GetAttributes(mHandle); 285 ret->mInterfaceBlocks = *sh::GetInterfaceBlocks(mHandle); 286 ret->mOutputVariables = *sh::GetOutputVariables(mHandle); 287 ret->mUniforms = *sh::GetUniforms(mHandle); 288 ret->mVaryings = *sh::GetVaryings(mHandle); 289 290 ret->mMaxVaryingVectors = mMaxVaryingVectors; 291 292 const auto& nameMap = *sh::GetNameHashingMap(mHandle); 293 for (const auto& pair : nameMap) { 294 ret->mNameMap.insert(pair); 295 } 296 297 // - 298 // Custom translation steps 299 auto* const translatedSource = &ret->mObjectCode; 300 301 // gl_VertexID -> webgl_gl_VertexID 302 // gl_InstanceID -> webgl_gl_InstanceID 303 304 std::string header; 305 std::string_view body = *translatedSource; 306 if (StartsWith(body, "#version")) { 307 const auto parts = Split(body, "\n", 1); 308 header = parts.at(0); 309 header += "\n"; 310 body = parts.at(1); 311 } 312 313 for (const auto& attrib : ret->mAttributes) { 314 if (mIfNeeded_webgl_gl_VertexID_Offset && attrib.name == "gl_VertexID" && 315 attrib.staticUse) { 316 header += "uniform int webgl_gl_VertexID_Offset;\n"; 317 header += 318 "#define gl_VertexID (gl_VertexID + webgl_gl_VertexID_Offset)\n"; 319 ret->mNeeds_webgl_gl_VertexID_Offset = true; 320 } 321 } 322 323 if (header.size()) { 324 auto combined = header; 325 combined += body; 326 *translatedSource = combined; 327 } 328 } 329 330 sh::ClearResults(mHandle); 331 return ret; 332 } 333 334 bool ShaderValidatorResults::CanLinkTo(const ShaderValidatorResults& vert, 335 nsCString* const out_log) const { 336 MOZ_ASSERT(mValid); 337 MOZ_ASSERT(vert.mValid); 338 339 if (vert.mShaderVersion != mShaderVersion) { 340 nsPrintfCString error( 341 "Vertex shader version %d does not match" 342 " fragment shader version %d.", 343 vert.mShaderVersion, mShaderVersion); 344 *out_log = error; 345 return false; 346 } 347 348 for (const auto& itrFrag : mUniforms) { 349 for (const auto& itrVert : vert.mUniforms) { 350 if (itrVert.name != itrFrag.name) continue; 351 352 if (!itrVert.isSameUniformAtLinkTime(itrFrag)) { 353 nsPrintfCString error( 354 "Uniform `%s` is not linkable between" 355 " attached shaders.", 356 itrFrag.name.c_str()); 357 *out_log = error; 358 return false; 359 } 360 361 break; 362 } 363 } 364 365 for (const auto& fragVar : mInterfaceBlocks) { 366 for (const auto& vertVar : vert.mInterfaceBlocks) { 367 if (vertVar.name != fragVar.name) continue; 368 369 if (!vertVar.isSameInterfaceBlockAtLinkTime(fragVar)) { 370 nsPrintfCString error( 371 "Interface block `%s` is not linkable between" 372 " attached shaders.", 373 fragVar.name.c_str()); 374 *out_log = error; 375 return false; 376 } 377 378 break; 379 } 380 } 381 382 { 383 std::vector<sh::ShaderVariable> staticUseVaryingList; 384 385 for (const auto& fragVarying : mVaryings) { 386 static const char prefix[] = "gl_"; 387 if (StartsWith(fragVarying.name, prefix)) { 388 if (fragVarying.staticUse) { 389 staticUseVaryingList.push_back(fragVarying); 390 } 391 continue; 392 } 393 394 bool definedInVertShader = false; 395 bool staticVertUse = false; 396 397 for (const auto& vertVarying : vert.mVaryings) { 398 if (vertVarying.name != fragVarying.name) continue; 399 400 if (!vertVarying.isSameVaryingAtLinkTime(fragVarying, mShaderVersion)) { 401 nsPrintfCString error( 402 "Varying `%s`is not linkable between" 403 " attached shaders.", 404 fragVarying.name.c_str()); 405 *out_log = error; 406 return false; 407 } 408 409 definedInVertShader = true; 410 staticVertUse = vertVarying.staticUse; 411 break; 412 } 413 414 if (!definedInVertShader && fragVarying.staticUse) { 415 nsPrintfCString error( 416 "Varying `%s` has static-use in the frag" 417 " shader, but is undeclared in the vert" 418 " shader.", 419 fragVarying.name.c_str()); 420 *out_log = error; 421 return false; 422 } 423 424 if (staticVertUse && fragVarying.staticUse) { 425 staticUseVaryingList.push_back(fragVarying); 426 } 427 } 428 429 if (!sh::CheckVariablesWithinPackingLimits(mMaxVaryingVectors, 430 staticUseVaryingList)) { 431 *out_log = 432 "Statically used varyings do not fit within packing limits. (see" 433 " GLSL ES Specification 1.0.17, p111)"; 434 return false; 435 } 436 } 437 438 if (mShaderVersion == 100) { 439 // Enforce ESSL1 invariant linking rules. 440 bool isInvariant_Position = false; 441 bool isInvariant_PointSize = false; 442 bool isInvariant_FragCoord = false; 443 bool isInvariant_PointCoord = false; 444 445 for (const auto& varying : vert.mVaryings) { 446 if (varying.name == "gl_Position") { 447 isInvariant_Position = varying.isInvariant; 448 } else if (varying.name == "gl_PointSize") { 449 isInvariant_PointSize = varying.isInvariant; 450 } 451 } 452 453 for (const auto& varying : mVaryings) { 454 if (varying.name == "gl_FragCoord") { 455 isInvariant_FragCoord = varying.isInvariant; 456 } else if (varying.name == "gl_PointCoord") { 457 isInvariant_PointCoord = varying.isInvariant; 458 } 459 } 460 461 //// 462 463 const auto fnCanBuiltInsLink = [](bool vertIsInvariant, 464 bool fragIsInvariant) { 465 if (vertIsInvariant) return true; 466 467 return !fragIsInvariant; 468 }; 469 470 if (!fnCanBuiltInsLink(isInvariant_Position, isInvariant_FragCoord)) { 471 *out_log = 472 "gl_Position must be invariant if gl_FragCoord is. (see GLSL ES" 473 " Specification 1.0.17, p39)"; 474 return false; 475 } 476 477 if (!fnCanBuiltInsLink(isInvariant_PointSize, isInvariant_PointCoord)) { 478 *out_log = 479 "gl_PointSize must be invariant if gl_PointCoord is. (see GLSL ES" 480 " Specification 1.0.17, p39)"; 481 return false; 482 } 483 } 484 485 return true; 486 } 487 488 size_t ShaderValidatorResults::SizeOfIncludingThis( 489 const MallocSizeOf fnSizeOf) const { 490 auto ret = fnSizeOf(this); 491 ret += mInfoLog.size(); 492 ret += mObjectCode.size(); 493 494 for (const auto& cur : mAttributes) { 495 ret += fnSizeOf(&cur); 496 } 497 for (const auto& cur : mInterfaceBlocks) { 498 ret += fnSizeOf(&cur); 499 } 500 for (const auto& cur : mOutputVariables) { 501 ret += fnSizeOf(&cur); 502 } 503 for (const auto& cur : mUniforms) { 504 ret += fnSizeOf(&cur); 505 } 506 for (const auto& cur : mVaryings) { 507 ret += fnSizeOf(&cur); 508 } 509 510 return ret; 511 } 512 513 } // namespace webgl 514 } // namespace mozilla