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 "GLContext.h"
9 #include "mozilla/gfx/Logging.h"
10 #include "mozilla/Preferences.h"
11 #include "mozilla/StaticPrefs_webgl.h"
12 #include "MurmurHash3.h"
13 #include "nsPrintfCString.h"
14 #include <string>
15 #include <vector>
16 #include "WebGLContext.h"
17 
18 namespace mozilla {
19 namespace webgl {
20 
IdentifierHashFunc(const char * name,size_t len)21 uint64_t IdentifierHashFunc(const char* name, size_t len) {
22   // NB: we use the x86 function everywhere, even though it's suboptimal perf
23   // on x64.  They return different results; not sure if that's a requirement.
24   uint64_t hash[2];
25   MurmurHash3_x86_128(name, len, 0, hash);
26   return hash[0];
27 }
28 
ChooseValidatorCompileOptions(const ShBuiltInResources & resources,const mozilla::gl::GLContext * gl)29 static ShCompileOptions ChooseValidatorCompileOptions(
30     const ShBuiltInResources& resources, const mozilla::gl::GLContext* gl) {
31   ShCompileOptions options = SH_VARIABLES | SH_ENFORCE_PACKING_RESTRICTIONS |
32                              SH_OBJECT_CODE | SH_INIT_GL_POSITION |
33                              SH_INITIALIZE_UNINITIALIZED_LOCALS |
34                              SH_INIT_OUTPUT_VARIABLES;
35 
36 #ifdef XP_MACOSX
37   options |= SH_REMOVE_INVARIANT_AND_CENTROID_FOR_ESSL3;
38 #else
39   // We want to do this everywhere, but to do this on Mac, we need
40   // to do it only on Mac OSX > 10.6 as this causes the shader
41   // compiler in 10.6 to crash
42   options |= SH_CLAMP_INDIRECT_ARRAY_BOUNDS;
43 #endif
44 
45   if (gl->WorkAroundDriverBugs()) {
46 #ifdef XP_MACOSX
47     // Work around https://bugs.webkit.org/show_bug.cgi?id=124684,
48     // https://chromium.googlesource.com/angle/angle/+/5e70cf9d0b1bb
49     options |= SH_UNFOLD_SHORT_CIRCUIT;
50 
51     // Work around that Mac drivers handle struct scopes incorrectly.
52     options |= SH_REGENERATE_STRUCT_NAMES;
53     options |= SH_INIT_OUTPUT_VARIABLES;
54 
55     if (gl->Vendor() == gl::GLVendor::Intel) {
56       // Work around that Intel drivers on Mac OSX handle for-loop incorrectly.
57       options |= SH_ADD_AND_TRUE_TO_LOOP_CONDITION;
58 
59       options |= SH_REWRITE_TEXELFETCHOFFSET_TO_TEXELFETCH;
60     }
61 #endif
62 
63     if (!gl->IsANGLE() && gl->Vendor() == gl::GLVendor::Intel) {
64       // Failures on at least Windows+Intel+OGL on:
65       // conformance/glsl/constructors/glsl-construct-mat2.html
66       options |= SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS;
67     }
68   }
69 
70   if (StaticPrefs::webgl_all_angle_options()) {
71     options = -1;
72 
73     options ^= SH_INTERMEDIATE_TREE;
74     options ^= SH_LINE_DIRECTIVES;
75     options ^= SH_SOURCE_PATH;
76 
77     options ^= SH_LIMIT_EXPRESSION_COMPLEXITY;
78     options ^= SH_LIMIT_CALL_STACK_DEPTH;
79 
80     options ^= SH_EXPAND_SELECT_HLSL_INTEGER_POW_EXPRESSIONS;
81     options ^= SH_HLSL_GET_DIMENSIONS_IGNORES_BASE_LEVEL;
82 
83     options ^= SH_REMOVE_INVARIANT_AND_CENTROID_FOR_ESSL3;
84   }
85 
86   if (resources.MaxExpressionComplexity > 0) {
87     options |= SH_LIMIT_EXPRESSION_COMPLEXITY;
88   }
89   if (resources.MaxCallStackDepth > 0) {
90     options |= SH_LIMIT_CALL_STACK_DEPTH;
91   }
92 
93   return options;
94 }
95 
96 }  // namespace webgl
97 
98 ////////////////////////////////////////
99 
ShaderOutput(gl::GLContext * gl)100 static ShShaderOutput ShaderOutput(gl::GLContext* gl) {
101   if (gl->IsGLES()) {
102     return SH_ESSL_OUTPUT;
103   }
104   uint32_t version = gl->ShadingLanguageVersion();
105   switch (version) {
106     case 100:
107       return SH_GLSL_COMPATIBILITY_OUTPUT;
108     case 120:
109       return SH_GLSL_COMPATIBILITY_OUTPUT;
110     case 130:
111       return SH_GLSL_130_OUTPUT;
112     case 140:
113       return SH_GLSL_140_OUTPUT;
114     case 150:
115       return SH_GLSL_150_CORE_OUTPUT;
116     case 330:
117       return SH_GLSL_330_CORE_OUTPUT;
118     case 400:
119       return SH_GLSL_400_CORE_OUTPUT;
120     case 410:
121       return SH_GLSL_410_CORE_OUTPUT;
122     case 420:
123       return SH_GLSL_420_CORE_OUTPUT;
124     case 430:
125       return SH_GLSL_430_CORE_OUTPUT;
126     case 440:
127       return SH_GLSL_440_CORE_OUTPUT;
128     default:
129       if (version >= 450) {
130         // "OpenGL 4.6 is also guaranteed to support all previous versions of
131         // the OpenGL Shading Language back to version 1.10."
132         return SH_GLSL_450_CORE_OUTPUT;
133       }
134       gfxCriticalNote << "Unexpected GLSL version: " << version;
135   }
136 
137   return SH_GLSL_COMPATIBILITY_OUTPUT;
138 }
139 
CreateShaderValidator(GLenum shaderType) const140 std::unique_ptr<webgl::ShaderValidator> WebGLContext::CreateShaderValidator(
141     GLenum shaderType) const {
142   const auto spec = (IsWebGL2() ? SH_WEBGL2_SPEC : SH_WEBGL_SPEC);
143   const auto outputLanguage = ShaderOutput(gl);
144 
145   ShBuiltInResources resources;
146   memset(&resources, 0, sizeof(resources));
147   sh::InitBuiltInResources(&resources);
148 
149   resources.HashFunction = webgl::IdentifierHashFunc;
150 
151   const auto& limits = Limits();
152 
153   resources.MaxVertexAttribs = limits.maxVertexAttribs;
154   resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors;
155   resources.MaxVertexTextureImageUnits = mGLMaxVertexTextureImageUnits;
156   resources.MaxCombinedTextureImageUnits = limits.maxTexUnits;
157   resources.MaxTextureImageUnits = mGLMaxFragmentTextureImageUnits;
158   resources.MaxFragmentUniformVectors = mGLMaxFragmentUniformVectors;
159 
160   resources.MaxVertexOutputVectors = mGLMaxVertexOutputVectors;
161   resources.MaxFragmentInputVectors = mGLMaxFragmentInputVectors;
162   resources.MaxVaryingVectors = mGLMaxFragmentInputVectors;
163 
164   if (IsWebGL2()) {
165     resources.MinProgramTexelOffset = mGLMinProgramTexelOffset;
166     resources.MaxProgramTexelOffset = mGLMaxProgramTexelOffset;
167   }
168 
169   resources.MaxDrawBuffers = MaxValidDrawBuffers();
170 
171   if (IsExtensionEnabled(WebGLExtensionID::EXT_frag_depth))
172     resources.EXT_frag_depth = 1;
173 
174   if (IsExtensionEnabled(WebGLExtensionID::OES_standard_derivatives))
175     resources.OES_standard_derivatives = 1;
176 
177   if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers))
178     resources.EXT_draw_buffers = 1;
179 
180   if (IsExtensionEnabled(WebGLExtensionID::EXT_shader_texture_lod))
181     resources.EXT_shader_texture_lod = 1;
182 
183   if (IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) {
184     resources.OVR_multiview = 1;
185     resources.OVR_multiview2 = 1;
186     resources.MaxViewsOVR = limits.maxMultiviewLayers;
187   }
188 
189   // Tell ANGLE to allow highp in frag shaders. (unless disabled)
190   // If underlying GLES doesn't have highp in frag shaders, it should complain
191   // anyways.
192   resources.FragmentPrecisionHigh = mDisableFragHighP ? 0 : 1;
193 
194   if (gl->WorkAroundDriverBugs()) {
195 #ifdef XP_MACOSX
196     if (gl->Vendor() == gl::GLVendor::NVIDIA) {
197       // Work around bug 890432
198       resources.MaxExpressionComplexity = 1000;
199     }
200 #endif
201   }
202 
203   const auto compileOptions =
204       webgl::ChooseValidatorCompileOptions(resources, gl);
205   return webgl::ShaderValidator::Create(shaderType, spec, outputLanguage,
206                                         resources, compileOptions);
207 }
208 
209 ////////////////////////////////////////
210 
211 namespace webgl {
212 
213 /*static*/
Create(GLenum shaderType,ShShaderSpec spec,ShShaderOutput outputLanguage,const ShBuiltInResources & resources,ShCompileOptions compileOptions)214 std::unique_ptr<ShaderValidator> ShaderValidator::Create(
215     GLenum shaderType, ShShaderSpec spec, ShShaderOutput outputLanguage,
216     const ShBuiltInResources& resources, ShCompileOptions compileOptions) {
217   ShHandle handle =
218       sh::ConstructCompiler(shaderType, spec, outputLanguage, &resources);
219   MOZ_RELEASE_ASSERT(handle);
220   if (!handle) return nullptr;
221 
222   return std::unique_ptr<ShaderValidator>(
223       new ShaderValidator(handle, compileOptions, resources.MaxVaryingVectors));
224 }
225 
~ShaderValidator()226 ShaderValidator::~ShaderValidator() { sh::Destruct(mHandle); }
227 
228 std::unique_ptr<const ShaderValidatorResults>
ValidateAndTranslate(const char * const source) const229 ShaderValidator::ValidateAndTranslate(const char* const source) const {
230   auto ret = std::make_unique<ShaderValidatorResults>();
231 
232   const std::array<const char*, 1> parts = {source};
233   ret->mValid =
234       sh::Compile(mHandle, parts.data(), parts.size(), mCompileOptions);
235 
236   ret->mInfoLog = sh::GetInfoLog(mHandle);
237 
238   if (ret->mValid) {
239     ret->mObjectCode = sh::GetObjectCode(mHandle);
240     ret->mShaderVersion = sh::GetShaderVersion(mHandle);
241     ret->mVertexShaderNumViews = sh::GetVertexShaderNumViews(mHandle);
242 
243     ret->mAttributes = *sh::GetAttributes(mHandle);
244     ret->mInterfaceBlocks = *sh::GetInterfaceBlocks(mHandle);
245     ret->mOutputVariables = *sh::GetOutputVariables(mHandle);
246     ret->mUniforms = *sh::GetUniforms(mHandle);
247     ret->mVaryings = *sh::GetVaryings(mHandle);
248 
249     ret->mMaxVaryingVectors = mMaxVaryingVectors;
250 
251     const auto& nameMap = *sh::GetNameHashingMap(mHandle);
252     for (const auto& pair : nameMap) {
253       ret->mNameMap.insert(pair);
254     }
255   }
256 
257   sh::ClearResults(mHandle);
258   return ret;
259 }
260 
261 template <size_t N>
StartsWith(const std::string & haystack,const char (& needle)[N])262 static bool StartsWith(const std::string& haystack, const char (&needle)[N]) {
263   return haystack.compare(0, N - 1, needle) == 0;
264 }
265 
CanLinkTo(const ShaderValidatorResults & vert,nsCString * const out_log) const266 bool ShaderValidatorResults::CanLinkTo(const ShaderValidatorResults& vert,
267                                        nsCString* const out_log) const {
268   MOZ_ASSERT(mValid);
269   MOZ_ASSERT(vert.mValid);
270 
271   if (vert.mShaderVersion != mShaderVersion) {
272     nsPrintfCString error(
273         "Vertex shader version %d does not match"
274         " fragment shader version %d.",
275         vert.mShaderVersion, mShaderVersion);
276     *out_log = error;
277     return false;
278   }
279 
280   for (const auto& itrFrag : mUniforms) {
281     for (const auto& itrVert : vert.mUniforms) {
282       if (itrVert.name != itrFrag.name) continue;
283 
284       if (!itrVert.isSameUniformAtLinkTime(itrFrag)) {
285         nsPrintfCString error(
286             "Uniform `%s` is not linkable between"
287             " attached shaders.",
288             itrFrag.name.c_str());
289         *out_log = error;
290         return false;
291       }
292 
293       break;
294     }
295   }
296 
297   for (const auto& fragVar : mInterfaceBlocks) {
298     for (const auto& vertVar : vert.mInterfaceBlocks) {
299       if (vertVar.name != fragVar.name) continue;
300 
301       if (!vertVar.isSameInterfaceBlockAtLinkTime(fragVar)) {
302         nsPrintfCString error(
303             "Interface block `%s` is not linkable between"
304             " attached shaders.",
305             fragVar.name.c_str());
306         *out_log = error;
307         return false;
308       }
309 
310       break;
311     }
312   }
313 
314   {
315     std::vector<sh::ShaderVariable> staticUseVaryingList;
316 
317     for (const auto& fragVarying : mVaryings) {
318       static const char prefix[] = "gl_";
319       if (StartsWith(fragVarying.name, prefix)) {
320         if (fragVarying.staticUse) {
321           staticUseVaryingList.push_back(fragVarying);
322         }
323         continue;
324       }
325 
326       bool definedInVertShader = false;
327       bool staticVertUse = false;
328 
329       for (const auto& vertVarying : vert.mVaryings) {
330         if (vertVarying.name != fragVarying.name) continue;
331 
332         if (!vertVarying.isSameVaryingAtLinkTime(fragVarying, mShaderVersion)) {
333           nsPrintfCString error(
334               "Varying `%s`is not linkable between"
335               " attached shaders.",
336               fragVarying.name.c_str());
337           *out_log = error;
338           return false;
339         }
340 
341         definedInVertShader = true;
342         staticVertUse = vertVarying.staticUse;
343         break;
344       }
345 
346       if (!definedInVertShader && fragVarying.staticUse) {
347         nsPrintfCString error(
348             "Varying `%s` has static-use in the frag"
349             " shader, but is undeclared in the vert"
350             " shader.",
351             fragVarying.name.c_str());
352         *out_log = error;
353         return false;
354       }
355 
356       if (staticVertUse && fragVarying.staticUse) {
357         staticUseVaryingList.push_back(fragVarying);
358       }
359     }
360 
361     if (!sh::CheckVariablesWithinPackingLimits(mMaxVaryingVectors,
362                                                staticUseVaryingList)) {
363       *out_log =
364           "Statically used varyings do not fit within packing limits. (see"
365           " GLSL ES Specification 1.0.17, p111)";
366       return false;
367     }
368   }
369 
370   if (mShaderVersion == 100) {
371     // Enforce ESSL1 invariant linking rules.
372     bool isInvariant_Position = false;
373     bool isInvariant_PointSize = false;
374     bool isInvariant_FragCoord = false;
375     bool isInvariant_PointCoord = false;
376 
377     for (const auto& varying : vert.mVaryings) {
378       if (varying.name == "gl_Position") {
379         isInvariant_Position = varying.isInvariant;
380       } else if (varying.name == "gl_PointSize") {
381         isInvariant_PointSize = varying.isInvariant;
382       }
383     }
384 
385     for (const auto& varying : mVaryings) {
386       if (varying.name == "gl_FragCoord") {
387         isInvariant_FragCoord = varying.isInvariant;
388       } else if (varying.name == "gl_PointCoord") {
389         isInvariant_PointCoord = varying.isInvariant;
390       }
391     }
392 
393     ////
394 
395     const auto fnCanBuiltInsLink = [](bool vertIsInvariant,
396                                       bool fragIsInvariant) {
397       if (vertIsInvariant) return true;
398 
399       return !fragIsInvariant;
400     };
401 
402     if (!fnCanBuiltInsLink(isInvariant_Position, isInvariant_FragCoord)) {
403       *out_log =
404           "gl_Position must be invariant if gl_FragCoord is. (see GLSL ES"
405           " Specification 1.0.17, p39)";
406       return false;
407     }
408 
409     if (!fnCanBuiltInsLink(isInvariant_PointSize, isInvariant_PointCoord)) {
410       *out_log =
411           "gl_PointSize must be invariant if gl_PointCoord is. (see GLSL ES"
412           " Specification 1.0.17, p39)";
413       return false;
414     }
415   }
416 
417   return true;
418 }
419 
SizeOfIncludingThis(const MallocSizeOf fnSizeOf) const420 size_t ShaderValidatorResults::SizeOfIncludingThis(
421     const MallocSizeOf fnSizeOf) const {
422   auto ret = fnSizeOf(this);
423   ret += mInfoLog.size();
424   ret += mObjectCode.size();
425 
426   for (const auto& cur : mAttributes) {
427     ret += fnSizeOf(&cur);
428   }
429   for (const auto& cur : mInterfaceBlocks) {
430     ret += fnSizeOf(&cur);
431   }
432   for (const auto& cur : mOutputVariables) {
433     ret += fnSizeOf(&cur);
434   }
435   for (const auto& cur : mUniforms) {
436     ret += fnSizeOf(&cur);
437   }
438   for (const auto& cur : mVaryings) {
439     ret += fnSizeOf(&cur);
440   }
441 
442   return ret;
443 }
444 
445 }  // namespace webgl
446 }  // namespace mozilla
447