Line data Source code
1 : /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 "angle/ShaderLang.h"
9 : #include "gfxPrefs.h"
10 : #include "GLContext.h"
11 : #include "mozilla/Preferences.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 :
21 : uint64_t
22 0 : IdentifierHashFunc(const char* name, size_t len)
23 : {
24 : // NB: we use the x86 function everywhere, even though it's suboptimal perf
25 : // on x64. They return different results; not sure if that's a requirement.
26 : uint64_t hash[2];
27 0 : MurmurHash3_x86_128(name, len, 0, hash);
28 0 : return hash[0];
29 : }
30 :
31 : static ShCompileOptions
32 0 : ChooseValidatorCompileOptions(const ShBuiltInResources& resources,
33 : const mozilla::gl::GLContext* gl)
34 : {
35 : ShCompileOptions options = SH_VARIABLES |
36 : SH_ENFORCE_PACKING_RESTRICTIONS |
37 : SH_OBJECT_CODE |
38 0 : SH_INIT_GL_POSITION;
39 :
40 : #ifndef XP_MACOSX
41 : // We want to do this everywhere, but to do this on Mac, we need
42 : // to do it only on Mac OSX > 10.6 as this causes the shader
43 : // compiler in 10.6 to crash
44 0 : options |= SH_CLAMP_INDIRECT_ARRAY_BOUNDS;
45 : #endif
46 :
47 0 : if (gl->WorkAroundDriverBugs()) {
48 : #ifdef XP_MACOSX
49 : // Work around https://bugs.webkit.org/show_bug.cgi?id=124684,
50 : // https://chromium.googlesource.com/angle/angle/+/5e70cf9d0b1bb
51 : options |= SH_UNFOLD_SHORT_CIRCUIT;
52 :
53 : // Work around that Mac drivers handle struct scopes incorrectly.
54 : options |= SH_REGENERATE_STRUCT_NAMES;
55 : options |= SH_INIT_OUTPUT_VARIABLES;
56 :
57 : // Work around that Intel drivers on Mac OSX handle for-loop incorrectly.
58 : if (gl->Vendor() == gl::GLVendor::Intel) {
59 : options |= SH_ADD_AND_TRUE_TO_LOOP_CONDITION;
60 : }
61 : #endif
62 :
63 0 : 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 0 : options |= SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS;
67 : }
68 : }
69 :
70 0 : if (gfxPrefs::WebGLAllANGLEOptions()) {
71 0 : options = -1;
72 :
73 0 : options ^= SH_INTERMEDIATE_TREE;
74 0 : options ^= SH_LINE_DIRECTIVES;
75 0 : options ^= SH_SOURCE_PATH;
76 :
77 0 : options ^= SH_LIMIT_EXPRESSION_COMPLEXITY;
78 0 : options ^= SH_LIMIT_CALL_STACK_DEPTH;
79 :
80 0 : options ^= SH_EXPAND_SELECT_HLSL_INTEGER_POW_EXPRESSIONS;
81 0 : options ^= SH_HLSL_GET_DIMENSIONS_IGNORES_BASE_LEVEL;
82 :
83 0 : options ^= SH_DONT_REMOVE_INVARIANT_FOR_FRAGMENT_INPUT;
84 0 : options ^= SH_REMOVE_INVARIANT_AND_CENTROID_FOR_ESSL3;
85 : }
86 :
87 0 : if (resources.MaxExpressionComplexity > 0) {
88 0 : options |= SH_LIMIT_EXPRESSION_COMPLEXITY;
89 : }
90 0 : if (resources.MaxCallStackDepth > 0) {
91 0 : options |= SH_LIMIT_CALL_STACK_DEPTH;
92 : }
93 :
94 0 : return options;
95 : }
96 :
97 : } // namespace webgl
98 :
99 : ////////////////////////////////////////
100 :
101 : static ShShaderOutput
102 0 : ShaderOutput(gl::GLContext* gl)
103 : {
104 0 : if (gl->IsGLES()) {
105 0 : return SH_ESSL_OUTPUT;
106 : } else {
107 0 : uint32_t version = gl->ShadingLanguageVersion();
108 0 : switch (version) {
109 0 : case 100: return SH_GLSL_COMPATIBILITY_OUTPUT;
110 0 : case 120: return SH_GLSL_COMPATIBILITY_OUTPUT;
111 0 : case 130: return SH_GLSL_130_OUTPUT;
112 0 : case 140: return SH_GLSL_140_OUTPUT;
113 0 : case 150: return SH_GLSL_150_CORE_OUTPUT;
114 0 : case 330: return SH_GLSL_330_CORE_OUTPUT;
115 0 : case 400: return SH_GLSL_400_CORE_OUTPUT;
116 0 : case 410: return SH_GLSL_410_CORE_OUTPUT;
117 0 : case 420: return SH_GLSL_420_CORE_OUTPUT;
118 0 : case 430: return SH_GLSL_430_CORE_OUTPUT;
119 0 : case 440: return SH_GLSL_440_CORE_OUTPUT;
120 0 : case 450: return SH_GLSL_450_CORE_OUTPUT;
121 : default:
122 0 : MOZ_CRASH("GFX: Unexpected GLSL version.");
123 : }
124 : }
125 :
126 : return SH_GLSL_COMPATIBILITY_OUTPUT;
127 : }
128 :
129 : webgl::ShaderValidator*
130 0 : WebGLContext::CreateShaderValidator(GLenum shaderType) const
131 : {
132 0 : if (mBypassShaderValidation)
133 0 : return nullptr;
134 :
135 0 : const auto spec = (IsWebGL2() ? SH_WEBGL2_SPEC : SH_WEBGL_SPEC);
136 0 : const auto outputLanguage = ShaderOutput(gl);
137 :
138 : ShBuiltInResources resources;
139 0 : memset(&resources, 0, sizeof(resources));
140 0 : ShInitBuiltInResources(&resources);
141 :
142 0 : resources.HashFunction = webgl::IdentifierHashFunc;
143 :
144 0 : resources.MaxVertexAttribs = mGLMaxVertexAttribs;
145 0 : resources.MaxVertexUniformVectors = mGLMaxVertexUniformVectors;
146 0 : resources.MaxVaryingVectors = mGLMaxVaryingVectors;
147 0 : resources.MaxVertexTextureImageUnits = mGLMaxVertexTextureImageUnits;
148 0 : resources.MaxCombinedTextureImageUnits = mGLMaxTextureUnits;
149 0 : resources.MaxTextureImageUnits = mGLMaxTextureImageUnits;
150 0 : resources.MaxFragmentUniformVectors = mGLMaxFragmentUniformVectors;
151 :
152 0 : const bool hasMRTs = (IsWebGL2() ||
153 0 : IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers));
154 0 : resources.MaxDrawBuffers = (hasMRTs ? mGLMaxDrawBuffers : 1);
155 :
156 0 : if (IsExtensionEnabled(WebGLExtensionID::EXT_frag_depth))
157 0 : resources.EXT_frag_depth = 1;
158 :
159 0 : if (IsExtensionEnabled(WebGLExtensionID::OES_standard_derivatives))
160 0 : resources.OES_standard_derivatives = 1;
161 :
162 0 : if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers))
163 0 : resources.EXT_draw_buffers = 1;
164 :
165 0 : if (IsExtensionEnabled(WebGLExtensionID::EXT_shader_texture_lod))
166 0 : resources.EXT_shader_texture_lod = 1;
167 :
168 : // Tell ANGLE to allow highp in frag shaders. (unless disabled)
169 : // If underlying GLES doesn't have highp in frag shaders, it should complain anyways.
170 0 : resources.FragmentPrecisionHigh = mDisableFragHighP ? 0 : 1;
171 :
172 0 : if (gl->WorkAroundDriverBugs()) {
173 : #ifdef XP_MACOSX
174 : if (gl->Vendor() == gl::GLVendor::NVIDIA) {
175 : // Work around bug 890432
176 : resources.MaxExpressionComplexity = 1000;
177 : }
178 : #endif
179 : }
180 :
181 0 : const auto compileOptions = webgl::ChooseValidatorCompileOptions(resources, gl);
182 : return webgl::ShaderValidator::Create(shaderType, spec, outputLanguage, resources,
183 0 : compileOptions);
184 : }
185 :
186 : ////////////////////////////////////////
187 :
188 : namespace webgl {
189 :
190 : /*static*/ ShaderValidator*
191 0 : ShaderValidator::Create(GLenum shaderType, ShShaderSpec spec,
192 : ShShaderOutput outputLanguage,
193 : const ShBuiltInResources& resources,
194 : ShCompileOptions compileOptions)
195 : {
196 0 : ShHandle handle = ShConstructCompiler(shaderType, spec, outputLanguage, &resources);
197 0 : if (!handle)
198 0 : return nullptr;
199 :
200 0 : return new ShaderValidator(handle, compileOptions, resources.MaxVaryingVectors);
201 : }
202 :
203 0 : ShaderValidator::~ShaderValidator()
204 : {
205 0 : ShDestruct(mHandle);
206 0 : }
207 :
208 : bool
209 0 : ShaderValidator::ValidateAndTranslate(const char* source)
210 : {
211 0 : MOZ_ASSERT(!mHasRun);
212 0 : mHasRun = true;
213 :
214 : const char* const parts[] = {
215 : source
216 0 : };
217 0 : return ShCompile(mHandle, parts, ArrayLength(parts), mCompileOptions);
218 : }
219 :
220 : void
221 0 : ShaderValidator::GetInfoLog(nsACString* out) const
222 : {
223 0 : MOZ_ASSERT(mHasRun);
224 :
225 0 : const std::string &log = ShGetInfoLog(mHandle);
226 0 : out->Assign(log.data(), log.length());
227 0 : }
228 :
229 : void
230 0 : ShaderValidator::GetOutput(nsACString* out) const
231 : {
232 0 : MOZ_ASSERT(mHasRun);
233 :
234 0 : const std::string &output = ShGetObjectCode(mHandle);
235 0 : out->Assign(output.data(), output.length());
236 0 : }
237 :
238 : template<size_t N>
239 : static bool
240 0 : StartsWith(const std::string& haystack, const char (&needle)[N])
241 : {
242 0 : return haystack.compare(0, N - 1, needle) == 0;
243 : }
244 :
245 : bool
246 0 : ShaderValidator::CanLinkTo(const ShaderValidator* prev, nsCString* const out_log) const
247 : {
248 0 : if (!prev) {
249 0 : nsPrintfCString error("Passed in NULL prev ShaderValidator.");
250 0 : *out_log = error;
251 0 : return false;
252 : }
253 :
254 0 : const auto shaderVersion = ShGetShaderVersion(mHandle);
255 0 : if (ShGetShaderVersion(prev->mHandle) != shaderVersion) {
256 : nsPrintfCString error("Vertex shader version %d does not match"
257 : " fragment shader version %d.",
258 0 : ShGetShaderVersion(prev->mHandle),
259 0 : ShGetShaderVersion(mHandle));
260 0 : *out_log = error;
261 0 : return false;
262 : }
263 :
264 : {
265 0 : const std::vector<sh::Uniform>* vertPtr = ShGetUniforms(prev->mHandle);
266 0 : const std::vector<sh::Uniform>* fragPtr = ShGetUniforms(mHandle);
267 0 : if (!vertPtr || !fragPtr) {
268 0 : nsPrintfCString error("Could not create uniform list.");
269 0 : *out_log = error;
270 0 : return false;
271 : }
272 :
273 0 : for (auto itrFrag = fragPtr->begin(); itrFrag != fragPtr->end(); ++itrFrag) {
274 0 : for (auto itrVert = vertPtr->begin(); itrVert != vertPtr->end(); ++itrVert) {
275 0 : if (itrVert->name != itrFrag->name)
276 0 : continue;
277 :
278 0 : if (!itrVert->isSameUniformAtLinkTime(*itrFrag)) {
279 : nsPrintfCString error("Uniform `%s` is not linkable between"
280 : " attached shaders.",
281 0 : itrFrag->name.c_str());
282 0 : *out_log = error;
283 0 : return false;
284 : }
285 :
286 0 : break;
287 : }
288 : }
289 : }
290 : {
291 0 : const auto vertVars = sh::GetInterfaceBlocks(prev->mHandle);
292 0 : const auto fragVars = sh::GetInterfaceBlocks(mHandle);
293 0 : if (!vertVars || !fragVars) {
294 0 : nsPrintfCString error("Could not create uniform block list.");
295 0 : *out_log = error;
296 0 : return false;
297 : }
298 :
299 0 : for (const auto& fragVar : *fragVars) {
300 0 : for (const auto& vertVar : *vertVars) {
301 0 : if (vertVar.name != fragVar.name)
302 0 : continue;
303 :
304 0 : if (!vertVar.isSameInterfaceBlockAtLinkTime(fragVar)) {
305 : nsPrintfCString error("Interface block `%s` is not linkable between"
306 : " attached shaders.",
307 0 : fragVar.name.c_str());
308 0 : *out_log = error;
309 0 : return false;
310 : }
311 :
312 0 : break;
313 : }
314 : }
315 : }
316 :
317 0 : const auto& vertVaryings = ShGetVaryings(prev->mHandle);
318 0 : const auto& fragVaryings = ShGetVaryings(mHandle);
319 0 : if (!vertVaryings || !fragVaryings) {
320 0 : nsPrintfCString error("Could not create varying list.");
321 0 : *out_log = error;
322 0 : return false;
323 : }
324 :
325 : {
326 0 : std::vector<sh::ShaderVariable> staticUseVaryingList;
327 :
328 0 : for (const auto& fragVarying : *fragVaryings) {
329 : static const char prefix[] = "gl_";
330 0 : if (StartsWith(fragVarying.name, prefix)) {
331 0 : if (fragVarying.staticUse) {
332 0 : staticUseVaryingList.push_back(fragVarying);
333 : }
334 0 : continue;
335 : }
336 :
337 0 : bool definedInVertShader = false;
338 0 : bool staticVertUse = false;
339 :
340 0 : for (const auto& vertVarying : *vertVaryings) {
341 0 : if (vertVarying.name != fragVarying.name)
342 0 : continue;
343 :
344 0 : if (!vertVarying.isSameVaryingAtLinkTime(fragVarying, shaderVersion)) {
345 : nsPrintfCString error("Varying `%s`is not linkable between"
346 : " attached shaders.",
347 0 : fragVarying.name.c_str());
348 0 : *out_log = error;
349 0 : return false;
350 : }
351 :
352 0 : definedInVertShader = true;
353 0 : staticVertUse = vertVarying.staticUse;
354 0 : break;
355 : }
356 :
357 0 : if (!definedInVertShader && fragVarying.staticUse) {
358 : nsPrintfCString error("Varying `%s` has static-use in the frag"
359 : " shader, but is undeclared in the vert"
360 0 : " shader.", fragVarying.name.c_str());
361 0 : *out_log = error;
362 0 : return false;
363 : }
364 :
365 0 : if (staticVertUse && fragVarying.staticUse) {
366 0 : staticUseVaryingList.push_back(fragVarying);
367 : }
368 : }
369 :
370 0 : if (!ShCheckVariablesWithinPackingLimits(mMaxVaryingVectors,
371 : staticUseVaryingList))
372 : {
373 : *out_log = "Statically used varyings do not fit within packing limits. (see"
374 0 : " GLSL ES Specification 1.0.17, p111)";
375 0 : return false;
376 : }
377 : }
378 :
379 0 : if (shaderVersion == 100) {
380 : // Enforce ESSL1 invariant linking rules.
381 0 : bool isInvariant_Position = false;
382 0 : bool isInvariant_PointSize = false;
383 0 : bool isInvariant_FragCoord = false;
384 0 : bool isInvariant_PointCoord = false;
385 :
386 0 : for (const auto& varying : *vertVaryings) {
387 0 : if (varying.name == "gl_Position") {
388 0 : isInvariant_Position = varying.isInvariant;
389 0 : } else if (varying.name == "gl_PointSize") {
390 0 : isInvariant_PointSize = varying.isInvariant;
391 : }
392 : }
393 :
394 0 : for (const auto& varying : *fragVaryings) {
395 0 : if (varying.name == "gl_FragCoord") {
396 0 : isInvariant_FragCoord = varying.isInvariant;
397 0 : } else if (varying.name == "gl_PointCoord") {
398 0 : isInvariant_PointCoord = varying.isInvariant;
399 : }
400 : }
401 :
402 : ////
403 :
404 0 : const auto fnCanBuiltInsLink = [](bool vertIsInvariant, bool fragIsInvariant) {
405 0 : if (vertIsInvariant)
406 0 : return true;
407 :
408 0 : return !fragIsInvariant;
409 : };
410 :
411 0 : if (!fnCanBuiltInsLink(isInvariant_Position, isInvariant_FragCoord)) {
412 : *out_log = "gl_Position must be invariant if gl_FragCoord is. (see GLSL ES"
413 0 : " Specification 1.0.17, p39)";
414 0 : return false;
415 : }
416 :
417 0 : if (!fnCanBuiltInsLink(isInvariant_PointSize, isInvariant_PointCoord)) {
418 : *out_log = "gl_PointSize must be invariant if gl_PointCoord is. (see GLSL ES"
419 0 : " Specification 1.0.17, p39)";
420 0 : return false;
421 : }
422 : }
423 :
424 0 : return true;
425 : }
426 :
427 : size_t
428 0 : ShaderValidator::CalcNumSamplerUniforms() const
429 : {
430 0 : size_t accum = 0;
431 :
432 0 : const std::vector<sh::Uniform>& uniforms = *ShGetUniforms(mHandle);
433 :
434 0 : for (auto itr = uniforms.begin(); itr != uniforms.end(); ++itr) {
435 0 : GLenum type = itr->type;
436 0 : if (type == LOCAL_GL_SAMPLER_2D ||
437 : type == LOCAL_GL_SAMPLER_CUBE)
438 : {
439 0 : accum += itr->arraySize;
440 : }
441 : }
442 :
443 0 : return accum;
444 : }
445 :
446 : size_t
447 0 : ShaderValidator::NumAttributes() const
448 : {
449 0 : return ShGetAttributes(mHandle)->size();
450 : }
451 :
452 : // Attribs cannot be structs or arrays, and neither can vertex inputs in ES3.
453 : // Therefore, attrib names are always simple.
454 : bool
455 0 : ShaderValidator::FindAttribUserNameByMappedName(const std::string& mappedName,
456 : const std::string** const out_userName) const
457 : {
458 0 : const std::vector<sh::Attribute>& attribs = *ShGetAttributes(mHandle);
459 0 : for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) {
460 0 : if (itr->mappedName == mappedName) {
461 0 : *out_userName = &(itr->name);
462 0 : return true;
463 : }
464 : }
465 :
466 0 : return false;
467 : }
468 :
469 : bool
470 0 : ShaderValidator::FindAttribMappedNameByUserName(const std::string& userName,
471 : const std::string** const out_mappedName) const
472 : {
473 0 : const std::vector<sh::Attribute>& attribs = *ShGetAttributes(mHandle);
474 0 : for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) {
475 0 : if (itr->name == userName) {
476 0 : *out_mappedName = &(itr->mappedName);
477 0 : return true;
478 : }
479 : }
480 :
481 0 : return false;
482 : }
483 :
484 : bool
485 0 : ShaderValidator::FindVaryingByMappedName(const std::string& mappedName,
486 : std::string* const out_userName,
487 : bool* const out_isArray) const
488 : {
489 0 : const std::vector<sh::Varying>& varyings = *ShGetVaryings(mHandle);
490 0 : for (auto itr = varyings.begin(); itr != varyings.end(); ++itr) {
491 : const sh::ShaderVariable* found;
492 0 : if (!itr->findInfoByMappedName(mappedName, &found, out_userName))
493 0 : continue;
494 :
495 0 : *out_isArray = found->isArray();
496 0 : return true;
497 : }
498 :
499 0 : return false;
500 : }
501 :
502 : bool
503 0 : ShaderValidator::FindVaryingMappedNameByUserName(const std::string& userName,
504 : const std::string** const out_mappedName) const
505 : {
506 0 : const std::vector<sh::Varying>& attribs = *ShGetVaryings(mHandle);
507 0 : for (auto itr = attribs.begin(); itr != attribs.end(); ++itr) {
508 0 : if (itr->name == userName) {
509 0 : *out_mappedName = &(itr->mappedName);
510 0 : return true;
511 : }
512 : }
513 :
514 0 : return false;
515 : }
516 : // This must handle names like "foo.bar[0]".
517 : bool
518 0 : ShaderValidator::FindUniformByMappedName(const std::string& mappedName,
519 : std::string* const out_userName,
520 : bool* const out_isArray) const
521 : {
522 0 : const std::vector<sh::Uniform>& uniforms = *ShGetUniforms(mHandle);
523 0 : for (auto itr = uniforms.begin(); itr != uniforms.end(); ++itr) {
524 : const sh::ShaderVariable* found;
525 0 : if (!itr->findInfoByMappedName(mappedName, &found, out_userName))
526 0 : continue;
527 :
528 0 : *out_isArray = found->isArray();
529 0 : return true;
530 : }
531 :
532 0 : const size_t dotPos = mappedName.find(".");
533 :
534 0 : const std::vector<sh::InterfaceBlock>& interfaces = *ShGetInterfaceBlocks(mHandle);
535 0 : for (const auto& interface : interfaces) {
536 :
537 0 : std::string mappedFieldName;
538 0 : const bool hasInstanceName = !interface.instanceName.empty();
539 :
540 : // If the InterfaceBlock has an instanceName, all variables defined
541 : // within the block are qualified with the block name, as opposed
542 : // to being placed in the global scope.
543 0 : if (hasInstanceName) {
544 :
545 : // If mappedName has no block name prefix, skip
546 0 : if (std::string::npos == dotPos)
547 0 : continue;
548 :
549 : // If mappedName has a block name prefix that doesn't match, skip
550 0 : const std::string mappedInterfaceBlockName = mappedName.substr(0, dotPos);
551 0 : if (interface.mappedName != mappedInterfaceBlockName)
552 0 : continue;
553 :
554 0 : mappedFieldName = mappedName.substr(dotPos + 1);
555 : } else {
556 0 : mappedFieldName = mappedName;
557 : }
558 :
559 0 : for (const auto& field : interface.fields) {
560 : const sh::ShaderVariable* found;
561 :
562 0 : if (!field.findInfoByMappedName(mappedFieldName, &found, out_userName))
563 0 : continue;
564 :
565 0 : if (hasInstanceName) {
566 : // Prepend the user name of the interface that matched
567 0 : *out_userName = interface.name + "." + *out_userName;
568 : }
569 :
570 0 : *out_isArray = found->isArray();
571 0 : return true;
572 : }
573 : }
574 :
575 0 : return false;
576 : }
577 :
578 : bool
579 0 : ShaderValidator::UnmapUniformBlockName(const nsACString& baseMappedName,
580 : nsCString* const out_baseUserName) const
581 : {
582 0 : const std::vector<sh::InterfaceBlock>& interfaces = *ShGetInterfaceBlocks(mHandle);
583 0 : for (const auto& interface : interfaces) {
584 : const nsDependentCString interfaceMappedName(interface.mappedName.data(),
585 0 : interface.mappedName.size());
586 0 : if (baseMappedName == interfaceMappedName) {
587 0 : *out_baseUserName = interface.name.data();
588 0 : return true;
589 : }
590 : }
591 :
592 0 : return false;
593 : }
594 :
595 : void
596 0 : ShaderValidator::EnumerateFragOutputs(std::map<nsCString, const nsCString> &out_FragOutputs) const
597 : {
598 0 : const auto* fragOutputs = ShGetOutputVariables(mHandle);
599 :
600 0 : if (fragOutputs) {
601 0 : for (const auto& fragOutput : *fragOutputs) {
602 0 : out_FragOutputs.insert({nsCString(fragOutput.name.c_str()),
603 0 : nsCString(fragOutput.mappedName.c_str())});
604 : }
605 : }
606 0 : }
607 :
608 : } // namespace webgl
609 : } // namespace mozilla
|