diff --git a/include/GLSLANG/ShaderLang.h b/include/GLSLANG/ShaderLang.h index 355f9f0c4..1b242dc9f 100644 --- a/include/GLSLANG/ShaderLang.h +++ b/include/GLSLANG/ShaderLang.h @@ -49,7 +49,7 @@ typedef unsigned int GLenum; // Version number for shader translation API. // It is incremented every time the API changes. -#define ANGLE_SH_VERSION 153 +#define ANGLE_SH_VERSION 154 typedef enum { SH_GLES2_SPEC, @@ -501,7 +501,7 @@ COMPILER_EXPORT const std::vector *ShGetVaryings(const ShHandle han COMPILER_EXPORT const std::vector *ShGetAttributes(const ShHandle handle); COMPILER_EXPORT const std::vector *ShGetOutputVariables(const ShHandle handle); COMPILER_EXPORT const std::vector *ShGetInterfaceBlocks(const ShHandle handle); -COMPILER_EXPORT std::array ShGetComputeShaderLocalGroupSize(const ShHandle handle); +COMPILER_EXPORT sh::WorkGroupSize ShGetComputeShaderLocalGroupSize(const ShHandle handle); typedef struct { diff --git a/include/GLSLANG/ShaderVars.h b/include/GLSLANG/ShaderVars.h index aa0009df6..e1c140f92 100644 --- a/include/GLSLANG/ShaderVars.h +++ b/include/GLSLANG/ShaderVars.h @@ -10,9 +10,9 @@ #ifndef GLSLANG_SHADERVARS_H_ #define GLSLANG_SHADERVARS_H_ +#include #include #include -#include // Assume ShaderLang.h is included before ShaderVars.h, for sh::GLenum // Note: make sure to increment ANGLE_SH_VERSION when changing ShaderVars.h @@ -216,6 +216,32 @@ struct COMPILER_EXPORT InterfaceBlock std::vector fields; }; +struct COMPILER_EXPORT WorkGroupSize +{ + void fill(int fillValue); + void setLocalSize(int localSizeX, int localSizeY, int localSizeZ); + + int &operator[](size_t index); + int operator[](size_t index) const; + size_t size() const; + + // Checks whether two work group size declarations match. + // Two work group size declarations are the same if the explicitly specified elements are the + // same or if one of them is specified as one and the other one is not specified + bool isWorkGroupSizeMatching(const WorkGroupSize &right) const; + + // Checks whether any of the values are set. + bool isAnyValueSet() const; + + // Checks whether all of the values are set. + bool isDeclared() const; + + // Checks whether either all of the values are set, or none of them are. + bool isLocalSizeValid() const; + + int localSizeQualifiers[3]; +}; + } // namespace sh #endif // GLSLANG_SHADERVARS_H_ diff --git a/src/compiler/translator/BaseTypes.h b/src/compiler/translator/BaseTypes.h index 5319f7ffe..72ecb50f4 100644 --- a/src/compiler/translator/BaseTypes.h +++ b/src/compiler/translator/BaseTypes.h @@ -11,6 +11,7 @@ #include #include "common/debug.h" +#include "GLSLANG/ShaderLang.h" // // Precision qualifiers @@ -377,8 +378,6 @@ enum TLayoutBlockStorage EbsStd140 }; -using TLocalSize = std::array; - struct TLayoutQualifier { int location; @@ -386,8 +385,7 @@ struct TLayoutQualifier TLayoutBlockStorage blockStorage; // Compute shader layout qualifiers. - // -1 means unspecified. - TLocalSize localSize; + sh::WorkGroupSize localSize; static TLayoutQualifier create() { @@ -405,19 +403,12 @@ struct TLayoutQualifier bool isEmpty() const { return location == -1 && matrixPacking == EmpUnspecified && - blockStorage == EbsUnspecified && localSize[0] == -1 && localSize[1] == -1 && - localSize[2] == -1; - } - - bool isGroupSizeSpecified() const - { - return std::any_of(localSize.begin(), localSize.end(), - [](int value) { return value != -1; }); + blockStorage == EbsUnspecified && !localSize.isAnyValueSet(); } bool isCombinationValid() const { - bool workSizeSpecified = isGroupSizeSpecified(); + bool workSizeSpecified = localSize.isAnyValueSet(); bool otherLayoutQualifiersSpecified = (location != -1 || matrixPacking != EmpUnspecified || blockStorage != EbsUnspecified); @@ -425,23 +416,13 @@ struct TLayoutQualifier return !(workSizeSpecified && otherLayoutQualifiersSpecified); } - bool isLocalSizeEqual(const TLocalSize &localSizeIn) const + bool isLocalSizeEqual(const sh::WorkGroupSize &localSizeIn) const { - for (size_t i = 0u; i < localSize.size(); ++i) - { - bool result = - (localSize[i] == localSizeIn[i] || (localSize[i] == 1 && localSizeIn[i] == -1) || - (localSize[i] == -1 && localSizeIn[i] == 1)); - if (!result) - { - return false; - } - } - return true; + return localSize.isWorkGroupSizeMatching(localSizeIn); } }; -inline const char *getLocalSizeString(size_t dimension) +inline const char *getWorkGroupSizeString(size_t dimension) { switch (dimension) { diff --git a/src/compiler/translator/Compiler.h b/src/compiler/translator/Compiler.h index db5ec263e..6b682374b 100644 --- a/src/compiler/translator/Compiler.h +++ b/src/compiler/translator/Compiler.h @@ -86,7 +86,7 @@ class TCompiler : public TShHandleBase TInfoSink& getInfoSink() { return infoSink; } bool isComputeShaderLocalSizeDeclared() const { return mComputeShaderLocalSizeDeclared; } - const TLocalSize &getComputeShaderLocalSize() { return mComputeShaderLocalSize; } + const sh::WorkGroupSize &getComputeShaderLocalSize() { return mComputeShaderLocalSize; } // Clears the results from the previous compilation. void clearResults(); @@ -237,7 +237,7 @@ class TCompiler : public TShHandleBase // compute shader local group size bool mComputeShaderLocalSizeDeclared; - TLocalSize mComputeShaderLocalSize; + sh::WorkGroupSize mComputeShaderLocalSize; // name hashing. ShHashFunction64 hashFunction; diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp index 66fdceb9f..3edabe4db 100644 --- a/src/compiler/translator/ParseContext.cpp +++ b/src/compiler/translator/ParseContext.cpp @@ -1044,12 +1044,12 @@ void TParseContext::checkLayoutQualifierSupported(const TSourceLoc &location, bool TParseContext::checkWorkGroupSizeIsNotSpecified(const TSourceLoc &location, const TLayoutQualifier &layoutQualifier) { - const TLocalSize &localSize = layoutQualifier.localSize; + const sh::WorkGroupSize &localSize = layoutQualifier.localSize; for (size_t i = 0u; i < localSize.size(); ++i) { if (localSize[i] != -1) { - error(location, "invalid layout qualifier:", getLocalSizeString(i), + error(location, "invalid layout qualifier:", getWorkGroupSizeString(i), "only valid when used with 'in' in a compute shader global layout declaration"); return false; } @@ -1119,9 +1119,9 @@ void TParseContext::handlePragmaDirective(const TSourceLoc &loc, mDirectiveHandler.handlePragma(srcLoc, name, value, stdgl); } -TLocalSize TParseContext::getComputeShaderLocalSize() const +sh::WorkGroupSize TParseContext::getComputeShaderLocalSize() const { - TLocalSize result; + sh::WorkGroupSize result; for (size_t i = 0u; i < result.size(); ++i) { if (mComputeShaderLocalSizeDeclared && mComputeShaderLocalSize[i] == -1) @@ -1895,7 +1895,7 @@ void TParseContext::parseGlobalLayoutQualifier(const TPublicType &typeQualifier) return; } - if (!layoutQualifier.isGroupSizeSpecified()) + if (!layoutQualifier.localSize.isAnyValueSet()) { error(typeQualifier.line, "No local work group size specified", "layout"); return; @@ -1921,7 +1921,7 @@ void TParseContext::parseGlobalLayoutQualifier(const TPublicType &typeQualifier) << maxComputeWorkGroupSizeValue; const std::string &errorMessage = errorMessageStream.str(); - error(typeQualifier.line, "invalid value:", getLocalSizeString(i), + error(typeQualifier.line, "invalid value:", getWorkGroupSizeString(i), errorMessage.c_str()); return; } @@ -3052,12 +3052,12 @@ void TParseContext::parseLocalSize(const TString &qualifierType, const TSourceLoc &intValueLine, const std::string &intValueString, size_t index, - TLocalSize *localSize) + sh::WorkGroupSize *localSize) { checkLayoutQualifierSupported(qualifierTypeLine, qualifierType, 310); if (intValue < 1) { - std::string errorMessage = std::string(getLocalSizeString(index)) + " must be positive"; + std::string errorMessage = std::string(getWorkGroupSizeString(index)) + " must be positive"; error(intValueLine, "out of range:", intValueString.c_str(), errorMessage.c_str()); } (*localSize)[index] = intValue; @@ -3136,7 +3136,7 @@ TLayoutQualifier TParseContext::joinLayoutQualifiers(TLayoutQualifier leftQualif { error(rightQualifierLocation, "Cannot have multiple different work group size specifiers", - getLocalSizeString(i)); + getWorkGroupSizeString(i)); } joinedQualifier.localSize[i] = rightQualifier.localSize[i]; } diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h index b5425d9bc..cf153db36 100644 --- a/src/compiler/translator/ParseContext.h +++ b/src/compiler/translator/ParseContext.h @@ -117,7 +117,7 @@ class TParseContext : angle::NonCopyable void decrSwitchNestingLevel() { --mSwitchNestingLevel; } bool isComputeShaderLocalSizeDeclared() const { return mComputeShaderLocalSizeDeclared; } - TLocalSize getComputeShaderLocalSize() const; + sh::WorkGroupSize getComputeShaderLocalSize() const; // This method is guaranteed to succeed, even if no variable with 'name' exists. const TVariable *getNamedVariable(const TSourceLoc &location, const TString *name, const TSymbol *symbol); @@ -308,7 +308,7 @@ class TParseContext : angle::NonCopyable const TSourceLoc &intValueLine, const std::string &intValueString, size_t index, - TLocalSize *localSize); + sh::WorkGroupSize *localSize); TLayoutQualifier parseLayoutQualifier( const TString &qualifierType, const TSourceLoc &qualifierTypeLine); TLayoutQualifier parseLayoutQualifier(const TString &qualifierType, @@ -437,7 +437,7 @@ class TParseContext : angle::NonCopyable // keep track of local group size declared in layout. It should be declared only once. bool mComputeShaderLocalSizeDeclared; - TLocalSize mComputeShaderLocalSize; + sh::WorkGroupSize mComputeShaderLocalSize; }; int PaParseStrings( diff --git a/src/compiler/translator/ShaderLang.cpp b/src/compiler/translator/ShaderLang.cpp index 4cbf2e538..429bbf2e4 100644 --- a/src/compiler/translator/ShaderLang.cpp +++ b/src/compiler/translator/ShaderLang.cpp @@ -358,7 +358,7 @@ const std::vector *ShGetInterfaceBlocks(const ShHandle handl return GetShaderVariables(handle); } -std::array ShGetComputeShaderLocalGroupSize(const ShHandle handle) +sh::WorkGroupSize ShGetComputeShaderLocalGroupSize(const ShHandle handle) { ASSERT(handle); diff --git a/src/compiler/translator/ShaderVars.cpp b/src/compiler/translator/ShaderVars.cpp index 846815e3f..8e217f1c0 100644 --- a/src/compiler/translator/ShaderVars.cpp +++ b/src/compiler/translator/ShaderVars.cpp @@ -418,4 +418,71 @@ bool InterfaceBlock::isSameInterfaceBlockAtLinkTime(const InterfaceBlock &other) return true; } +void WorkGroupSize::fill(int fillValue) +{ + localSizeQualifiers[0] = fillValue; + localSizeQualifiers[1] = fillValue; + localSizeQualifiers[2] = fillValue; +} + +void WorkGroupSize::setLocalSize(int localSizeX, int localSizeY, int localSizeZ) +{ + localSizeQualifiers[0] = localSizeX; + localSizeQualifiers[1] = localSizeY; + localSizeQualifiers[2] = localSizeZ; +} + +// check that if one of them is less than 1, then all of them are. +// Or if one is positive, then all of them are positive. +bool WorkGroupSize::isLocalSizeValid() const +{ + return ( + (localSizeQualifiers[0] < 1 && localSizeQualifiers[1] < 1 && localSizeQualifiers[2] < 1) || + (localSizeQualifiers[0] > 0 && localSizeQualifiers[1] > 0 && localSizeQualifiers[2] > 0)); +} + +bool WorkGroupSize::isAnyValueSet() const +{ + return localSizeQualifiers[0] > 0 || localSizeQualifiers[1] > 0 || localSizeQualifiers[2] > 0; +} + +bool WorkGroupSize::isDeclared() const +{ + bool localSizeDeclared = localSizeQualifiers[0] > 0; + ASSERT(isLocalSizeValid()); + return localSizeDeclared; +} + +bool WorkGroupSize::isWorkGroupSizeMatching(const WorkGroupSize &right) const +{ + for (size_t i = 0u; i < size(); ++i) + { + bool result = (localSizeQualifiers[i] == right.localSizeQualifiers[i] || + (localSizeQualifiers[i] == 1 && right.localSizeQualifiers[i] == -1) || + (localSizeQualifiers[i] == -1 && right.localSizeQualifiers[i] == 1)); + if (!result) + { + return false; + } + } + return true; +} + +int &WorkGroupSize::operator[](size_t index) +{ + ASSERT(index < size()); + return localSizeQualifiers[index]; +} + +int WorkGroupSize::operator[](size_t index) const +{ + ASSERT(index < size()); + return localSizeQualifiers[index]; +} + +size_t WorkGroupSize::size() const +{ + return 3u; +} + } // namespace sh diff --git a/src/compiler/translator/TranslatorESSL.cpp b/src/compiler/translator/TranslatorESSL.cpp index 392ea09f3..fef176857 100644 --- a/src/compiler/translator/TranslatorESSL.cpp +++ b/src/compiler/translator/TranslatorESSL.cpp @@ -80,7 +80,7 @@ void TranslatorESSL::translate(TIntermNode *root, int compileOptions) if (getShaderType() == GL_COMPUTE_SHADER && isComputeShaderLocalSizeDeclared()) { - const TLocalSize &localSize = getComputeShaderLocalSize(); + const sh::WorkGroupSize &localSize = getComputeShaderLocalSize(); sink << "layout (local_size_x=" << localSize[0] << ", local_size_y=" << localSize[1] << ", local_size_z=" << localSize[2] << ") in;\n"; } diff --git a/src/compiler/translator/TranslatorGLSL.cpp b/src/compiler/translator/TranslatorGLSL.cpp index dc4541065..0ee96f590 100644 --- a/src/compiler/translator/TranslatorGLSL.cpp +++ b/src/compiler/translator/TranslatorGLSL.cpp @@ -167,7 +167,7 @@ void TranslatorGLSL::translate(TIntermNode *root, int compileOptions) if (getShaderType() == GL_COMPUTE_SHADER && isComputeShaderLocalSizeDeclared()) { - const TLocalSize &localSize = getComputeShaderLocalSize(); + const sh::WorkGroupSize &localSize = getComputeShaderLocalSize(); sink << "layout (local_size_x=" << localSize[0] << ", local_size_y=" << localSize[1] << ", local_size_z=" << localSize[2] << ") in;\n"; } diff --git a/src/libANGLE/Compiler.cpp b/src/libANGLE/Compiler.cpp index 0015b7a64..b745888f2 100644 --- a/src/libANGLE/Compiler.cpp +++ b/src/libANGLE/Compiler.cpp @@ -47,7 +47,8 @@ Compiler::Compiler(rx::GLImplFactory *implFactory, const ContextState &state) mOutputType(mImplementation->getTranslatorOutputType()), mResources(), mFragmentCompiler(nullptr), - mVertexCompiler(nullptr) + mVertexCompiler(nullptr), + mComputeCompiler(nullptr) { ASSERT(state.getClientMajorVersion() == 2 || state.getClientMajorVersion() == 3); @@ -135,6 +136,15 @@ Error Compiler::release() activeCompilerHandles--; } + if (mComputeCompiler) + { + ShDestruct(mComputeCompiler); + mComputeCompiler = nullptr; + + ASSERT(activeCompilerHandles > 0); + activeCompilerHandles--; + } + if (activeCompilerHandles == 0) { ShFinalize(); @@ -157,7 +167,9 @@ ShHandle Compiler::getCompilerHandle(GLenum type) case GL_FRAGMENT_SHADER: compiler = &mFragmentCompiler; break; - + case GL_COMPUTE_SHADER: + compiler = &mComputeCompiler; + break; default: UNREACHABLE(); return nullptr; diff --git a/src/libANGLE/Compiler.h b/src/libANGLE/Compiler.h index 03b1a49db..14226630e 100644 --- a/src/libANGLE/Compiler.h +++ b/src/libANGLE/Compiler.h @@ -42,6 +42,7 @@ class Compiler final : angle::NonCopyable ShHandle mFragmentCompiler; ShHandle mVertexCompiler; + ShHandle mComputeCompiler; }; } // namespace gl diff --git a/src/libANGLE/Program.cpp b/src/libANGLE/Program.cpp index ae6777782..7f4226da1 100644 --- a/src/libANGLE/Program.cpp +++ b/src/libANGLE/Program.cpp @@ -237,9 +237,11 @@ ProgramState::ProgramState() : mLabel(), mAttachedFragmentShader(nullptr), mAttachedVertexShader(nullptr), + mAttachedComputeShader(nullptr), mTransformFeedbackBufferMode(GL_INTERLEAVED_ATTRIBS), mBinaryRetrieveableHint(false) { + mComputeShaderLocalSize.fill(1); } ProgramState::~ProgramState() @@ -253,6 +255,11 @@ ProgramState::~ProgramState() { mAttachedFragmentShader->release(); } + + if (mAttachedComputeShader != nullptr) + { + mAttachedComputeShader->release(); + } } const std::string &ProgramState::getLabel() @@ -372,61 +379,96 @@ const std::string &Program::getLabel() const bool Program::attachShader(Shader *shader) { - if (shader->getType() == GL_VERTEX_SHADER) + switch (shader->getType()) { - if (mState.mAttachedVertexShader) + case GL_VERTEX_SHADER: { - return false; - } + if (mState.mAttachedVertexShader) + { + return false; + } - mState.mAttachedVertexShader = shader; - mState.mAttachedVertexShader->addRef(); - } - else if (shader->getType() == GL_FRAGMENT_SHADER) - { - if (mState.mAttachedFragmentShader) + mState.mAttachedVertexShader = shader; + mState.mAttachedVertexShader->addRef(); + break; + } + case GL_FRAGMENT_SHADER: { - return false; - } + if (mState.mAttachedFragmentShader) + { + return false; + } - mState.mAttachedFragmentShader = shader; - mState.mAttachedFragmentShader->addRef(); + mState.mAttachedFragmentShader = shader; + mState.mAttachedFragmentShader->addRef(); + break; + } + case GL_COMPUTE_SHADER: + { + if (mState.mAttachedComputeShader) + { + return false; + } + + mState.mAttachedComputeShader = shader; + mState.mAttachedComputeShader->addRef(); + break; + } + default: + UNREACHABLE(); } - else UNREACHABLE(); return true; } bool Program::detachShader(Shader *shader) { - if (shader->getType() == GL_VERTEX_SHADER) + switch (shader->getType()) { - if (mState.mAttachedVertexShader != shader) + case GL_VERTEX_SHADER: { - return false; - } + if (mState.mAttachedVertexShader != shader) + { + return false; + } - shader->release(); - mState.mAttachedVertexShader = nullptr; - } - else if (shader->getType() == GL_FRAGMENT_SHADER) - { - if (mState.mAttachedFragmentShader != shader) + shader->release(); + mState.mAttachedVertexShader = nullptr; + break; + } + case GL_FRAGMENT_SHADER: { - return false; - } + if (mState.mAttachedFragmentShader != shader) + { + return false; + } - shader->release(); - mState.mAttachedFragmentShader = nullptr; + shader->release(); + mState.mAttachedFragmentShader = nullptr; + break; + } + case GL_COMPUTE_SHADER: + { + if (mState.mAttachedComputeShader != shader) + { + return false; + } + + shader->release(); + mState.mAttachedComputeShader = nullptr; + break; + } + default: + UNREACHABLE(); } - else UNREACHABLE(); return true; } int Program::getAttachedShadersCount() const { - return (mState.mAttachedVertexShader ? 1 : 0) + (mState.mAttachedFragmentShader ? 1 : 0); + return (mState.mAttachedVertexShader ? 1 : 0) + (mState.mAttachedFragmentShader ? 1 : 0) + + (mState.mAttachedComputeShader ? 1 : 0); } void Program::bindAttributeLocation(GLuint index, const char *name) @@ -516,9 +558,9 @@ void Program::pathFragmentInputGen(GLint index, mProgram->setPathFragmentInputGen(binding.name, genMode, components, coeffs); } -// Links the HLSL code of the vertex and pixel shader by matching up their varyings, -// compiling them into binaries, determining the attribute mappings, and collecting -// a list of uniforms +// The attached shaders are checked for linking errors by matching up their variables. +// Uniform, input and output variables get collected. +// The code gets compiled into binaries. Error Program::link(const ContextState &data) { unlink(false); @@ -526,65 +568,119 @@ Error Program::link(const ContextState &data) mInfoLog.reset(); resetUniformBlockBindings(); - if (!mState.mAttachedFragmentShader || !mState.mAttachedFragmentShader->isCompiled()) - { - return Error(GL_NO_ERROR); - } - ASSERT(mState.mAttachedFragmentShader->getType() == GL_FRAGMENT_SHADER); + const Caps &caps = data.getCaps(); - if (!mState.mAttachedVertexShader || !mState.mAttachedVertexShader->isCompiled()) + bool isComputeShaderAttached = (mState.mAttachedComputeShader != nullptr); + bool nonComputeShadersAttached = + (mState.mAttachedVertexShader != nullptr || mState.mAttachedFragmentShader != nullptr); + // Check whether we both have a compute and non-compute shaders attached. + // If there are of both types attached, then linking should fail. + // OpenGL ES 3.10, 7.3 Program Objects, under LinkProgram + if (isComputeShaderAttached == true && nonComputeShadersAttached == true) { - return Error(GL_NO_ERROR); - } - ASSERT(mState.mAttachedVertexShader->getType() == GL_VERTEX_SHADER); - - if (mState.mAttachedFragmentShader->getShaderVersion() != - mState.mAttachedVertexShader->getShaderVersion()) - { - mInfoLog << "Fragment shader version does not match vertex shader version."; - return Error(GL_NO_ERROR); + mInfoLog << "Both a compute and non-compute shaders are attached to the same program."; + return NoError(); } - if (!linkAttributes(data, mInfoLog, mAttributeBindings, mState.mAttachedVertexShader)) + if (mState.mAttachedComputeShader) { - return Error(GL_NO_ERROR); + if (!mState.mAttachedComputeShader->isCompiled()) + { + mInfoLog << "Attached compute shader is not compiled."; + return NoError(); + } + ASSERT(mState.mAttachedComputeShader->getType() == GL_COMPUTE_SHADER); + + mState.mComputeShaderLocalSize = mState.mAttachedComputeShader->getWorkGroupSize(); + + // GLSL ES 3.10, 4.4.1.1 Compute Shader Inputs + // If the work group size is not specified, a link time error should occur. + if (!mState.mComputeShaderLocalSize.isDeclared()) + { + mInfoLog << "Work group size is not specified."; + return NoError(); + } + + if (!linkUniforms(mInfoLog, caps, mUniformBindings)) + { + return NoError(); + } + + if (!linkUniformBlocks(mInfoLog, caps)) + { + return NoError(); + } + + rx::LinkResult result = mProgram->link(data, mInfoLog); + + if (result.error.isError() || !result.linkSuccess) + { + return result.error; + } + } + else + { + if (!mState.mAttachedFragmentShader || !mState.mAttachedFragmentShader->isCompiled()) + { + return NoError(); + } + ASSERT(mState.mAttachedFragmentShader->getType() == GL_FRAGMENT_SHADER); + + if (!mState.mAttachedVertexShader || !mState.mAttachedVertexShader->isCompiled()) + { + return NoError(); + } + ASSERT(mState.mAttachedVertexShader->getType() == GL_VERTEX_SHADER); + + if (mState.mAttachedFragmentShader->getShaderVersion() != + mState.mAttachedVertexShader->getShaderVersion()) + { + mInfoLog << "Fragment shader version does not match vertex shader version."; + return NoError(); + } + + if (!linkAttributes(data, mInfoLog, mAttributeBindings, mState.mAttachedVertexShader)) + { + return NoError(); + } + + if (!linkVaryings(mInfoLog, mState.mAttachedVertexShader, mState.mAttachedFragmentShader)) + { + return NoError(); + } + + if (!linkUniforms(mInfoLog, caps, mUniformBindings)) + { + return NoError(); + } + + if (!linkUniformBlocks(mInfoLog, caps)) + { + return NoError(); + } + + const auto &mergedVaryings = getMergedVaryings(); + + if (!linkValidateTransformFeedback(mInfoLog, mergedVaryings, caps)) + { + return NoError(); + } + + linkOutputVariables(); + + rx::LinkResult result = mProgram->link(data, mInfoLog); + if (result.error.isError() || !result.linkSuccess) + { + return result.error; + } + + gatherTransformFeedbackVaryings(mergedVaryings); } - if (!linkVaryings(mInfoLog, mState.mAttachedVertexShader, mState.mAttachedFragmentShader)) - { - return Error(GL_NO_ERROR); - } - - if (!linkUniforms(mInfoLog, data.getCaps(), mUniformBindings)) - { - return Error(GL_NO_ERROR); - } - - if (!linkUniformBlocks(mInfoLog, data.getCaps())) - { - return Error(GL_NO_ERROR); - } - - const auto &mergedVaryings = getMergedVaryings(); - - if (!linkValidateTransformFeedback(mInfoLog, mergedVaryings, data.getCaps())) - { - return Error(GL_NO_ERROR); - } - - linkOutputVariables(); - - rx::LinkResult result = mProgram->link(data, mInfoLog); - if (result.error.isError() || !result.linkSuccess) - { - return result.error; - } - - gatherTransformFeedbackVaryings(mergedVaryings); gatherInterfaceBlockInfo(); mLinked = true; - return gl::Error(GL_NO_ERROR); + return NoError(); } // Returns the program object to an unlinked state, before re-linking, or at destruction @@ -603,6 +699,12 @@ void Program::unlink(bool destroy) mState.mAttachedVertexShader->release(); mState.mAttachedVertexShader = nullptr; } + + if (mState.mAttachedComputeShader) + { + mState.mAttachedComputeShader->release(); + mState.mAttachedComputeShader = nullptr; + } } mState.mAttributes.clear(); @@ -612,6 +714,7 @@ void Program::unlink(bool destroy) mState.mUniformLocations.clear(); mState.mUniformBlocks.clear(); mState.mOutputVariables.clear(); + mState.mComputeShaderLocalSize.fill(1); mValidated = false; @@ -655,6 +758,10 @@ Error Program::loadBinary(GLenum binaryFormat, const void *binary, GLsizei lengt return Error(GL_NO_ERROR); } + mState.mComputeShaderLocalSize[0] = stream.readInt(); + mState.mComputeShaderLocalSize[1] = stream.readInt(); + mState.mComputeShaderLocalSize[2] = stream.readInt(); + static_assert(MAX_VERTEX_ATTRIBS <= sizeof(unsigned long) * 8, "Too many vertex attribs for mask"); mState.mActiveAttribLocationsMask = stream.readInt(); @@ -776,6 +883,10 @@ Error Program::saveBinary(GLenum *binaryFormat, void *binary, GLsizei bufSize, G stream.writeInt(ANGLE_MINOR_VERSION); stream.writeBytes(reinterpret_cast(ANGLE_COMMIT_HASH), ANGLE_COMMIT_HASH_SIZE); + stream.writeInt(mState.mComputeShaderLocalSize[0]); + stream.writeInt(mState.mComputeShaderLocalSize[1]); + stream.writeInt(mState.mComputeShaderLocalSize[2]); + stream.writeInt(mState.mActiveAttribLocationsMask.to_ulong()); stream.writeInt(mState.mAttributes.size()); @@ -947,6 +1058,15 @@ void Program::getAttachedShaders(GLsizei maxCount, GLsizei *count, GLuint *shade { int total = 0; + if (mState.mAttachedComputeShader) + { + if (total < maxCount) + { + shaders[total] = mState.mAttachedComputeShader->getHandle(); + total++; + } + } + if (mState.mAttachedVertexShader) { if (total < maxCount) @@ -1772,17 +1892,14 @@ bool Program::linkVaryings(InfoLog &infoLog, return true; } -bool Program::linkUniforms(gl::InfoLog &infoLog, - const gl::Caps &caps, - const Bindings &uniformBindings) +bool Program::validateVertexAndFragmentUniforms(InfoLog &infoLog) const { + // Check that uniforms defined in the vertex and fragment shaders are identical + std::map linkedUniforms; const std::vector &vertexUniforms = mState.mAttachedVertexShader->getUniforms(); const std::vector &fragmentUniforms = mState.mAttachedFragmentShader->getUniforms(); - // Check that uniforms defined in the vertex and fragment shaders are identical - std::map linkedUniforms; - for (const sh::Uniform &vertexUniform : vertexUniforms) { linkedUniforms[vertexUniform.name] = LinkedUniform(vertexUniform); @@ -1801,6 +1918,21 @@ bool Program::linkUniforms(gl::InfoLog &infoLog, } } } + return true; +} + +bool Program::linkUniforms(gl::InfoLog &infoLog, + const gl::Caps &caps, + const Bindings &uniformBindings) +{ + if (mState.mAttachedVertexShader && mState.mAttachedFragmentShader) + { + ASSERT(mState.mAttachedComputeShader == nullptr); + if (!validateVertexAndFragmentUniforms(infoLog)) + { + return false; + } + } // Flatten the uniforms list (nested fields) into a simple list (no nesting). // Also check the maximum uniform vector and sampler counts. @@ -1910,7 +2042,10 @@ bool Program::indexUniforms(gl::InfoLog &infoLog, return true; } -bool Program::linkValidateInterfaceBlockFields(InfoLog &infoLog, const std::string &uniformName, const sh::InterfaceBlockField &vertexUniform, const sh::InterfaceBlockField &fragmentUniform) +bool Program::linkValidateInterfaceBlockFields(InfoLog &infoLog, + const std::string &uniformName, + const sh::InterfaceBlockField &vertexUniform, + const sh::InterfaceBlockField &fragmentUniform) { // We don't validate precision on UBO fields. See resolution of Khronos bug 10287. if (!linkValidateVariablesBase(infoLog, uniformName, vertexUniform, fragmentUniform, false)) @@ -2034,36 +2169,40 @@ bool Program::linkAttributes(const ContextState &data, return true; } -bool Program::linkUniformBlocks(InfoLog &infoLog, const Caps &caps) +bool Program::validateUniformBlocksCount(GLuint maxUniformBlocks, + const std::vector &intefaceBlocks, + const std::string &errorMessage, + InfoLog &infoLog) const { - const Shader &vertexShader = *mState.mAttachedVertexShader; - const Shader &fragmentShader = *mState.mAttachedFragmentShader; - - const std::vector &vertexInterfaceBlocks = vertexShader.getInterfaceBlocks(); - const std::vector &fragmentInterfaceBlocks = fragmentShader.getInterfaceBlocks(); - - // Check that interface blocks defined in the vertex and fragment shaders are identical - typedef std::map UniformBlockMap; - UniformBlockMap linkedUniformBlocks; - - GLuint vertexBlockCount = 0; - for (const sh::InterfaceBlock &vertexInterfaceBlock : vertexInterfaceBlocks) + GLuint blockCount = 0; + for (const sh::InterfaceBlock &block : intefaceBlocks) { - linkedUniformBlocks[vertexInterfaceBlock.name] = &vertexInterfaceBlock; - - // Note: shared and std140 layouts are always considered active - if (vertexInterfaceBlock.staticUse || vertexInterfaceBlock.layout != sh::BLOCKLAYOUT_PACKED) + if (block.staticUse || block.layout != sh::BLOCKLAYOUT_PACKED) { - if (++vertexBlockCount > caps.maxVertexUniformBlocks) + if (++blockCount > maxUniformBlocks) { - infoLog << "Vertex shader uniform block count exceed GL_MAX_VERTEX_UNIFORM_BLOCKS (" - << caps.maxVertexUniformBlocks << ")"; + infoLog << errorMessage << maxUniformBlocks << ")"; return false; } } } + return true; +} + +bool Program::validateVertexAndFragmentInterfaceBlocks( + const std::vector &vertexInterfaceBlocks, + const std::vector &fragmentInterfaceBlocks, + InfoLog &infoLog) const +{ + // Check that interface blocks defined in the vertex and fragment shaders are identical + typedef std::map UniformBlockMap; + UniformBlockMap linkedUniformBlocks; + + for (const sh::InterfaceBlock &vertexInterfaceBlock : vertexInterfaceBlocks) + { + linkedUniformBlocks[vertexInterfaceBlock.name] = &vertexInterfaceBlock; + } - GLuint fragmentBlockCount = 0; for (const sh::InterfaceBlock &fragmentInterfaceBlock : fragmentInterfaceBlocks) { auto entry = linkedUniformBlocks.find(fragmentInterfaceBlock.name); @@ -2075,26 +2214,59 @@ bool Program::linkUniformBlocks(InfoLog &infoLog, const Caps &caps) return false; } } + } + return true; +} - // Note: shared and std140 layouts are always considered active - if (fragmentInterfaceBlock.staticUse || - fragmentInterfaceBlock.layout != sh::BLOCKLAYOUT_PACKED) +bool Program::linkUniformBlocks(InfoLog &infoLog, const Caps &caps) +{ + if (mState.mAttachedComputeShader) + { + const Shader &computeShader = *mState.mAttachedComputeShader; + const auto &computeInterfaceBlocks = computeShader.getInterfaceBlocks(); + + if (!validateUniformBlocksCount( + caps.maxComputeUniformBlocks, computeInterfaceBlocks, + "Compute shader uniform block count exceeds GL_MAX_COMPUTE_UNIFORM_BLOCKS (", + infoLog)) { - if (++fragmentBlockCount > caps.maxFragmentUniformBlocks) - { - infoLog - << "Fragment shader uniform block count exceed GL_MAX_FRAGMENT_UNIFORM_BLOCKS (" - << caps.maxFragmentUniformBlocks << ")"; - return false; - } + return false; } + return true; + } + + const Shader &vertexShader = *mState.mAttachedVertexShader; + const Shader &fragmentShader = *mState.mAttachedFragmentShader; + + const auto &vertexInterfaceBlocks = vertexShader.getInterfaceBlocks(); + const auto &fragmentInterfaceBlocks = fragmentShader.getInterfaceBlocks(); + + if (!validateUniformBlocksCount( + caps.maxVertexUniformBlocks, vertexInterfaceBlocks, + "Vertex shader uniform block count exceeds GL_MAX_VERTEX_UNIFORM_BLOCKS (", infoLog)) + { + return false; + } + if (!validateUniformBlocksCount( + caps.maxFragmentUniformBlocks, fragmentInterfaceBlocks, + "Fragment shader uniform block count exceeds GL_MAX_FRAGMENT_UNIFORM_BLOCKS (", + infoLog)) + { + + return false; + } + if (!validateVertexAndFragmentInterfaceBlocks(vertexInterfaceBlocks, fragmentInterfaceBlocks, + infoLog)) + { + return false; } return true; } -bool Program::areMatchingInterfaceBlocks(gl::InfoLog &infoLog, const sh::InterfaceBlock &vertexInterfaceBlock, - const sh::InterfaceBlock &fragmentInterfaceBlock) +bool Program::areMatchingInterfaceBlocks(gl::InfoLog &infoLog, + const sh::InterfaceBlock &vertexInterfaceBlock, + const sh::InterfaceBlock &fragmentInterfaceBlock) const { const char* blockName = vertexInterfaceBlock.name.c_str(); // validate blocks for the same member types @@ -2385,58 +2557,79 @@ void Program::linkOutputVariables() } } +bool Program::flattenUniformsAndCheckCapsForShader(const gl::Shader &shader, + GLuint maxUniformComponents, + GLuint maxTextureImageUnits, + const std::string &componentsErrorMessage, + const std::string &samplerErrorMessage, + std::vector &samplerUniforms, + InfoLog &infoLog) +{ + VectorAndSamplerCount vasCount; + for (const sh::Uniform &uniform : shader.getUniforms()) + { + if (uniform.staticUse) + { + vasCount += flattenUniform(uniform, uniform.name, &samplerUniforms); + } + } + + if (vasCount.vectorCount > maxUniformComponents) + { + infoLog << componentsErrorMessage << maxUniformComponents << ")."; + return false; + } + + if (vasCount.samplerCount > maxTextureImageUnits) + { + infoLog << samplerErrorMessage << maxTextureImageUnits << ")."; + return false; + } + + return true; +} + bool Program::flattenUniformsAndCheckCaps(const Caps &caps, InfoLog &infoLog) { - const gl::Shader *vertexShader = mState.getAttachedVertexShader(); - VectorAndSamplerCount vsCounts; - std::vector samplerUniforms; - for (const sh::Uniform &uniform : vertexShader->getUniforms()) + if (mState.mAttachedComputeShader) { - if (uniform.staticUse) + const gl::Shader *computeShader = mState.getAttachedComputeShader(); + + // TODO (mradev): check whether we need finer-grained component counting + if (!flattenUniformsAndCheckCapsForShader( + *computeShader, caps.maxComputeUniformComponents / 4, + caps.maxComputeTextureImageUnits, + "Compute shader active uniforms exceed MAX_COMPUTE_UNIFORM_COMPONENTS (", + "Compute shader sampler count exceeds MAX_COMPUTE_TEXTURE_IMAGE_UNITS (", + samplerUniforms, infoLog)) { - vsCounts += flattenUniform(uniform, uniform.name, &samplerUniforms); + return false; } } - - if (vsCounts.vectorCount > caps.maxVertexUniformVectors) + else { - infoLog << "Vertex shader active uniforms exceed MAX_VERTEX_UNIFORM_VECTORS (" - << caps.maxVertexUniformVectors << ")."; - return false; - } + const gl::Shader *vertexShader = mState.getAttachedVertexShader(); - if (vsCounts.samplerCount > caps.maxVertexTextureImageUnits) - { - infoLog << "Vertex shader sampler count exceeds MAX_VERTEX_TEXTURE_IMAGE_UNITS (" - << caps.maxVertexTextureImageUnits << ")."; - return false; - } - - const gl::Shader *fragmentShader = mState.getAttachedFragmentShader(); - VectorAndSamplerCount fsCounts; - - for (const sh::Uniform &uniform : fragmentShader->getUniforms()) - { - if (uniform.staticUse) + if (!flattenUniformsAndCheckCapsForShader( + *vertexShader, caps.maxVertexUniformVectors, caps.maxVertexTextureImageUnits, + "Vertex shader active uniforms exceed MAX_VERTEX_UNIFORM_VECTORS (", + "Vertex shader sampler count exceeds MAX_VERTEX_TEXTURE_IMAGE_UNITS (", + samplerUniforms, infoLog)) { - fsCounts += flattenUniform(uniform, uniform.name, &samplerUniforms); + return false; } - } + const gl::Shader *fragmentShader = mState.getAttachedFragmentShader(); - if (fsCounts.vectorCount > caps.maxFragmentUniformVectors) - { - infoLog << "Fragment shader active uniforms exceed MAX_FRAGMENT_UNIFORM_VECTORS (" - << caps.maxFragmentUniformVectors << ")."; - return false; - } - - if (fsCounts.samplerCount > caps.maxTextureImageUnits) - { - infoLog << "Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (" - << caps.maxTextureImageUnits << ")."; - return false; + if (!flattenUniformsAndCheckCapsForShader( + *fragmentShader, caps.maxFragmentUniformVectors, caps.maxTextureImageUnits, + "Fragment shader active uniforms exceed MAX_FRAGMENT_UNIFORM_VECTORS (", + "Fragment shader sampler count exceeds MAX_TEXTURE_IMAGE_UNITS (", samplerUniforms, + infoLog)) + { + return false; + } } mSamplerUniformRange.start = static_cast(mState.mUniforms.size()); @@ -2506,14 +2699,39 @@ Program::VectorAndSamplerCount Program::flattenUniform(const sh::ShaderVariable void Program::gatherInterfaceBlockInfo() { + ASSERT(mState.mUniformBlocks.empty()); + + if (mState.mAttachedComputeShader) + { + const gl::Shader *computeShader = mState.getAttachedComputeShader(); + + for (const sh::InterfaceBlock &computeBlock : computeShader->getInterfaceBlocks()) + { + + // Only 'packed' blocks are allowed to be considered inactive. + if (!computeBlock.staticUse && computeBlock.layout == sh::BLOCKLAYOUT_PACKED) + continue; + + for (gl::UniformBlock &block : mState.mUniformBlocks) + { + if (block.name == computeBlock.name) + { + block.computeStaticUse = computeBlock.staticUse; + } + } + + defineUniformBlock(computeBlock, GL_COMPUTE_SHADER); + } + return; + } + std::set visitedList; const gl::Shader *vertexShader = mState.getAttachedVertexShader(); - ASSERT(mState.mUniformBlocks.empty()); for (const sh::InterfaceBlock &vertexBlock : vertexShader->getInterfaceBlocks()) { - // Only 'packed' blocks are allowed to be considered inacive. + // Only 'packed' blocks are allowed to be considered inactive. if (!vertexBlock.staticUse && vertexBlock.layout == sh::BLOCKLAYOUT_PACKED) continue; @@ -2528,7 +2746,7 @@ void Program::gatherInterfaceBlockInfo() for (const sh::InterfaceBlock &fragmentBlock : fragmentShader->getInterfaceBlocks()) { - // Only 'packed' blocks are allowed to be considered inacive. + // Only 'packed' blocks are allowed to be considered inactive. if (!fragmentBlock.staticUse && fragmentBlock.layout == sh::BLOCKLAYOUT_PACKED) continue; @@ -2618,14 +2836,25 @@ void Program::defineUniformBlock(const sh::InterfaceBlock &interfaceBlock, GLenu UniformBlock block(interfaceBlock.name, true, arrayElement); block.memberUniformIndexes = blockUniformIndexes; - if (shaderType == GL_VERTEX_SHADER) + switch (shaderType) { - block.vertexStaticUse = interfaceBlock.staticUse; - } - else - { - ASSERT(shaderType == GL_FRAGMENT_SHADER); - block.fragmentStaticUse = interfaceBlock.staticUse; + case GL_VERTEX_SHADER: + { + block.vertexStaticUse = interfaceBlock.staticUse; + break; + } + case GL_FRAGMENT_SHADER: + { + block.fragmentStaticUse = interfaceBlock.staticUse; + break; + } + case GL_COMPUTE_SHADER: + { + block.computeStaticUse = interfaceBlock.staticUse; + break; + } + default: + UNREACHABLE(); } // TODO(jmadill): Determine if we can ever have an inactive array element block. @@ -2645,14 +2874,25 @@ void Program::defineUniformBlock(const sh::InterfaceBlock &interfaceBlock, GLenu UniformBlock block(interfaceBlock.name, false, 0); block.memberUniformIndexes = blockUniformIndexes; - if (shaderType == GL_VERTEX_SHADER) + switch (shaderType) { - block.vertexStaticUse = interfaceBlock.staticUse; - } - else - { - ASSERT(shaderType == GL_FRAGMENT_SHADER); - block.fragmentStaticUse = interfaceBlock.staticUse; + case GL_VERTEX_SHADER: + { + block.vertexStaticUse = interfaceBlock.staticUse; + break; + } + case GL_FRAGMENT_SHADER: + { + block.fragmentStaticUse = interfaceBlock.staticUse; + break; + } + case GL_COMPUTE_SHADER: + { + block.computeStaticUse = interfaceBlock.staticUse; + break; + } + default: + UNREACHABLE(); } block.dataSize = static_cast(blockSize); diff --git a/src/libANGLE/Program.h b/src/libANGLE/Program.h index 9d499c1a3..03212a5cd 100644 --- a/src/libANGLE/Program.h +++ b/src/libANGLE/Program.h @@ -165,6 +165,7 @@ class ProgramState final : angle::NonCopyable const Shader *getAttachedVertexShader() const { return mAttachedVertexShader; } const Shader *getAttachedFragmentShader() const { return mAttachedFragmentShader; } + const Shader *getAttachedComputeShader() const { return mAttachedComputeShader; } const std::vector &getTransformFeedbackVaryingNames() const { return mTransformFeedbackVaryingNames; @@ -198,8 +199,11 @@ class ProgramState final : angle::NonCopyable std::string mLabel; + sh::WorkGroupSize mComputeShaderLocalSize; + Shader *mAttachedFragmentShader; Shader *mAttachedVertexShader; + Shader *mAttachedComputeShader; std::vector mTransformFeedbackVaryingNames; std::vector mTransformFeedbackVaryingVars; @@ -377,12 +381,22 @@ class Program final : angle::NonCopyable, public LabeledObject InfoLog &infoLog, const Bindings &attributeBindings, const Shader *vertexShader); + bool validateUniformBlocksCount(GLuint maxUniformBlocks, + const std::vector &block, + const std::string &errorMessage, + InfoLog &infoLog) const; + bool validateVertexAndFragmentInterfaceBlocks( + const std::vector &vertexInterfaceBlocks, + const std::vector &fragmentInterfaceBlocks, + InfoLog &infoLog) const; bool linkUniformBlocks(InfoLog &infoLog, const Caps &caps); bool linkVaryings(InfoLog &infoLog, const Shader *vertexShader, const Shader *fragmentShader) const; + bool validateVertexAndFragmentUniforms(InfoLog &infoLog) const; bool linkUniforms(gl::InfoLog &infoLog, const gl::Caps &caps, const Bindings &uniformBindings); bool indexUniforms(gl::InfoLog &infoLog, const gl::Caps &caps, const Bindings &uniformBindings); - bool areMatchingInterfaceBlocks(gl::InfoLog &infoLog, const sh::InterfaceBlock &vertexInterfaceBlock, - const sh::InterfaceBlock &fragmentInterfaceBlock); + bool areMatchingInterfaceBlocks(gl::InfoLog &infoLog, + const sh::InterfaceBlock &vertexInterfaceBlock, + const sh::InterfaceBlock &fragmentInterfaceBlock) const; static bool linkValidateVariablesBase(InfoLog &infoLog, const std::string &variableName, @@ -406,6 +420,13 @@ class Program final : angle::NonCopyable, public LabeledObject std::vector getMergedVaryings() const; void linkOutputVariables(); + bool flattenUniformsAndCheckCapsForShader(const gl::Shader &shader, + GLuint maxUniformComponents, + GLuint maxTextureImageUnits, + const std::string &componentsErrorMessage, + const std::string &samplerErrorMessage, + std::vector &samplerUniforms, + InfoLog &infoLog); bool flattenUniformsAndCheckCaps(const Caps &caps, InfoLog &infoLog); struct VectorAndSamplerCount diff --git a/src/libANGLE/ResourceManager.cpp b/src/libANGLE/ResourceManager.cpp index 7f963874a..3fdfe47d9 100644 --- a/src/libANGLE/ResourceManager.cpp +++ b/src/libANGLE/ResourceManager.cpp @@ -97,13 +97,10 @@ GLuint ResourceManager::createShader(rx::GLImplFactory *factory, const gl::Limitations &rendererLimitations, GLenum type) { + ASSERT(type == GL_VERTEX_SHADER || type == GL_FRAGMENT_SHADER || type == GL_COMPUTE_SHADER); GLuint handle = mProgramShaderHandleAllocator.allocate(); - if (type == GL_VERTEX_SHADER || type == GL_FRAGMENT_SHADER) - { - mShaderMap[handle] = new Shader(this, factory, rendererLimitations, type, handle); - } - else UNREACHABLE(); + mShaderMap[handle] = new Shader(this, factory, rendererLimitations, type, handle); return handle; } diff --git a/src/libANGLE/Shader.cpp b/src/libANGLE/Shader.cpp index bf0f742cb..904345d58 100644 --- a/src/libANGLE/Shader.cpp +++ b/src/libANGLE/Shader.cpp @@ -75,6 +75,7 @@ bool CompareShaderVar(const sh::ShaderVariable &x, const sh::ShaderVariable &y) ShaderState::ShaderState(GLenum shaderType) : mLabel(), mShaderType(shaderType), mShaderVersion(100) { + mLocalSize.fill(-1); } ShaderState::~ShaderState() @@ -306,18 +307,28 @@ void Shader::compile(Compiler *compiler) mState.mUniforms = GetShaderVariables(ShGetUniforms(compilerHandle)); mState.mInterfaceBlocks = GetShaderVariables(ShGetInterfaceBlocks(compilerHandle)); - if (mState.mShaderType == GL_VERTEX_SHADER) + switch (mState.mShaderType) { - mState.mActiveAttributes = GetActiveShaderVariables(ShGetAttributes(compilerHandle)); - } - else - { - ASSERT(mState.mShaderType == GL_FRAGMENT_SHADER); - - // TODO(jmadill): Figure out why we only sort in the FS, and if we need to. - std::sort(mState.mVaryings.begin(), mState.mVaryings.end(), CompareShaderVar); - mState.mActiveOutputVariables = - GetActiveShaderVariables(ShGetOutputVariables(compilerHandle)); + case GL_COMPUTE_SHADER: + { + mState.mLocalSize = ShGetComputeShaderLocalGroupSize(compilerHandle); + break; + } + case GL_VERTEX_SHADER: + { + mState.mActiveAttributes = GetActiveShaderVariables(ShGetAttributes(compilerHandle)); + break; + } + case GL_FRAGMENT_SHADER: + { + // TODO(jmadill): Figure out why we only sort in the FS, and if we need to. + std::sort(mState.mVaryings.begin(), mState.mVaryings.end(), CompareShaderVar); + mState.mActiveOutputVariables = + GetActiveShaderVariables(ShGetOutputVariables(compilerHandle)); + break; + } + default: + UNREACHABLE(); } ASSERT(!mState.mTranslatedSource.empty()); diff --git a/src/libANGLE/Shader.h b/src/libANGLE/Shader.h index 237a91544..d73400a80 100644 --- a/src/libANGLE/Shader.h +++ b/src/libANGLE/Shader.h @@ -70,6 +70,8 @@ class ShaderState final : angle::NonCopyable std::string mTranslatedSource; std::string mSource; + sh::WorkGroupSize mLocalSize; + std::vector mVaryings; std::vector mUniforms; std::vector mInterfaceBlocks; @@ -127,6 +129,8 @@ class Shader final : angle::NonCopyable, public LabeledObject int getSemanticIndex(const std::string &attributeName) const; + const sh::WorkGroupSize &getWorkGroupSize() const { return mState.mLocalSize; } + private: static void getSourceImpl(const std::string &source, GLsizei bufSize, GLsizei *length, char *buffer); diff --git a/src/libANGLE/Uniform.cpp b/src/libANGLE/Uniform.cpp index bfae3c014..9b4b47aa2 100644 --- a/src/libANGLE/Uniform.cpp +++ b/src/libANGLE/Uniform.cpp @@ -121,7 +121,12 @@ const uint8_t *LinkedUniform::getDataPtrToElement(size_t elementIndex) const } UniformBlock::UniformBlock() - : isArray(false), arrayElement(0), dataSize(0), vertexStaticUse(false), fragmentStaticUse(false) + : isArray(false), + arrayElement(0), + dataSize(0), + vertexStaticUse(false), + fragmentStaticUse(false), + computeStaticUse(false) { } @@ -131,7 +136,8 @@ UniformBlock::UniformBlock(const std::string &nameIn, bool isArrayIn, unsigned i arrayElement(arrayElementIn), dataSize(0), vertexStaticUse(false), - fragmentStaticUse(false) + fragmentStaticUse(false), + computeStaticUse(false) { } diff --git a/src/libANGLE/Uniform.h b/src/libANGLE/Uniform.h index e62a583f3..4d7d24e3b 100644 --- a/src/libANGLE/Uniform.h +++ b/src/libANGLE/Uniform.h @@ -63,6 +63,7 @@ struct UniformBlock bool vertexStaticUse; bool fragmentStaticUse; + bool computeStaticUse; std::vector memberUniformIndexes; }; diff --git a/src/libANGLE/renderer/gl/ProgramGL.cpp b/src/libANGLE/renderer/gl/ProgramGL.cpp index 5a0e1ba0d..2c2c4e7ad 100644 --- a/src/libANGLE/renderer/gl/ProgramGL.cpp +++ b/src/libANGLE/renderer/gl/ProgramGL.cpp @@ -101,54 +101,69 @@ LinkResult ProgramGL::link(const gl::ContextState &data, gl::InfoLog &infoLog) { preLink(); - // Set the transform feedback state - std::vector transformFeedbackVaryings; - for (const auto &tfVarying : mState.getTransformFeedbackVaryingNames()) + if (mState.getAttachedComputeShader()) { - transformFeedbackVaryings.push_back(tfVarying.c_str()); - } + const ShaderGL *computeShaderGL = GetImplAs(mState.getAttachedComputeShader()); - if (transformFeedbackVaryings.empty()) - { - if (mFunctions->transformFeedbackVaryings) - { - mFunctions->transformFeedbackVaryings(mProgramID, 0, nullptr, - mState.getTransformFeedbackBufferMode()); - } + mFunctions->attachShader(mProgramID, computeShaderGL->getShaderID()); + + // Link and verify + mFunctions->linkProgram(mProgramID); + + // Detach the shaders + mFunctions->detachShader(mProgramID, computeShaderGL->getShaderID()); } else { - ASSERT(mFunctions->transformFeedbackVaryings); - mFunctions->transformFeedbackVaryings( - mProgramID, static_cast(transformFeedbackVaryings.size()), - &transformFeedbackVaryings[0], mState.getTransformFeedbackBufferMode()); - } - - const ShaderGL *vertexShaderGL = GetImplAs(mState.getAttachedVertexShader()); - const ShaderGL *fragmentShaderGL = GetImplAs(mState.getAttachedFragmentShader()); - - // Attach the shaders - mFunctions->attachShader(mProgramID, vertexShaderGL->getShaderID()); - mFunctions->attachShader(mProgramID, fragmentShaderGL->getShaderID()); - - // Bind attribute locations to match the GL layer. - for (const sh::Attribute &attribute : mState.getAttributes()) - { - if (!attribute.staticUse) + // Set the transform feedback state + std::vector transformFeedbackVaryings; + for (const auto &tfVarying : mState.getTransformFeedbackVaryingNames()) { - continue; + transformFeedbackVaryings.push_back(tfVarying.c_str()); } - mFunctions->bindAttribLocation(mProgramID, attribute.location, attribute.name.c_str()); + if (transformFeedbackVaryings.empty()) + { + if (mFunctions->transformFeedbackVaryings) + { + mFunctions->transformFeedbackVaryings(mProgramID, 0, nullptr, + mState.getTransformFeedbackBufferMode()); + } + } + else + { + ASSERT(mFunctions->transformFeedbackVaryings); + mFunctions->transformFeedbackVaryings( + mProgramID, static_cast(transformFeedbackVaryings.size()), + &transformFeedbackVaryings[0], mState.getTransformFeedbackBufferMode()); + } + + const ShaderGL *vertexShaderGL = GetImplAs(mState.getAttachedVertexShader()); + const ShaderGL *fragmentShaderGL = GetImplAs(mState.getAttachedFragmentShader()); + + // Attach the shaders + mFunctions->attachShader(mProgramID, vertexShaderGL->getShaderID()); + mFunctions->attachShader(mProgramID, fragmentShaderGL->getShaderID()); + + // Bind attribute locations to match the GL layer. + for (const sh::Attribute &attribute : mState.getAttributes()) + { + if (!attribute.staticUse) + { + continue; + } + + mFunctions->bindAttribLocation(mProgramID, attribute.location, attribute.name.c_str()); + } + + // Link and verify + mFunctions->linkProgram(mProgramID); + + // Detach the shaders + mFunctions->detachShader(mProgramID, vertexShaderGL->getShaderID()); + mFunctions->detachShader(mProgramID, fragmentShaderGL->getShaderID()); } - // Link and verify - mFunctions->linkProgram(mProgramID); - - // Detach the shaders - mFunctions->detachShader(mProgramID, vertexShaderGL->getShaderID()); - mFunctions->detachShader(mProgramID, fragmentShaderGL->getShaderID()); - // Verify the link if (!checkLinkStatus(infoLog)) { diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp index 0d2fe1457..609385f98 100644 --- a/src/libANGLE/validationES2.cpp +++ b/src/libANGLE/validationES2.cpp @@ -3309,4 +3309,23 @@ bool ValidateCopySubTextureCHROMIUM(Context *context, return true; } +bool ValidateCreateShader(Context *context, GLenum type) +{ + switch (type) + { + case GL_VERTEX_SHADER: + case GL_FRAGMENT_SHADER: + break; + case GL_COMPUTE_SHADER: + if (context->getGLVersion().isES31()) + { + break; + } + default: + context->handleError(Error(GL_INVALID_ENUM)); + return false; + } + return true; +} + } // namespace gl diff --git a/src/libANGLE/validationES2.h b/src/libANGLE/validationES2.h index 101b12ed4..09451f4c2 100644 --- a/src/libANGLE/validationES2.h +++ b/src/libANGLE/validationES2.h @@ -305,6 +305,8 @@ bool ValidateCopySubTextureCHROMIUM(Context *context, GLboolean unpackPremultiplyAlpha, GLboolean unpackUnmultiplyAlpha); +bool ValidateCreateShader(Context *context, GLenum type); + } // namespace gl #endif // LIBANGLE_VALIDATION_ES2_H_ diff --git a/src/libGLESv2/entry_points_gles_2_0.cpp b/src/libGLESv2/entry_points_gles_2_0.cpp index 87cc5e4c1..c3e9ad875 100644 --- a/src/libGLESv2/entry_points_gles_2_0.cpp +++ b/src/libGLESv2/entry_points_gles_2_0.cpp @@ -748,18 +748,13 @@ GLuint GL_APIENTRY CreateShader(GLenum type) Context *context = GetValidGlobalContext(); if (context) { - switch (type) - { - case GL_FRAGMENT_SHADER: - case GL_VERTEX_SHADER: - return context->createShader(type); - default: - context->handleError(Error(GL_INVALID_ENUM)); + if (!context->skipValidation() && !ValidateCreateShader(context, type)) + { return 0; } + return context->createShader(type); } - return 0; } diff --git a/src/tests/angle_end2end_tests.gypi b/src/tests/angle_end2end_tests.gypi index e6d447139..9941e9606 100644 --- a/src/tests/angle_end2end_tests.gypi +++ b/src/tests/angle_end2end_tests.gypi @@ -22,6 +22,7 @@ '<(angle_path)/src/tests/gl_tests/BuiltinVariableTest.cpp', '<(angle_path)/src/tests/gl_tests/ClearTest.cpp', '<(angle_path)/src/tests/gl_tests/ColorMaskTest.cpp', + '<(angle_path)/src/tests/gl_tests/ComputeShaderTest.cpp', '<(angle_path)/src/tests/gl_tests/CopyTexImageTest.cpp', '<(angle_path)/src/tests/gl_tests/CopyTextureTest.cpp', '<(angle_path)/src/tests/gl_tests/CubeMapTextureTest.cpp', diff --git a/src/tests/compiler_tests/WorkGroupSize_test.cpp b/src/tests/compiler_tests/WorkGroupSize_test.cpp index a574b7cd4..f2a9d4e8d 100644 --- a/src/tests/compiler_tests/WorkGroupSize_test.cpp +++ b/src/tests/compiler_tests/WorkGroupSize_test.cpp @@ -56,7 +56,7 @@ TEST_F(WorkGroupSizeTest, OnlyLocalSizeXSpecified) compile(shaderString); - const TLocalSize &localSize = mTranslator->getComputeShaderLocalSize(); + const sh::WorkGroupSize &localSize = mTranslator->getComputeShaderLocalSize(); ASSERT_EQ(5, localSize[0]); ASSERT_EQ(1, localSize[1]); ASSERT_EQ(1, localSize[2]); @@ -73,7 +73,7 @@ TEST_F(WorkGroupSizeTest, LocalSizeXandZ) compile(shaderString); - const TLocalSize &localSize = mTranslator->getComputeShaderLocalSize(); + const sh::WorkGroupSize &localSize = mTranslator->getComputeShaderLocalSize(); ASSERT_EQ(5, localSize[0]); ASSERT_EQ(1, localSize[1]); ASSERT_EQ(10, localSize[2]); @@ -90,7 +90,7 @@ TEST_F(WorkGroupSizeTest, LocalSizeAll) compile(shaderString); - const TLocalSize &localSize = mTranslator->getComputeShaderLocalSize(); + const sh::WorkGroupSize &localSize = mTranslator->getComputeShaderLocalSize(); ASSERT_EQ(5, localSize[0]); ASSERT_EQ(15, localSize[1]); ASSERT_EQ(10, localSize[2]); diff --git a/src/tests/gl_tests/ComputeShaderTest.cpp b/src/tests/gl_tests/ComputeShaderTest.cpp new file mode 100644 index 000000000..375741deb --- /dev/null +++ b/src/tests/gl_tests/ComputeShaderTest.cpp @@ -0,0 +1,205 @@ +// +// Copyright 2016 The ANGLE Project Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// ComputeShaderTest: +// Compute shader specific tests. + +#include "test_utils/ANGLETest.h" +#include "test_utils/gl_raii.h" +#include + +using namespace angle; + +namespace +{ + +class ComputeShaderTest : public ANGLETest +{ + protected: + ComputeShaderTest() {} +}; + +class ComputeShaderTestES3 : public ANGLETest +{ + protected: + ComputeShaderTestES3() {} +}; + +// link a simple compute program. It should be successful. +TEST_P(ComputeShaderTest, LinkComputeProgram) +{ + const std::string csSource = + "#version 310 es\n" + "layout(local_size_x=1) in;\n" + "void main()\n" + "{\n" + "}\n"; + + ANGLE_GL_COMPUTE_PROGRAM(program, csSource); + + EXPECT_GL_NO_ERROR(); +} + +// link a simple compute program. There is no local size and linking should fail. +TEST_P(ComputeShaderTest, LinkComputeProgramNoLocalSizeLinkError) +{ + const std::string csSource = + "#version 310 es\n" + "void main()\n" + "{\n" + "}\n"; + + GLuint program = CompileComputeProgram(csSource, false); + EXPECT_EQ(0u, program); + + glDeleteProgram(program); + + EXPECT_GL_NO_ERROR(); +} + +// link a simple compute program. +// make sure that uniforms and uniform samplers get recorded +TEST_P(ComputeShaderTest, LinkComputeProgramWithUniforms) +{ + const std::string csSource = + "#version 310 es\n" + "precision mediump sampler2D;\n" + "layout(local_size_x=1) in;\n" + "uniform int myUniformInt;\n" + "uniform sampler2D myUniformSampler;\n" + "void main()\n" + "{\n" + "int q = myUniformInt;\n" + "texture(myUniformSampler, vec2(0.0));\n" + "}\n"; + + ANGLE_GL_COMPUTE_PROGRAM(program, csSource); + + GLint uniformLoc = glGetUniformLocation(program.get(), "myUniformInt"); + EXPECT_NE(-1, uniformLoc); + + uniformLoc = glGetUniformLocation(program.get(), "myUniformSampler"); + EXPECT_NE(-1, uniformLoc); + + EXPECT_GL_NO_ERROR(); +} + +// Attach both compute and non-compute shaders. A link time error should occur. +// OpenGL ES 3.10, 7.3 Program Objects +TEST_P(ComputeShaderTest, AttachMultipleShaders) +{ + const std::string csSource = + "#version 310 es\n" + "layout(local_size_x=1) in;\n" + "void main()\n" + "{\n" + "}\n"; + + const std::string vsSource = + "#version 310 es\n" + "void main()\n" + "{\n" + "}\n"; + + const std::string fsSource = + "#version 310 es\n" + "void main()\n" + "{\n" + "}\n"; + + GLuint program = glCreateProgram(); + + GLuint vs = CompileShader(GL_VERTEX_SHADER, vsSource); + GLuint fs = CompileShader(GL_FRAGMENT_SHADER, fsSource); + GLuint cs = CompileShader(GL_COMPUTE_SHADER, csSource); + + EXPECT_NE(0u, vs); + EXPECT_NE(0u, fs); + EXPECT_NE(0u, cs); + + glAttachShader(program, vs); + glDeleteShader(vs); + + glAttachShader(program, fs); + glDeleteShader(fs); + + glAttachShader(program, cs); + glDeleteShader(cs); + + glLinkProgram(program); + + GLint linkStatus; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + + EXPECT_EQ(0, linkStatus); + + EXPECT_GL_NO_ERROR(); +} + +// Attach a vertex, fragment and compute shader. +// Query for the number of attached shaders and check the count. +TEST_P(ComputeShaderTest, AttachmentCount) +{ + const std::string csSource = + "#version 310 es\n" + "layout(local_size_x=1) in;\n" + "void main()\n" + "{\n" + "}\n"; + + const std::string vsSource = + "#version 310 es\n" + "void main()\n" + "{\n" + "}\n"; + + const std::string fsSource = + "#version 310 es\n" + "void main()\n" + "{\n" + "}\n"; + + GLuint program = glCreateProgram(); + + GLuint vs = CompileShader(GL_VERTEX_SHADER, vsSource); + GLuint fs = CompileShader(GL_FRAGMENT_SHADER, fsSource); + GLuint cs = CompileShader(GL_COMPUTE_SHADER, csSource); + + EXPECT_NE(0u, vs); + EXPECT_NE(0u, fs); + EXPECT_NE(0u, cs); + + glAttachShader(program, vs); + glDeleteShader(vs); + + glAttachShader(program, fs); + glDeleteShader(fs); + + glAttachShader(program, cs); + glDeleteShader(cs); + + GLint numAttachedShaders; + glGetProgramiv(program, GL_ATTACHED_SHADERS, &numAttachedShaders); + + EXPECT_EQ(3, numAttachedShaders); + + glDeleteProgram(program); + + EXPECT_GL_NO_ERROR(); +} + +// Check that it is not possible to create a compute shader when the context does not support ES +// 3.10 +TEST_P(ComputeShaderTestES3, NotSupported) +{ + GLuint computeShaderHandle = glCreateShader(GL_COMPUTE_SHADER); + EXPECT_EQ(0u, computeShaderHandle); + EXPECT_GL_ERROR(GL_INVALID_ENUM); +} + +ANGLE_INSTANTIATE_TEST(ComputeShaderTest, ES31_OPENGL(), ES31_OPENGLES()); +ANGLE_INSTANTIATE_TEST(ComputeShaderTestES3, ES3_OPENGL(), ES3_OPENGLES()); + +} // namespace diff --git a/src/tests/test_utils/angle_test_configs.cpp b/src/tests/test_utils/angle_test_configs.cpp index 0ed7a74b6..76690ca7b 100644 --- a/src/tests/test_utils/angle_test_configs.cpp +++ b/src/tests/test_utils/angle_test_configs.cpp @@ -555,6 +555,16 @@ PlatformParameters ES3_OPENGLES(EGLint major, EGLint minor) return PlatformParameters(3, 0, egl_platform::OPENGLES(major, minor)); } +PlatformParameters ES31_OPENGLES() +{ + return PlatformParameters(3, 1, egl_platform::OPENGLES()); +} + +PlatformParameters ES31_OPENGLES(EGLint major, EGLint minor) +{ + return PlatformParameters(3, 1, egl_platform::OPENGLES(major, minor)); +} + PlatformParameters ES2_OPENGL() { return PlatformParameters(2, 0, egl_platform::OPENGL()); @@ -575,4 +585,14 @@ PlatformParameters ES3_OPENGL(EGLint major, EGLint minor) return PlatformParameters(3, 0, egl_platform::OPENGL(major, minor)); } +PlatformParameters ES31_OPENGL() +{ + return PlatformParameters(3, 1, egl_platform::OPENGL()); +} + +PlatformParameters ES31_OPENGL(EGLint major, EGLint minor) +{ + return PlatformParameters(3, 1, egl_platform::OPENGL(major, minor)); +} + } // namespace angle diff --git a/src/tests/test_utils/angle_test_configs.h b/src/tests/test_utils/angle_test_configs.h index a40a7a18b..fa595a556 100644 --- a/src/tests/test_utils/angle_test_configs.h +++ b/src/tests/test_utils/angle_test_configs.h @@ -129,11 +129,15 @@ PlatformParameters ES2_OPENGL(); PlatformParameters ES2_OPENGL(EGLint major, EGLint minor); PlatformParameters ES3_OPENGL(); PlatformParameters ES3_OPENGL(EGLint major, EGLint minor); +PlatformParameters ES31_OPENGL(); +PlatformParameters ES31_OPENGL(EGLint major, EGLint minor); PlatformParameters ES2_OPENGLES(); PlatformParameters ES2_OPENGLES(EGLint major, EGLint minor); PlatformParameters ES3_OPENGLES(); PlatformParameters ES3_OPENGLES(EGLint major, EGLint minor); +PlatformParameters ES31_OPENGLES(); +PlatformParameters ES31_OPENGLES(EGLint major, EGLint minor); } // namespace angle diff --git a/src/tests/test_utils/gl_raii.h b/src/tests/test_utils/gl_raii.h index 1c8738950..9adc0cf02 100644 --- a/src/tests/test_utils/gl_raii.h +++ b/src/tests/test_utils/gl_raii.h @@ -55,13 +55,22 @@ class GLProgram { } + GLProgram(const std::string &computeShader) : mHandle(0), mComputeShader(computeShader) {} + ~GLProgram() { glDeleteProgram(mHandle); } GLuint get() { if (mHandle == 0) { - mHandle = CompileProgram(mVertexShader, mFragmentShader); + if (!mComputeShader.empty()) + { + mHandle = CompileComputeProgram(mComputeShader); + } + else + { + mHandle = CompileProgram(mVertexShader, mFragmentShader); + } } return mHandle; } @@ -70,12 +79,17 @@ class GLProgram GLuint mHandle; const std::string mVertexShader; const std::string mFragmentShader; + const std::string mComputeShader; }; #define ANGLE_GL_PROGRAM(name, vertex, fragment) \ GLProgram name(vertex, fragment); \ ASSERT_NE(0u, name.get()); +#define ANGLE_GL_COMPUTE_PROGRAM(name, compute) \ + GLProgram name(compute); \ + ASSERT_NE(0u, name.get()); + } // namespace angle #endif // ANGLE_TESTS_GL_RAII_H_ diff --git a/util/shader_utils.cpp b/util/shader_utils.cpp index 62223b524..f92d71570 100644 --- a/util/shader_utils.cpp +++ b/util/shader_utils.cpp @@ -77,6 +77,40 @@ GLuint CompileShaderFromFile(GLenum type, const std::string &sourcePath) return CompileShader(type, source); } +GLuint CheckLinkStatusAndReturnProgram(GLuint program, bool outputErrorMessages) +{ + GLint linkStatus; + glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); + if (linkStatus == 0) + { + if (outputErrorMessages) + { + GLint infoLogLength; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); + + // Info log length includes the null terminator, so 1 means that the info log is an + // empty string. + if (infoLogLength > 1) + { + std::vector infoLog(infoLogLength); + glGetProgramInfoLog(program, static_cast(infoLog.size()), nullptr, + &infoLog[0]); + + std::cerr << "program link failed: " << &infoLog[0]; + } + else + { + std::cerr << "program link failed. "; + } + } + + glDeleteProgram(program); + return 0; + } + + return program; +} + GLuint CompileProgramWithTransformFeedback( const std::string &vsSource, const std::string &fsSource, @@ -117,33 +151,7 @@ GLuint CompileProgramWithTransformFeedback( glLinkProgram(program); - GLint linkStatus; - glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); - - if (linkStatus == 0) - { - GLint infoLogLength; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); - - // Info log length includes the null terminator, so 1 means that the info log is an empty - // string. - if (infoLogLength > 1) - { - std::vector infoLog(infoLogLength); - glGetProgramInfoLog(program, static_cast(infoLog.size()), nullptr, &infoLog[0]); - - std::cerr << "program link failed: " << &infoLog[0]; - } - else - { - std::cerr << "program link failed. "; - } - - glDeleteProgram(program); - return 0; - } - - return program; + return CheckLinkStatusAndReturnProgram(program, true); } GLuint CompileProgram(const std::string &vsSource, const std::string &fsSource) @@ -163,3 +171,21 @@ GLuint CompileProgramFromFiles(const std::string &vsPath, const std::string &fsP return CompileProgram(vsSource, fsSource); } + +GLuint CompileComputeProgram(const std::string &csSource, bool outputErrorMessages) +{ + GLuint program = glCreateProgram(); + + GLuint cs = CompileShader(GL_COMPUTE_SHADER, csSource); + if (cs == 0) + { + glDeleteProgram(program); + return 0; + } + + glAttachShader(program, cs); + + glLinkProgram(program); + + return CheckLinkStatusAndReturnProgram(program, outputErrorMessages); +} diff --git a/util/shader_utils.h b/util/shader_utils.h index abf32fee6..fde05d5b4 100644 --- a/util/shader_utils.h +++ b/util/shader_utils.h @@ -7,6 +7,7 @@ #ifndef SAMPLE_UTIL_SHADER_UTILS_H #define SAMPLE_UTIL_SHADER_UTILS_H +#include #include #include #include @@ -28,5 +29,5 @@ GLuint CompileProgramWithTransformFeedback( GLenum bufferMode); GLuint CompileProgram(const std::string &vsSource, const std::string &fsSource); GLuint CompileProgramFromFiles(const std::string &vsPath, const std::string &fsPath); - +GLuint CompileComputeProgram(const std::string &csSource, bool outputErrorMessages = true); #endif // SAMPLE_UTIL_SHADER_UTILS_H