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/. */
6 #include "WebGLShaderValidator.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"
16 #include "WebGLContext.h"
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.
25 MurmurHash3_x86_128(name
, len
, 0, hash
);
29 static ShCompileOptions
ChooseValidatorCompileOptions(
30 const ShBuiltInResources
& resources
, const mozilla::gl::GLContext
* gl
) {
31 ShCompileOptions options
= {};
32 options
.variables
= true;
33 options
.enforcePackingRestrictions
= true;
34 options
.objectCode
= true;
35 options
.initGLPosition
= true;
36 options
.initializeUninitializedLocals
= true;
37 options
.initOutputVariables
= true;
40 options
.removeInvariantAndCentroidForESSL3
= true;
42 // We want to do this everywhere, but to do this on Mac, we need
43 // to do it only on Mac OSX > 10.6 as this causes the shader
44 // compiler in 10.6 to crash
45 options
.clampIndirectArrayBounds
= true;
48 if (gl
->WorkAroundDriverBugs()) {
50 // Work around https://bugs.webkit.org/show_bug.cgi?id=124684,
51 // https://chromium.googlesource.com/angle/angle/+/5e70cf9d0b1bb
52 options
.unfoldShortCircuit
= true;
54 // Work around that Mac drivers handle struct scopes incorrectly.
55 options
.regenerateStructNames
= true;
56 options
.initOutputVariables
= true;
57 options
.initGLPointSize
= true;
59 if (gl
->Vendor() == gl::GLVendor::Intel
) {
60 // Work around that Intel drivers on Mac OSX handle for-loop
62 options
.addAndTrueToLoopCondition
= true;
64 options
.rewriteTexelFetchOffsetToTexelFetch
= true;
68 if (!gl
->IsANGLE() && gl
->Vendor() == gl::GLVendor::Intel
) {
69 // Failures on at least Windows+Intel+OGL on:
70 // conformance/glsl/constructors/glsl-construct-mat2.html
71 options
.scalarizeVecAndMatConstructorArgs
= true;
77 if (resources
.MaxExpressionComplexity
> 0) {
78 options
.limitExpressionComplexity
= true;
80 if (resources
.MaxCallStackDepth
> 0) {
81 options
.limitCallStackDepth
= true;
89 ////////////////////////////////////////
91 static ShShaderOutput
ShaderOutput(gl::GLContext
* gl
) {
93 return SH_ESSL_OUTPUT
;
95 uint32_t version
= gl
->ShadingLanguageVersion();
98 return SH_GLSL_COMPATIBILITY_OUTPUT
;
100 return SH_GLSL_COMPATIBILITY_OUTPUT
;
102 return SH_GLSL_130_OUTPUT
;
104 return SH_GLSL_140_OUTPUT
;
106 return SH_GLSL_150_CORE_OUTPUT
;
108 return SH_GLSL_330_CORE_OUTPUT
;
110 return SH_GLSL_400_CORE_OUTPUT
;
112 return SH_GLSL_410_CORE_OUTPUT
;
114 return SH_GLSL_420_CORE_OUTPUT
;
116 return SH_GLSL_430_CORE_OUTPUT
;
118 return SH_GLSL_440_CORE_OUTPUT
;
120 if (version
>= 450) {
121 // "OpenGL 4.6 is also guaranteed to support all previous versions of
122 // the OpenGL Shading Language back to version 1.10."
123 return SH_GLSL_450_CORE_OUTPUT
;
125 gfxCriticalNote
<< "Unexpected GLSL version: " << version
;
128 return SH_GLSL_COMPATIBILITY_OUTPUT
;
131 std::unique_ptr
<webgl::ShaderValidator
> WebGLContext::CreateShaderValidator(
132 GLenum shaderType
) const {
133 const auto spec
= (IsWebGL2() ? SH_WEBGL2_SPEC
: SH_WEBGL_SPEC
);
134 const auto outputLanguage
= ShaderOutput(gl
);
136 ShBuiltInResources resources
;
137 sh::InitBuiltInResources(&resources
);
139 resources
.HashFunction
= webgl::IdentifierHashFunc
;
141 const auto& limits
= Limits();
143 resources
.MaxVertexAttribs
= limits
.maxVertexAttribs
;
144 resources
.MaxVertexUniformVectors
= mGLMaxVertexUniformVectors
;
145 resources
.MaxVertexTextureImageUnits
= mGLMaxVertexTextureImageUnits
;
146 resources
.MaxCombinedTextureImageUnits
= limits
.maxTexUnits
;
147 resources
.MaxTextureImageUnits
= mGLMaxFragmentTextureImageUnits
;
148 resources
.MaxFragmentUniformVectors
= mGLMaxFragmentUniformVectors
;
150 resources
.MaxVertexOutputVectors
= mGLMaxVertexOutputVectors
;
151 resources
.MaxFragmentInputVectors
= mGLMaxFragmentInputVectors
;
152 resources
.MaxVaryingVectors
= mGLMaxFragmentInputVectors
;
155 resources
.MinProgramTexelOffset
= mGLMinProgramTexelOffset
;
156 resources
.MaxProgramTexelOffset
= mGLMaxProgramTexelOffset
;
159 resources
.MaxDrawBuffers
= MaxValidDrawBuffers();
161 if (IsExtensionEnabled(WebGLExtensionID::EXT_frag_depth
))
162 resources
.EXT_frag_depth
= 1;
164 if (IsExtensionEnabled(WebGLExtensionID::OES_standard_derivatives
))
165 resources
.OES_standard_derivatives
= 1;
167 if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers
))
168 resources
.EXT_draw_buffers
= 1;
170 if (IsExtensionEnabled(WebGLExtensionID::EXT_shader_texture_lod
))
171 resources
.EXT_shader_texture_lod
= 1;
173 if (IsExtensionEnabled(WebGLExtensionID::OVR_multiview2
)) {
174 resources
.OVR_multiview
= 1;
175 resources
.OVR_multiview2
= 1;
176 resources
.MaxViewsOVR
= limits
.maxMultiviewLayers
;
179 // Tell ANGLE to allow highp in frag shaders. (unless disabled)
180 // If underlying GLES doesn't have highp in frag shaders, it should complain
182 resources
.FragmentPrecisionHigh
= mDisableFragHighP
? 0 : 1;
184 if (gl
->WorkAroundDriverBugs()) {
186 if (gl
->Vendor() == gl::GLVendor::NVIDIA
) {
187 // Work around bug 890432
188 resources
.MaxExpressionComplexity
= 1000;
195 resources
.MaxVariableSizeInBytes
= [&]() -> size_t {
196 const auto kibytes
= StaticPrefs::webgl_glsl_max_var_size_in_kibytes();
198 return static_cast<size_t>(kibytes
) * 1024;
201 return resources
.MaxVariableSizeInBytes
;
204 resources
.MaxPrivateVariableSizeInBytes
= [&]() -> size_t {
205 const auto bytes
= StaticPrefs::webgl_glsl_max_private_var_size_in_bytes();
207 return static_cast<size_t>(bytes
);
211 return 128 * 1024; // 8k vec4s
214 return resources
.MaxPrivateVariableSizeInBytes
;
219 const auto compileOptions
=
220 webgl::ChooseValidatorCompileOptions(resources
, gl
);
221 auto ret
= webgl::ShaderValidator::Create(shaderType
, spec
, outputLanguage
,
222 resources
, compileOptions
);
223 if (!ret
) return ret
;
225 ret
->mIfNeeded_webgl_gl_VertexID_Offset
|=
226 mBug_DrawArraysInstancedUserAttribFetchAffectedByFirst
;
231 ////////////////////////////////////////
236 std::unique_ptr
<ShaderValidator
> ShaderValidator::Create(
237 GLenum shaderType
, ShShaderSpec spec
, ShShaderOutput outputLanguage
,
238 const ShBuiltInResources
& resources
, ShCompileOptions compileOptions
) {
240 sh::ConstructCompiler(shaderType
, spec
, outputLanguage
, &resources
);
241 MOZ_RELEASE_ASSERT(handle
);
242 if (!handle
) return nullptr;
244 return std::unique_ptr
<ShaderValidator
>(
245 new ShaderValidator(handle
, compileOptions
, resources
.MaxVaryingVectors
));
248 ShaderValidator::~ShaderValidator() { sh::Destruct(mHandle
); }
250 inline bool StartsWith(const std::string_view
& str
,
251 const std::string_view
& part
) {
252 return str
.find(part
) == 0;
255 inline std::vector
<std::string_view
> Split(std::string_view src
,
256 const std::string_view
& delim
,
257 const size_t maxSplits
= -1) {
258 std::vector
<std::string_view
> ret
;
259 for (const auto i
: IntegerRange(maxSplits
)) {
261 const auto end
= src
.find(delim
);
262 if (end
== size_t(-1)) {
265 ret
.push_back(src
.substr(0, end
));
266 src
= src
.substr(end
+ delim
.size());
272 std::unique_ptr
<const ShaderValidatorResults
>
273 ShaderValidator::ValidateAndTranslate(const char* const source
) const {
274 auto ret
= std::make_unique
<ShaderValidatorResults
>();
276 const std::array
<const char*, 1> parts
= {source
};
278 sh::Compile(mHandle
, parts
.data(), parts
.size(), mCompileOptions
);
280 ret
->mInfoLog
= sh::GetInfoLog(mHandle
);
283 ret
->mObjectCode
= sh::GetObjectCode(mHandle
);
284 ret
->mShaderVersion
= sh::GetShaderVersion(mHandle
);
285 ret
->mVertexShaderNumViews
= sh::GetVertexShaderNumViews(mHandle
);
287 ret
->mAttributes
= *sh::GetAttributes(mHandle
);
288 ret
->mInterfaceBlocks
= *sh::GetInterfaceBlocks(mHandle
);
289 ret
->mOutputVariables
= *sh::GetOutputVariables(mHandle
);
290 ret
->mUniforms
= *sh::GetUniforms(mHandle
);
291 ret
->mVaryings
= *sh::GetVaryings(mHandle
);
293 ret
->mMaxVaryingVectors
= mMaxVaryingVectors
;
295 const auto& nameMap
= *sh::GetNameHashingMap(mHandle
);
296 for (const auto& pair
: nameMap
) {
297 ret
->mNameMap
.insert(pair
);
301 // Custom translation steps
302 auto* const translatedSource
= &ret
->mObjectCode
;
304 // gl_VertexID -> webgl_gl_VertexID
305 // gl_InstanceID -> webgl_gl_InstanceID
308 std::string_view body
= *translatedSource
;
309 if (StartsWith(body
, "#version")) {
310 const auto parts
= Split(body
, "\n", 1);
311 header
= parts
.at(0);
316 for (const auto& attrib
: ret
->mAttributes
) {
317 if (mIfNeeded_webgl_gl_VertexID_Offset
&& attrib
.name
== "gl_VertexID" &&
319 header
+= "uniform int webgl_gl_VertexID_Offset;\n";
321 "#define gl_VertexID (gl_VertexID + webgl_gl_VertexID_Offset)\n";
322 ret
->mNeeds_webgl_gl_VertexID_Offset
= true;
327 auto combined
= header
;
329 *translatedSource
= combined
;
333 sh::ClearResults(mHandle
);
337 bool ShaderValidatorResults::CanLinkTo(const ShaderValidatorResults
& vert
,
338 nsCString
* const out_log
) const {
340 MOZ_ASSERT(vert
.mValid
);
342 if (vert
.mShaderVersion
!= mShaderVersion
) {
343 nsPrintfCString
error(
344 "Vertex shader version %d does not match"
345 " fragment shader version %d.",
346 vert
.mShaderVersion
, mShaderVersion
);
351 for (const auto& itrFrag
: mUniforms
) {
352 for (const auto& itrVert
: vert
.mUniforms
) {
353 if (itrVert
.name
!= itrFrag
.name
) continue;
355 if (!itrVert
.isSameUniformAtLinkTime(itrFrag
)) {
356 nsPrintfCString
error(
357 "Uniform `%s` is not linkable between"
358 " attached shaders.",
359 itrFrag
.name
.c_str());
368 for (const auto& fragVar
: mInterfaceBlocks
) {
369 for (const auto& vertVar
: vert
.mInterfaceBlocks
) {
370 if (vertVar
.name
!= fragVar
.name
) continue;
372 if (!vertVar
.isSameInterfaceBlockAtLinkTime(fragVar
)) {
373 nsPrintfCString
error(
374 "Interface block `%s` is not linkable between"
375 " attached shaders.",
376 fragVar
.name
.c_str());
386 std::vector
<sh::ShaderVariable
> staticUseVaryingList
;
388 for (const auto& fragVarying
: mVaryings
) {
389 static const char prefix
[] = "gl_";
390 if (StartsWith(fragVarying
.name
, prefix
)) {
391 if (fragVarying
.staticUse
) {
392 staticUseVaryingList
.push_back(fragVarying
);
397 bool definedInVertShader
= false;
398 bool staticVertUse
= false;
400 for (const auto& vertVarying
: vert
.mVaryings
) {
401 if (vertVarying
.name
!= fragVarying
.name
) continue;
403 if (!vertVarying
.isSameVaryingAtLinkTime(fragVarying
, mShaderVersion
)) {
404 nsPrintfCString
error(
405 "Varying `%s`is not linkable between"
406 " attached shaders.",
407 fragVarying
.name
.c_str());
412 definedInVertShader
= true;
413 staticVertUse
= vertVarying
.staticUse
;
417 if (!definedInVertShader
&& fragVarying
.staticUse
) {
418 nsPrintfCString
error(
419 "Varying `%s` has static-use in the frag"
420 " shader, but is undeclared in the vert"
422 fragVarying
.name
.c_str());
427 if (staticVertUse
&& fragVarying
.staticUse
) {
428 staticUseVaryingList
.push_back(fragVarying
);
432 if (!sh::CheckVariablesWithinPackingLimits(mMaxVaryingVectors
,
433 staticUseVaryingList
)) {
435 "Statically used varyings do not fit within packing limits. (see"
436 " GLSL ES Specification 1.0.17, p111)";
441 if (mShaderVersion
== 100) {
442 // Enforce ESSL1 invariant linking rules.
443 bool isInvariant_Position
= false;
444 bool isInvariant_PointSize
= false;
445 bool isInvariant_FragCoord
= false;
446 bool isInvariant_PointCoord
= false;
448 for (const auto& varying
: vert
.mVaryings
) {
449 if (varying
.name
== "gl_Position") {
450 isInvariant_Position
= varying
.isInvariant
;
451 } else if (varying
.name
== "gl_PointSize") {
452 isInvariant_PointSize
= varying
.isInvariant
;
456 for (const auto& varying
: mVaryings
) {
457 if (varying
.name
== "gl_FragCoord") {
458 isInvariant_FragCoord
= varying
.isInvariant
;
459 } else if (varying
.name
== "gl_PointCoord") {
460 isInvariant_PointCoord
= varying
.isInvariant
;
466 const auto fnCanBuiltInsLink
= [](bool vertIsInvariant
,
467 bool fragIsInvariant
) {
468 if (vertIsInvariant
) return true;
470 return !fragIsInvariant
;
473 if (!fnCanBuiltInsLink(isInvariant_Position
, isInvariant_FragCoord
)) {
475 "gl_Position must be invariant if gl_FragCoord is. (see GLSL ES"
476 " Specification 1.0.17, p39)";
480 if (!fnCanBuiltInsLink(isInvariant_PointSize
, isInvariant_PointCoord
)) {
482 "gl_PointSize must be invariant if gl_PointCoord is. (see GLSL ES"
483 " Specification 1.0.17, p39)";
491 size_t ShaderValidatorResults::SizeOfIncludingThis(
492 const MallocSizeOf fnSizeOf
) const {
493 auto ret
= fnSizeOf(this);
494 ret
+= mInfoLog
.size();
495 ret
+= mObjectCode
.size();
497 for (const auto& cur
: mAttributes
) {
498 ret
+= fnSizeOf(&cur
);
500 for (const auto& cur
: mInterfaceBlocks
) {
501 ret
+= fnSizeOf(&cur
);
503 for (const auto& cur
: mOutputVariables
) {
504 ret
+= fnSizeOf(&cur
);
506 for (const auto& cur
: mUniforms
) {
507 ret
+= fnSizeOf(&cur
);
509 for (const auto& cur
: mVaryings
) {
510 ret
+= fnSizeOf(&cur
);
517 } // namespace mozilla