1 //
2 // Copyright 2002 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6
7 // Shader.cpp: Implements the gl::Shader class and its derived classes
8 // VertexShader and FragmentShader. Implements GL shader objects and related
9 // functionality. [OpenGL ES 2.0.24] section 2.10 page 24 and section 3.8 page 84.
10
11 #include "libANGLE/Shader.h"
12
13 #include <functional>
14 #include <sstream>
15
16 #include "GLSLANG/ShaderLang.h"
17 #include "common/utilities.h"
18 #include "libANGLE/Caps.h"
19 #include "libANGLE/Compiler.h"
20 #include "libANGLE/Constants.h"
21 #include "libANGLE/Context.h"
22 #include "libANGLE/ResourceManager.h"
23 #include "libANGLE/renderer/GLImplFactory.h"
24 #include "libANGLE/renderer/ShaderImpl.h"
25 #include "platform/FrontendFeatures.h"
26
27 namespace gl
28 {
29
30 namespace
31 {
32 template <typename VarT>
GetActiveShaderVariables(const std::vector<VarT> * variableList)33 std::vector<VarT> GetActiveShaderVariables(const std::vector<VarT> *variableList)
34 {
35 ASSERT(variableList);
36 std::vector<VarT> result;
37 for (size_t varIndex = 0; varIndex < variableList->size(); varIndex++)
38 {
39 const VarT &var = variableList->at(varIndex);
40 if (var.active)
41 {
42 result.push_back(var);
43 }
44 }
45 return result;
46 }
47
48 template <typename VarT>
GetShaderVariables(const std::vector<VarT> * variableList)49 const std::vector<VarT> &GetShaderVariables(const std::vector<VarT> *variableList)
50 {
51 ASSERT(variableList);
52 return *variableList;
53 }
54
55 } // anonymous namespace
56
57 // true if varying x has a higher priority in packing than y
CompareShaderVar(const sh::ShaderVariable & x,const sh::ShaderVariable & y)58 bool CompareShaderVar(const sh::ShaderVariable &x, const sh::ShaderVariable &y)
59 {
60 if (x.type == y.type)
61 {
62 return x.getArraySizeProduct() > y.getArraySizeProduct();
63 }
64
65 // Special case for handling structs: we sort these to the end of the list
66 if (x.type == GL_NONE)
67 {
68 return false;
69 }
70
71 if (y.type == GL_NONE)
72 {
73 return true;
74 }
75
76 return gl::VariableSortOrder(x.type) < gl::VariableSortOrder(y.type);
77 }
78
GetShaderTypeString(ShaderType type)79 const char *GetShaderTypeString(ShaderType type)
80 {
81 switch (type)
82 {
83 case ShaderType::Vertex:
84 return "VERTEX";
85
86 case ShaderType::Fragment:
87 return "FRAGMENT";
88
89 case ShaderType::Compute:
90 return "COMPUTE";
91
92 case ShaderType::Geometry:
93 return "GEOMETRY";
94
95 default:
96 UNREACHABLE();
97 return "";
98 }
99 }
100
101 class ScopedExit final : angle::NonCopyable
102 {
103 public:
ScopedExit(std::function<void ()> exit)104 ScopedExit(std::function<void()> exit) : mExit(exit) {}
~ScopedExit()105 ~ScopedExit() { mExit(); }
106
107 private:
108 std::function<void()> mExit;
109 };
110
111 struct Shader::CompilingState
112 {
113 std::shared_ptr<rx::WaitableCompileEvent> compileEvent;
114 ShCompilerInstance shCompilerInstance;
115 };
116
ShaderState(ShaderType shaderType)117 ShaderState::ShaderState(ShaderType shaderType)
118 : mLabel(),
119 mShaderType(shaderType),
120 mShaderVersion(100),
121 mNumViews(-1),
122 mGeometryShaderInvocations(1),
123 mCompileStatus(CompileStatus::NOT_COMPILED)
124 {
125 mLocalSize.fill(-1);
126 }
127
~ShaderState()128 ShaderState::~ShaderState() {}
129
Shader(ShaderProgramManager * manager,rx::GLImplFactory * implFactory,const gl::Limitations & rendererLimitations,ShaderType type,ShaderProgramID handle)130 Shader::Shader(ShaderProgramManager *manager,
131 rx::GLImplFactory *implFactory,
132 const gl::Limitations &rendererLimitations,
133 ShaderType type,
134 ShaderProgramID handle)
135 : mState(type),
136 mImplementation(implFactory->createShader(mState)),
137 mRendererLimitations(rendererLimitations),
138 mHandle(handle),
139 mType(type),
140 mRefCount(0),
141 mDeleteStatus(false),
142 mResourceManager(manager),
143 mCurrentMaxComputeWorkGroupInvocations(0u)
144 {
145 ASSERT(mImplementation);
146 }
147
onDestroy(const gl::Context * context)148 void Shader::onDestroy(const gl::Context *context)
149 {
150 resolveCompile();
151 mImplementation->destroy();
152 mBoundCompiler.set(context, nullptr);
153 mImplementation.reset(nullptr);
154 delete this;
155 }
156
~Shader()157 Shader::~Shader()
158 {
159 ASSERT(!mImplementation);
160 }
161
setLabel(const Context * context,const std::string & label)162 void Shader::setLabel(const Context *context, const std::string &label)
163 {
164 mState.mLabel = label;
165 }
166
getLabel() const167 const std::string &Shader::getLabel() const
168 {
169 return mState.mLabel;
170 }
171
getHandle() const172 ShaderProgramID Shader::getHandle() const
173 {
174 return mHandle;
175 }
176
setSource(GLsizei count,const char * const * string,const GLint * length)177 void Shader::setSource(GLsizei count, const char *const *string, const GLint *length)
178 {
179 std::ostringstream stream;
180
181 for (int i = 0; i < count; i++)
182 {
183 if (length == nullptr || length[i] < 0)
184 {
185 stream.write(string[i], strlen(string[i]));
186 }
187 else
188 {
189 stream.write(string[i], length[i]);
190 }
191 }
192
193 mState.mSource = stream.str();
194 }
195
getInfoLogLength()196 int Shader::getInfoLogLength()
197 {
198 resolveCompile();
199 if (mInfoLog.empty())
200 {
201 return 0;
202 }
203
204 return (static_cast<int>(mInfoLog.length()) + 1);
205 }
206
getInfoLog(GLsizei bufSize,GLsizei * length,char * infoLog)207 void Shader::getInfoLog(GLsizei bufSize, GLsizei *length, char *infoLog)
208 {
209 resolveCompile();
210
211 int index = 0;
212
213 if (bufSize > 0)
214 {
215 index = std::min(bufSize - 1, static_cast<GLsizei>(mInfoLog.length()));
216 memcpy(infoLog, mInfoLog.c_str(), index);
217
218 infoLog[index] = '\0';
219 }
220
221 if (length)
222 {
223 *length = index;
224 }
225 }
226
getSourceLength() const227 int Shader::getSourceLength() const
228 {
229 return mState.mSource.empty() ? 0 : (static_cast<int>(mState.mSource.length()) + 1);
230 }
231
getTranslatedSourceLength()232 int Shader::getTranslatedSourceLength()
233 {
234 resolveCompile();
235
236 if (mState.mTranslatedSource.empty())
237 {
238 return 0;
239 }
240
241 return (static_cast<int>(mState.mTranslatedSource.length()) + 1);
242 }
243
getTranslatedSourceWithDebugInfoLength()244 int Shader::getTranslatedSourceWithDebugInfoLength()
245 {
246 resolveCompile();
247
248 const std::string &debugInfo = mImplementation->getDebugInfo();
249 if (debugInfo.empty())
250 {
251 return 0;
252 }
253
254 return (static_cast<int>(debugInfo.length()) + 1);
255 }
256
257 // static
GetSourceImpl(const std::string & source,GLsizei bufSize,GLsizei * length,char * buffer)258 void Shader::GetSourceImpl(const std::string &source,
259 GLsizei bufSize,
260 GLsizei *length,
261 char *buffer)
262 {
263 int index = 0;
264
265 if (bufSize > 0)
266 {
267 index = std::min(bufSize - 1, static_cast<GLsizei>(source.length()));
268 memcpy(buffer, source.c_str(), index);
269
270 buffer[index] = '\0';
271 }
272
273 if (length)
274 {
275 *length = index;
276 }
277 }
278
getSource(GLsizei bufSize,GLsizei * length,char * buffer) const279 void Shader::getSource(GLsizei bufSize, GLsizei *length, char *buffer) const
280 {
281 GetSourceImpl(mState.mSource, bufSize, length, buffer);
282 }
283
getTranslatedSource(GLsizei bufSize,GLsizei * length,char * buffer)284 void Shader::getTranslatedSource(GLsizei bufSize, GLsizei *length, char *buffer)
285 {
286 GetSourceImpl(getTranslatedSource(), bufSize, length, buffer);
287 }
288
getTranslatedSource()289 const std::string &Shader::getTranslatedSource()
290 {
291 resolveCompile();
292 return mState.mTranslatedSource;
293 }
294
getTranslatedSourceWithDebugInfo(GLsizei bufSize,GLsizei * length,char * buffer)295 void Shader::getTranslatedSourceWithDebugInfo(GLsizei bufSize, GLsizei *length, char *buffer)
296 {
297 resolveCompile();
298 const std::string &debugInfo = mImplementation->getDebugInfo();
299 GetSourceImpl(debugInfo, bufSize, length, buffer);
300 }
301
compile(const Context * context)302 void Shader::compile(const Context *context)
303 {
304 resolveCompile();
305
306 mState.mTranslatedSource.clear();
307 mInfoLog.clear();
308 mState.mShaderVersion = 100;
309 mState.mInputVaryings.clear();
310 mState.mOutputVaryings.clear();
311 mState.mUniforms.clear();
312 mState.mUniformBlocks.clear();
313 mState.mShaderStorageBlocks.clear();
314 mState.mActiveAttributes.clear();
315 mState.mActiveOutputVariables.clear();
316 mState.mNumViews = -1;
317 mState.mGeometryShaderInputPrimitiveType.reset();
318 mState.mGeometryShaderOutputPrimitiveType.reset();
319 mState.mGeometryShaderMaxVertices.reset();
320 mState.mGeometryShaderInvocations = 1;
321 mState.mEarlyFragmentTestsOptimization = false;
322
323 mState.mCompileStatus = CompileStatus::COMPILE_REQUESTED;
324 mBoundCompiler.set(context, context->getCompiler());
325
326 ShCompileOptions options = (SH_OBJECT_CODE | SH_VARIABLES | SH_EMULATE_GL_DRAW_ID |
327 SH_EMULATE_GL_BASE_VERTEX_BASE_INSTANCE);
328
329 // Add default options to WebGL shaders to prevent unexpected behavior during
330 // compilation.
331 if (context->getExtensions().webglCompatibility)
332 {
333 options |= SH_INIT_GL_POSITION;
334 options |= SH_LIMIT_CALL_STACK_DEPTH;
335 options |= SH_LIMIT_EXPRESSION_COMPLEXITY;
336 options |= SH_ENFORCE_PACKING_RESTRICTIONS;
337 options |= SH_INIT_SHARED_VARIABLES;
338 }
339
340 // Some targets (eg D3D11 Feature Level 9_3 and below) do not support non-constant loop
341 // indexes in fragment shaders. Shader compilation will fail. To provide a better error
342 // message we can instruct the compiler to pre-validate.
343 if (mRendererLimitations.shadersRequireIndexedLoopValidation)
344 {
345 options |= SH_VALIDATE_LOOP_INDEXING;
346 }
347
348 if (context->getFrontendFeatures().scalarizeVecAndMatConstructorArgs.enabled)
349 {
350 options |= SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS;
351 }
352
353 mCurrentMaxComputeWorkGroupInvocations =
354 static_cast<GLuint>(context->getCaps().maxComputeWorkGroupInvocations);
355
356 mMaxComputeSharedMemory = context->getCaps().maxComputeSharedMemorySize;
357
358 ASSERT(mBoundCompiler.get());
359 ShCompilerInstance compilerInstance = mBoundCompiler->getInstance(mState.mShaderType);
360 ShHandle compilerHandle = compilerInstance.getHandle();
361 ASSERT(compilerHandle);
362 mCompilerResourcesString = compilerInstance.getBuiltinResourcesString();
363
364 mCompilingState.reset(new CompilingState());
365 mCompilingState->shCompilerInstance = std::move(compilerInstance);
366 mCompilingState->compileEvent =
367 mImplementation->compile(context, &(mCompilingState->shCompilerInstance), options);
368 }
369
resolveCompile()370 void Shader::resolveCompile()
371 {
372 if (!mState.compilePending())
373 {
374 return;
375 }
376
377 ASSERT(mCompilingState.get());
378
379 mCompilingState->compileEvent->wait();
380
381 mInfoLog += mCompilingState->compileEvent->getInfoLog();
382
383 ScopedExit exit([this]() {
384 mBoundCompiler->putInstance(std::move(mCompilingState->shCompilerInstance));
385 mCompilingState->compileEvent.reset();
386 mCompilingState.reset();
387 });
388
389 ShHandle compilerHandle = mCompilingState->shCompilerInstance.getHandle();
390 if (!mCompilingState->compileEvent->getResult())
391 {
392 mInfoLog += sh::GetInfoLog(compilerHandle);
393 WARN() << std::endl << mInfoLog;
394 mState.mCompileStatus = CompileStatus::NOT_COMPILED;
395 return;
396 }
397
398 mState.mTranslatedSource = sh::GetObjectCode(compilerHandle);
399
400 #if !defined(NDEBUG)
401 // Prefix translated shader with commented out un-translated shader.
402 // Useful in diagnostics tools which capture the shader source.
403 std::ostringstream shaderStream;
404 shaderStream << "// GLSL\n";
405 shaderStream << "//\n";
406
407 std::istringstream inputSourceStream(mState.mSource);
408 std::string line;
409 while (std::getline(inputSourceStream, line))
410 {
411 // Remove null characters from the source line
412 line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
413
414 shaderStream << "// " << line;
415
416 // glslang complains if a comment ends with backslash
417 if (!line.empty() && line.back() == '\\')
418 {
419 shaderStream << "\\";
420 }
421
422 shaderStream << std::endl;
423 }
424 shaderStream << "\n\n";
425 shaderStream << mState.mTranslatedSource;
426 mState.mTranslatedSource = shaderStream.str();
427 #endif // !defined(NDEBUG)
428
429 // Gather the shader information
430 mState.mShaderVersion = sh::GetShaderVersion(compilerHandle);
431
432 mState.mUniforms = GetShaderVariables(sh::GetUniforms(compilerHandle));
433 mState.mUniformBlocks = GetShaderVariables(sh::GetUniformBlocks(compilerHandle));
434 mState.mShaderStorageBlocks = GetShaderVariables(sh::GetShaderStorageBlocks(compilerHandle));
435
436 switch (mState.mShaderType)
437 {
438 case ShaderType::Compute:
439 {
440 mState.mAllAttributes = GetShaderVariables(sh::GetAttributes(compilerHandle));
441 mState.mActiveAttributes = GetActiveShaderVariables(&mState.mAllAttributes);
442 mState.mLocalSize = sh::GetComputeShaderLocalGroupSize(compilerHandle);
443 if (mState.mLocalSize.isDeclared())
444 {
445 angle::CheckedNumeric<uint32_t> checked_local_size_product(mState.mLocalSize[0]);
446 checked_local_size_product *= mState.mLocalSize[1];
447 checked_local_size_product *= mState.mLocalSize[2];
448
449 if (!checked_local_size_product.IsValid())
450 {
451 WARN() << std::endl
452 << "Integer overflow when computing the product of local_size_x, "
453 << "local_size_y and local_size_z.";
454 mState.mCompileStatus = CompileStatus::NOT_COMPILED;
455 return;
456 }
457 if (checked_local_size_product.ValueOrDie() >
458 mCurrentMaxComputeWorkGroupInvocations)
459 {
460 WARN() << std::endl
461 << "The total number of invocations within a work group exceeds "
462 << "MAX_COMPUTE_WORK_GROUP_INVOCATIONS.";
463 mState.mCompileStatus = CompileStatus::NOT_COMPILED;
464 return;
465 }
466 }
467
468 unsigned int sharedMemSize = sh::GetShaderSharedMemorySize(compilerHandle);
469 if (sharedMemSize > mMaxComputeSharedMemory)
470 {
471 WARN() << std::endl << "Exceeded maximum shared memory size";
472 mState.mCompileStatus = CompileStatus::NOT_COMPILED;
473 return;
474 }
475 break;
476 }
477 case ShaderType::Vertex:
478 {
479 mState.mOutputVaryings = GetShaderVariables(sh::GetOutputVaryings(compilerHandle));
480 mState.mAllAttributes = GetShaderVariables(sh::GetAttributes(compilerHandle));
481 mState.mActiveAttributes = GetActiveShaderVariables(&mState.mAllAttributes);
482 mState.mNumViews = sh::GetVertexShaderNumViews(compilerHandle);
483 break;
484 }
485 case ShaderType::Fragment:
486 {
487 mState.mAllAttributes = GetShaderVariables(sh::GetAttributes(compilerHandle));
488 mState.mActiveAttributes = GetActiveShaderVariables(&mState.mAllAttributes);
489 mState.mInputVaryings = GetShaderVariables(sh::GetInputVaryings(compilerHandle));
490 // TODO(jmadill): Figure out why we only sort in the FS, and if we need to.
491 std::sort(mState.mInputVaryings.begin(), mState.mInputVaryings.end(), CompareShaderVar);
492 mState.mActiveOutputVariables =
493 GetActiveShaderVariables(sh::GetOutputVariables(compilerHandle));
494 mState.mEarlyFragmentTestsOptimization =
495 sh::HasEarlyFragmentTestsOptimization(compilerHandle);
496 break;
497 }
498 case ShaderType::Geometry:
499 {
500 mState.mInputVaryings = GetShaderVariables(sh::GetInputVaryings(compilerHandle));
501 mState.mOutputVaryings = GetShaderVariables(sh::GetOutputVaryings(compilerHandle));
502
503 if (sh::HasValidGeometryShaderInputPrimitiveType(compilerHandle))
504 {
505 mState.mGeometryShaderInputPrimitiveType = FromGLenum<PrimitiveMode>(
506 sh::GetGeometryShaderInputPrimitiveType(compilerHandle));
507 }
508 if (sh::HasValidGeometryShaderOutputPrimitiveType(compilerHandle))
509 {
510 mState.mGeometryShaderOutputPrimitiveType = FromGLenum<PrimitiveMode>(
511 sh::GetGeometryShaderOutputPrimitiveType(compilerHandle));
512 }
513 if (sh::HasValidGeometryShaderMaxVertices(compilerHandle))
514 {
515 mState.mGeometryShaderMaxVertices =
516 sh::GetGeometryShaderMaxVertices(compilerHandle);
517 }
518 mState.mGeometryShaderInvocations = sh::GetGeometryShaderInvocations(compilerHandle);
519 break;
520 }
521 default:
522 UNREACHABLE();
523 }
524
525 ASSERT(!mState.mTranslatedSource.empty());
526
527 bool success = mCompilingState->compileEvent->postTranslate(&mInfoLog);
528 mState.mCompileStatus = success ? CompileStatus::COMPILED : CompileStatus::NOT_COMPILED;
529 }
530
addRef()531 void Shader::addRef()
532 {
533 mRefCount++;
534 }
535
release(const Context * context)536 void Shader::release(const Context *context)
537 {
538 mRefCount--;
539
540 if (mRefCount == 0 && mDeleteStatus)
541 {
542 mResourceManager->deleteShader(context, mHandle);
543 }
544 }
545
getRefCount() const546 unsigned int Shader::getRefCount() const
547 {
548 return mRefCount;
549 }
550
isFlaggedForDeletion() const551 bool Shader::isFlaggedForDeletion() const
552 {
553 return mDeleteStatus;
554 }
555
flagForDeletion()556 void Shader::flagForDeletion()
557 {
558 mDeleteStatus = true;
559 }
560
isCompiled()561 bool Shader::isCompiled()
562 {
563 resolveCompile();
564 return mState.mCompileStatus == CompileStatus::COMPILED;
565 }
566
isCompleted()567 bool Shader::isCompleted()
568 {
569 return (!mState.compilePending() || mCompilingState->compileEvent->isReady());
570 }
571
getShaderVersion()572 int Shader::getShaderVersion()
573 {
574 resolveCompile();
575 return mState.mShaderVersion;
576 }
577
getInputVaryings()578 const std::vector<sh::ShaderVariable> &Shader::getInputVaryings()
579 {
580 resolveCompile();
581 return mState.getInputVaryings();
582 }
583
getOutputVaryings()584 const std::vector<sh::ShaderVariable> &Shader::getOutputVaryings()
585 {
586 resolveCompile();
587 return mState.getOutputVaryings();
588 }
589
getUniforms()590 const std::vector<sh::ShaderVariable> &Shader::getUniforms()
591 {
592 resolveCompile();
593 return mState.getUniforms();
594 }
595
getUniformBlocks()596 const std::vector<sh::InterfaceBlock> &Shader::getUniformBlocks()
597 {
598 resolveCompile();
599 return mState.getUniformBlocks();
600 }
601
getShaderStorageBlocks()602 const std::vector<sh::InterfaceBlock> &Shader::getShaderStorageBlocks()
603 {
604 resolveCompile();
605 return mState.getShaderStorageBlocks();
606 }
607
getActiveAttributes()608 const std::vector<sh::ShaderVariable> &Shader::getActiveAttributes()
609 {
610 resolveCompile();
611 return mState.getActiveAttributes();
612 }
613
getAllAttributes()614 const std::vector<sh::ShaderVariable> &Shader::getAllAttributes()
615 {
616 resolveCompile();
617 return mState.getAllAttributes();
618 }
619
getActiveOutputVariables()620 const std::vector<sh::ShaderVariable> &Shader::getActiveOutputVariables()
621 {
622 resolveCompile();
623 return mState.getActiveOutputVariables();
624 }
625
getTransformFeedbackVaryingMappedName(const std::string & tfVaryingName)626 std::string Shader::getTransformFeedbackVaryingMappedName(const std::string &tfVaryingName)
627 {
628 // TODO(jiawei.shao@intel.com): support transform feedback on geometry shader.
629 ASSERT(mState.getShaderType() == ShaderType::Vertex ||
630 mState.getShaderType() == ShaderType::Geometry);
631 const auto &varyings = getOutputVaryings();
632 auto bracketPos = tfVaryingName.find("[");
633 if (bracketPos != std::string::npos)
634 {
635 auto tfVaryingBaseName = tfVaryingName.substr(0, bracketPos);
636 for (const auto &varying : varyings)
637 {
638 if (varying.name == tfVaryingBaseName)
639 {
640 std::string mappedNameWithArrayIndex =
641 varying.mappedName + tfVaryingName.substr(bracketPos);
642 return mappedNameWithArrayIndex;
643 }
644 }
645 }
646 else
647 {
648 for (const auto &varying : varyings)
649 {
650 if (varying.name == tfVaryingName)
651 {
652 return varying.mappedName;
653 }
654 else if (varying.isStruct())
655 {
656 GLuint fieldIndex = 0;
657 const auto *field = FindShaderVarField(varying, tfVaryingName, &fieldIndex);
658 ASSERT(field != nullptr && !field->isStruct() && !field->isArray());
659 return varying.mappedName + "." + field->mappedName;
660 }
661 }
662 }
663 UNREACHABLE();
664 return std::string();
665 }
666
getWorkGroupSize()667 const sh::WorkGroupSize &Shader::getWorkGroupSize()
668 {
669 resolveCompile();
670 return mState.mLocalSize;
671 }
672
getNumViews()673 int Shader::getNumViews()
674 {
675 resolveCompile();
676 return mState.mNumViews;
677 }
678
getGeometryShaderInputPrimitiveType()679 Optional<PrimitiveMode> Shader::getGeometryShaderInputPrimitiveType()
680 {
681 resolveCompile();
682 return mState.mGeometryShaderInputPrimitiveType;
683 }
684
getGeometryShaderOutputPrimitiveType()685 Optional<PrimitiveMode> Shader::getGeometryShaderOutputPrimitiveType()
686 {
687 resolveCompile();
688 return mState.mGeometryShaderOutputPrimitiveType;
689 }
690
getGeometryShaderInvocations()691 int Shader::getGeometryShaderInvocations()
692 {
693 resolveCompile();
694 return mState.mGeometryShaderInvocations;
695 }
696
getGeometryShaderMaxVertices()697 Optional<GLint> Shader::getGeometryShaderMaxVertices()
698 {
699 resolveCompile();
700 return mState.mGeometryShaderMaxVertices;
701 }
702
getCompilerResourcesString() const703 const std::string &Shader::getCompilerResourcesString() const
704 {
705 return mCompilerResourcesString;
706 }
707
708 } // namespace gl
709