From 96f97d4e7a28700b595067820a60b9c3e93281e8 Mon Sep 17 00:00:00 2001 From: Vladislav Oleshko Date: Fri, 8 Jul 2022 22:01:09 +0300 Subject: [PATCH] GL: implement APIs for async shader compilation & linking. --- src/Magnum/GL/AbstractShaderProgram.cpp | 88 +++++++------ src/Magnum/GL/AbstractShaderProgram.h | 53 ++++++-- .../GL/Implementation/ShaderProgramState.cpp | 7 + .../GL/Implementation/ShaderProgramState.h | 3 + src/Magnum/GL/Implementation/ShaderState.cpp | 12 +- src/Magnum/GL/Implementation/ShaderState.h | 3 + src/Magnum/GL/Shader.cpp | 113 ++++++++-------- src/Magnum/GL/Shader.h | 42 +++++- .../GL/Test/AbstractShaderProgramGLTest.cpp | 123 ++++++++++++++++++ src/Magnum/GL/Test/ShaderGLTest.cpp | 88 ++++++++++++- 10 files changed, 410 insertions(+), 122 deletions(-) diff --git a/src/Magnum/GL/AbstractShaderProgram.cpp b/src/Magnum/GL/AbstractShaderProgram.cpp index aab585448..c551d876b 100644 --- a/src/Magnum/GL/AbstractShaderProgram.cpp +++ b/src/Magnum/GL/AbstractShaderProgram.cpp @@ -585,53 +585,55 @@ void AbstractShaderProgram::transformFeedbackVaryingsImplementationDanglingWorka bool AbstractShaderProgram::link() { return link({*this}); } -bool AbstractShaderProgram::link(std::initializer_list> shaders) { - bool allSuccess = true; +void AbstractShaderProgram::submitLink() { + glLinkProgram(_id); +} + +bool AbstractShaderProgram::checkLink() { + GLint success, logLength; + glGetProgramiv(_id, GL_LINK_STATUS, &success); + glGetProgramiv(_id, GL_INFO_LOG_LENGTH, &logLength); - /* Invoke (possibly parallel) linking on all shaders */ - for(AbstractShaderProgram& shader: shaders) glLinkProgram(shader._id); - - /* After linking phase, check status of all shaders */ - Int i = 1; - for(AbstractShaderProgram& shader: shaders) { - GLint success, logLength; - glGetProgramiv(shader._id, GL_LINK_STATUS, &success); - glGetProgramiv(shader._id, GL_INFO_LOG_LENGTH, &logLength); - - /* Error or warning message. The string is returned null-terminated, - strip the \0 at the end afterwards. */ - std::string message(logLength, '\n'); - if(message.size() > 1) - glGetProgramInfoLog(shader._id, message.size(), nullptr, &message[0]); - message.resize(Math::max(logLength, 1)-1); - - /* Some drivers are chatty and can't keep shut when there's nothing to - be said, handle that as well. */ - Context::current().state().shaderProgram.cleanLogImplementation(message); - - /* Show error log */ - if(!success) { - Error out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::AbstractShaderProgram::link(): linking"; - if(shaders.size() != 1) out << "of shader" << i; - out << "failed with the following message:" << Debug::newline << message; - - /* Or just warnings, if any */ - } else if(!message.empty()) { - Warning out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::AbstractShaderProgram::link(): linking"; - if(shaders.size() != 1) out << "of shader" << i; - out << "succeeded with the following message:" << Debug::newline << message; - } - - /* Success of all depends on each of them */ - allSuccess = allSuccess && success; - ++i; + /* Error or warning message. The string is returned null-terminated, + strip the \0 at the end afterwards. */ + std::string message(logLength, '\n'); + if(message.size() > 1) + glGetProgramInfoLog(_id, message.size(), nullptr, &message[0]); + message.resize(Math::max(logLength, 1)-1); + + /* Some drivers are chatty and can't keep shut when there's nothing to + be said, handle that as well. */ + Context::current().state().shaderProgram.cleanLogImplementation(message); + + /* Show error log */ + if(!success) { + Error out{Debug::Flag::NoNewlineAtTheEnd}; + out << "GL::AbstractShaderProgram::link(): linking failed with the following message:" + << Debug::newline << message; + + /* Or just warnings, if any */ + } else if(!message.empty()) { + Warning out{Debug::Flag::NoNewlineAtTheEnd}; + out << "GL::AbstractShaderProgram::link(): linking succeeded with the following message:" + << Debug::newline << message; } + return success; +} + +bool AbstractShaderProgram::link(std::initializer_list> shaders) { + for(AbstractShaderProgram& shader: shaders) shader.submitLink(); + bool allSuccess = true; + for(AbstractShaderProgram& shader: shaders) allSuccess = allSuccess && shader.checkLink(); return allSuccess; } +bool AbstractShaderProgram::isLinkFinished() { + GLint success; + Context::current().state().shaderProgram.completionStatusImplementation(_id, GL_COMPLETION_STATUS_KHR, &success); + return success == GL_TRUE; +} + void AbstractShaderProgram::cleanLogImplementationNoOp(std::string&) {} #if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES) @@ -646,6 +648,10 @@ void AbstractShaderProgram::cleanLogImplementationAngle(std::string& message) { } #endif +void AbstractShaderProgram::completionStatusImplementationFallback(GLuint, GLenum, GLint* value) { + *value = GL_TRUE; +} + Int AbstractShaderProgram::uniformLocationInternal(const Containers::ArrayView name) { const GLint location = glGetUniformLocation(_id, name); if(location == -1) diff --git a/src/Magnum/GL/AbstractShaderProgram.h b/src/Magnum/GL/AbstractShaderProgram.h index 01b4429ab..a7ad8c5e1 100644 --- a/src/Magnum/GL/AbstractShaderProgram.h +++ b/src/Magnum/GL/AbstractShaderProgram.h @@ -1258,19 +1258,26 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { AbstractShaderProgram& dispatchCompute(const Vector3ui& workgroupCount); #endif - protected: /** - * @brief Link the shader + * @brief Non-blocking linking status check + * @return @cpp true @ce if linking finished, @cpp false @ce otherwise + * + * On some drivers this might return false even after + * @ref checkLink() reported successful linking. * + * @see @fn_gl_keyword{GetProgram} with + * @def_gl_extension{COMPLETION_STATUS,KHR,parallel_shader_compile} + */ + bool isLinkFinished(); + + protected: + /** @brief Link the shaders + * + * Calls @ref submitLink() on all shaders first, then @ref checkLink(). * Returns @cpp false @ce if linking of any shader failed, @cpp true @ce - * if everything succeeded. Linker message (if any) is printed to error - * output. All attached shaders must be compiled with - * @ref Shader::compile() before linking. The operation is batched in a + * if everything succeeded. The operation is batched in a * way that allows the driver to link multiple shaders simultaneously * (i.e. in multiple threads). - * @see @fn_gl_keyword{LinkProgram}, @fn_gl_keyword{GetProgram} with - * @def_gl{LINK_STATUS} and @def_gl{INFO_LOG_LENGTH}, - * @fn_gl_keyword{GetProgramInfoLog} */ static bool link(std::initializer_list> shaders); @@ -1446,13 +1453,37 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { /** * @brief Link the shader * - * Links single shader. If possible, prefer to link multiple shaders - * at once using @ref link(std::initializer_list>) + * Calls @ref submitLink(), then @ref checkLink(). + * If possible, prefer to link multiple shaders at once using + * @ref link(std::initializer_list>) * for improved performance, see its documentation for more * information. */ bool link(); + /** + * @brief Submit for linking + * + * The attached shaders must be compiled with @ref Shader::compile() + * or @ref Shader::submitCompile() before linking. + * + * @see @fn_gl_keyword{LinkProgram} + */ + void submitLink(); + + /** + * @brief Check link status and await completion + * + * Returns @cpp false @ce if linking failed, @cpp true @ce on success. + * Linker message (if any) is printed to error output. This function + * must be called only after @ref submitLink(). + * + * @see @fn_gl_keyword{GetProgram} with + * @def_gl{LINK_STATUS} and @def_gl{INFO_LOG_LENGTH}, + * @fn_gl_keyword{GetProgramInfoLog} + */ + bool checkLink(); + /** * @brief Get uniform location * @param name Uniform name @@ -1668,6 +1699,8 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject { static MAGNUM_GL_LOCAL void cleanLogImplementationAngle(std::string& message); #endif + MAGNUM_GL_LOCAL static void APIENTRY completionStatusImplementationFallback(GLuint, GLenum, GLint*); + MAGNUM_GL_LOCAL static void use(GLuint id); void use(); diff --git a/src/Magnum/GL/Implementation/ShaderProgramState.cpp b/src/Magnum/GL/Implementation/ShaderProgramState.cpp index 1e3125560..9799fc087 100644 --- a/src/Magnum/GL/Implementation/ShaderProgramState.cpp +++ b/src/Magnum/GL/Implementation/ShaderProgramState.cpp @@ -78,6 +78,13 @@ ShaderProgramState::ShaderProgramState(Context& context, Containers::StaticArray cleanLogImplementation = &AbstractShaderProgram::cleanLogImplementationNoOp; } + if(context.isExtensionSupported()) { + extensions[Extensions::KHR::parallel_shader_compile::Index] = Extensions::KHR::parallel_shader_compile::string(); + completionStatusImplementation = glGetProgramiv; + } else { + completionStatusImplementation = &AbstractShaderProgram::completionStatusImplementationFallback; + } + #ifndef MAGNUM_TARGET_WEBGL #ifndef MAGNUM_TARGET_GLES2 #ifndef MAGNUM_TARGET_GLES diff --git a/src/Magnum/GL/Implementation/ShaderProgramState.h b/src/Magnum/GL/Implementation/ShaderProgramState.h index 01f131b8f..95001ea80 100644 --- a/src/Magnum/GL/Implementation/ShaderProgramState.h +++ b/src/Magnum/GL/Implementation/ShaderProgramState.h @@ -47,6 +47,9 @@ struct ShaderProgramState { void(AbstractShaderProgram::*transformFeedbackVaryingsImplementation)(Containers::ArrayView, AbstractShaderProgram::TransformFeedbackBufferMode); #endif void(*cleanLogImplementation)(std::string&); + /* This is a direct pointer to a GL function, so needs a __stdcall on + Windows to compile properly on 32 bits */ + void(APIENTRY *completionStatusImplementation)(GLuint, GLenum, GLint* value); #ifndef MAGNUM_TARGET_WEBGL void(APIENTRY *uniform1fvImplementation)(GLuint, GLint, GLsizei, const GLfloat*); diff --git a/src/Magnum/GL/Implementation/ShaderState.cpp b/src/Magnum/GL/Implementation/ShaderState.cpp index ffc0c2f1e..17758a801 100644 --- a/src/Magnum/GL/Implementation/ShaderState.cpp +++ b/src/Magnum/GL/Implementation/ShaderState.cpp @@ -31,12 +31,13 @@ #include "Magnum/GL/Context.h" #include "Magnum/GL/Shader.h" +#include "Magnum/GL/Extensions.h" namespace Magnum { namespace GL { namespace Implementation { using namespace Containers::Literals; -ShaderState::ShaderState(Context& context, Containers::StaticArrayView): +ShaderState::ShaderState(Context& context, Containers::StaticArrayView extensions): maxVertexOutputComponents{}, maxFragmentInputComponents{}, #if !defined(MAGNUM_TARGET_GLES2) && !defined(MAGNUM_TARGET_WEBGL) maxTessellationControlInputComponents{}, maxTessellationControlOutputComponents{}, maxTessellationControlTotalOutputComponents{}, maxTessellationEvaluationInputComponents{}, maxTessellationEvaluationOutputComponents{}, maxGeometryInputComponents{}, maxGeometryOutputComponents{}, maxGeometryTotalOutputComponents{}, maxAtomicCounterBuffers{}, maxCombinedAtomicCounterBuffers{}, maxAtomicCounters{}, maxCombinedAtomicCounters{}, maxImageUniforms{}, maxCombinedImageUniforms{}, maxShaderStorageBlocks{}, maxCombinedShaderStorageBlocks{}, @@ -68,9 +69,12 @@ ShaderState::ShaderState(Context& context, Containers::StaticArrayView(context); + if(context.isExtensionSupported()) { + extensions[Extensions::KHR::parallel_shader_compile::Index] = Extensions::KHR::parallel_shader_compile::string(); + completionStatusImplementation = glGetShaderiv; + } else { + completionStatusImplementation = &Shader::completionStatusImplementationFallback; + } } }}} diff --git a/src/Magnum/GL/Implementation/ShaderState.h b/src/Magnum/GL/Implementation/ShaderState.h index 3eef9f0b5..4d37c764d 100644 --- a/src/Magnum/GL/Implementation/ShaderState.h +++ b/src/Magnum/GL/Implementation/ShaderState.h @@ -53,6 +53,9 @@ struct ShaderState { void(Shader::*addSourceImplementation)(std::string); void(*cleanLogImplementation)(std::string&); + /* This is a direct pointer to a GL function, so needs a __stdcall on + Windows to compile properly on 32 bits */ + void(APIENTRY *completionStatusImplementation)(GLuint, GLenum, GLint* value); GLint maxVertexOutputComponents, maxFragmentInputComponents; diff --git a/src/Magnum/GL/Shader.cpp b/src/Magnum/GL/Shader.cpp index 8708fa4b9..8b7bbb98b 100644 --- a/src/Magnum/GL/Shader.cpp +++ b/src/Magnum/GL/Shader.cpp @@ -749,74 +749,69 @@ Shader& Shader::addFile(const std::string& filename) { bool Shader::compile() { return compile({*this}); } -bool Shader::compile(std::initializer_list> shaders) { - bool allSuccess = true; +void Shader::submitCompile() { + CORRADE_ASSERT(_sources.size() > 1, "GL::Shader::compile(): no files added", ); - /* Allocate large enough array for source pointers and sizes (to avoid - reallocating it for each of them) */ - std::size_t maxSourceCount = 0; - for(Shader& shader: shaders) { - CORRADE_ASSERT(shader._sources.size() > 1, "GL::Shader::compile(): no files added", false); - maxSourceCount = Math::max(shader._sources.size(), maxSourceCount); - } /** @todo ArrayTuple/VLAs */ - Containers::Array pointers(maxSourceCount); - Containers::Array sizes(maxSourceCount); + Containers::Array pointers(_sources.size()); + Containers::Array sizes(_sources.size()); /* Upload sources of all shaders */ - for(Shader& shader: shaders) { - for(std::size_t i = 0; i != shader._sources.size(); ++i) { - pointers[i] = static_cast(shader._sources[i].data()); - sizes[i] = shader._sources[i].size(); - } - - glShaderSource(shader._id, shader._sources.size(), pointers, sizes); + for(std::size_t i = 0; i != _sources.size(); ++i) { + pointers[i] = static_cast(_sources[i].data()); + sizes[i] = _sources[i].size(); } - /* Invoke (possibly parallel) compilation on all shaders */ - for(Shader& shader: shaders) glCompileShader(shader._id); - - /* After compilation phase, check status of all shaders */ - Int i = 1; - for(Shader& shader: shaders) { - GLint success, logLength; - glGetShaderiv(shader._id, GL_COMPILE_STATUS, &success); - glGetShaderiv(shader._id, GL_INFO_LOG_LENGTH, &logLength); - - /* Error or warning message. The string is returned null-terminated, - strip the \0 at the end afterwards. */ - std::string message(logLength, '\0'); - if(message.size() > 1) - glGetShaderInfoLog(shader._id, message.size(), nullptr, &message[0]); - message.resize(Math::max(logLength, 1)-1); - - /* Some drivers are chatty and can't keep shut when there's nothing to - be said, handle that as well. */ - Context::current().state().shader.cleanLogImplementation(message); - - /* Show error log */ - if(!success) { - Error out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::Shader::compile(): compilation of" << shaderName(shader._type) << "shader"; - if(shaders.size() != 1) out << i; - out << "failed with the following message:" << Debug::newline << message; - - /* Or just warnings, if any */ - } else if(!message.empty()) { - Warning out{Debug::Flag::NoNewlineAtTheEnd}; - out << "GL::Shader::compile(): compilation of" << shaderName(shader._type) << "shader"; - if(shaders.size() != 1) out << i; - out << "succeeded with the following message:" << Debug::newline << message; - } - - /* Success of all depends on each of them */ - allSuccess = allSuccess && success; - ++i; + glShaderSource(_id, _sources.size(), pointers, sizes); + glCompileShader(_id); +} + +bool Shader::checkCompile() { /* After compilation phase, check status of all shaders */ + GLint success, logLength; + glGetShaderiv(_id, GL_COMPILE_STATUS, &success); + glGetShaderiv(_id, GL_INFO_LOG_LENGTH, &logLength); + + /* Error or warning message. The string is returned null-terminated, + strip the \0 at the end afterwards. */ + std::string message(logLength, '\0'); + if(message.size() > 1) + glGetShaderInfoLog(_id, message.size(), nullptr, &message[0]); + message.resize(Math::max(logLength, 1)-1); + + /* Some drivers are chatty and can't keep shut when there's nothing to + be said, handle that as well. */ + Context::current().state().shader.cleanLogImplementation(message); + + /* Show error log */ + if(!success) { + Error out{Debug::Flag::NoNewlineAtTheEnd}; + out << "GL::Shader::compile(): compilation of" << shaderName(_type) << "shader" + << "failed with the following message:" << Debug::newline << message; + + /* Or just warnings, if any */ + } else if(!message.empty()) { + Warning out{Debug::Flag::NoNewlineAtTheEnd}; + out << "GL::Shader::compile(): compilation of" << shaderName(_type) << "shader" + << "succeeded with the following message:" << Debug::newline << message; } + return success; +} + +bool Shader::compile(std::initializer_list> shaders) { + /* Invoke (possibly parallel) compilation on all shaders */ + for(Shader& shader: shaders) shader.submitCompile(); + bool allSuccess = true; + for(Shader& shader: shaders) allSuccess = allSuccess && shader.checkCompile(); return allSuccess; } +bool Shader::isCompileFinished() { + GLint success; + Context::current().state().shader.completionStatusImplementation(_id, GL_COMPLETION_STATUS_KHR, &success); + return success == GL_TRUE; +} + void Shader::cleanLogImplementationNoOp(std::string&) {} #if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES) @@ -825,6 +820,10 @@ void Shader::cleanLogImplementationIntelWindows(std::string& message) { } #endif +void Shader::completionStatusImplementationFallback(GLuint, GLenum, GLint* value) { + *value = GL_TRUE; +} + #ifndef DOXYGEN_GENERATING_OUTPUT Debug& operator<<(Debug& debug, const Shader::Type value) { debug << "GL::Shader::Type" << Debug::nospace; diff --git a/src/Magnum/GL/Shader.h b/src/Magnum/GL/Shader.h index 99984dc64..ee3ded533 100644 --- a/src/Magnum/GL/Shader.h +++ b/src/Magnum/GL/Shader.h @@ -510,14 +510,11 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { /** * @brief Compile multiple shaders simultaneously * + * Calls @ref submitCompile() on all shaders first, then @ref checkCompile(). * Returns @cpp false @ce if compilation of any shader failed, - * @cpp true @ce if everything succeeded. Compiler messages (if any) - * are printed to error output. The operation is batched in a way that + * @cpp true @ce if everything succeeded. The operation is batched in a way that * allows the driver to perform multiple compilations simultaneously * (i.e. in multiple threads). - * @see @fn_gl_keyword{ShaderSource}, @fn_gl_keyword{CompileShader}, - * @fn_gl_keyword{GetShader} with @def_gl{COMPILE_STATUS} and - * @def_gl{INFO_LOG_LENGTH}, @fn_gl_keyword{GetShaderInfoLog} */ static bool compile(std::initializer_list> shaders); @@ -636,13 +633,42 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { /** * @brief Compile shader * - * Compiles single shader. Prefer to compile multiple shaders at once - * using @ref compile(std::initializer_list>) + * Calls @ref submitCompile(), then @ref checkCompile(). + * Prefer to compile multiple shaders at once using + * @ref compile(std::initializer_list>) * for improved performance, see its documentation for more * information. */ bool compile(); + /** + * @brief Submit shader for compilation + * + * @see @fn_gl_keyword{ShaderSource}, @fn_gl_keyword{CompileShader} + */ + void submitCompile(); + + /** + * @brief Check compilation status and await completion + * + * Returns @cpp false @ce if compilation of failed, @cpp true @ce on success. + * This function must be called only after @ref submitCompile(). + * + * @see @fn_gl_keyword{GetShader} with @def_gl{COMPILE_STATUS} and + * @def_gl{INFO_LOG_LENGTH}, @fn_gl_keyword{GetShaderInfoLog} + */ + bool checkCompile(); + + /** + * @brief Non-blocking compilation status check + * @return @cpp true @ce if shader compilation finished, @cpp false @ce otherwise + * + * @see @fn_gl_keyword{GetProgram} with + * @def_gl_extension{COMPLETION_STATUS,KHR,parallel_shader_compile} + * + */ + bool isCompileFinished(); + private: void MAGNUM_GL_LOCAL addSourceImplementationDefault(std::string source); #if defined(CORRADE_TARGET_EMSCRIPTEN) && defined(__EMSCRIPTEN_PTHREADS__) @@ -654,6 +680,8 @@ class MAGNUM_GL_EXPORT Shader: public AbstractObject { static MAGNUM_GL_LOCAL void cleanLogImplementationIntelWindows(std::string& message); #endif + MAGNUM_GL_LOCAL static void APIENTRY completionStatusImplementationFallback(GLuint, GLenum, GLint*); + Type _type; GLuint _id; diff --git a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp index 658fa112e..e378aca0e 100644 --- a/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp +++ b/src/Magnum/GL/Test/AbstractShaderProgramGLTest.cpp @@ -27,8 +27,10 @@ #include #include /** @todo remove when Shader is -free */ #include +#include #include #include +#include #include "Magnum/Image.h" #include "Magnum/ImageView.h" @@ -68,8 +70,11 @@ struct AbstractShaderProgramGLTest: OpenGLTester { #ifndef MAGNUM_TARGET_GLES void createMultipleOutputsIndexed(); #endif + void createAsync(); void linkFailure(); + void linkFailureAsync(); + void uniformNotFound(); void uniform(); @@ -103,12 +108,14 @@ AbstractShaderProgramGLTest::AbstractShaderProgramGLTest() { #endif &AbstractShaderProgramGLTest::create, + &AbstractShaderProgramGLTest::createAsync, &AbstractShaderProgramGLTest::createMultipleOutputs, #ifndef MAGNUM_TARGET_GLES &AbstractShaderProgramGLTest::createMultipleOutputsIndexed, #endif &AbstractShaderProgramGLTest::linkFailure, + &AbstractShaderProgramGLTest::linkFailureAsync, &AbstractShaderProgramGLTest::uniformNotFound, &AbstractShaderProgramGLTest::uniform, @@ -205,6 +212,8 @@ struct MyPublicShader: AbstractShaderProgram { using AbstractShaderProgram::bindFragmentDataLocation; #endif using AbstractShaderProgram::link; + using AbstractShaderProgram::submitLink; + using AbstractShaderProgram::checkLink; using AbstractShaderProgram::uniformLocation; #ifndef MAGNUM_TARGET_GLES2 using AbstractShaderProgram::uniformBlockIndex; @@ -257,6 +266,80 @@ void AbstractShaderProgramGLTest::create() { MAGNUM_VERIFY_NO_GL_ERROR(); CORRADE_VERIFY(linked); + + // Some drivers need a bit of time to update this result + Utility::System::sleep(200); + CORRADE_VERIFY(program.isLinkFinished()); + { + #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) + CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); + #endif + CORRADE_VERIFY(valid); + } + + const Int matrixUniform = program.uniformLocation("matrix"); + const Int multiplierUniform = program.uniformLocation("multiplier"); + const Int colorUniform = program.uniformLocation("color"); + const Int additionsUniform = program.uniformLocation("additions"); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(matrixUniform >= 0); + CORRADE_VERIFY(multiplierUniform >= 0); + CORRADE_VERIFY(colorUniform >= 0); + CORRADE_VERIFY(additionsUniform >= 0); +} + +void AbstractShaderProgramGLTest::createAsync() { + Utility::Resource rs("AbstractShaderProgramGLTest"); + + Shader vert( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Vertex); + vert.addSource(rs.getString("MyShader.vert")); + const bool vertCompiled = vert.compile(); + + Shader frag( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Fragment); + frag.addSource(rs.getString("MyShader.frag")); + const bool fragCompiled = frag.compile(); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_VERIFY(vertCompiled); + CORRADE_VERIFY(fragCompiled); + + MyPublicShader program; + program.attachShaders({vert, frag}); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + program.bindAttributeLocation(0, "position"); + program.submitLink(); + + while(!program.isLinkFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(program.checkLink()); + CORRADE_VERIFY(program.isLinkFinished()); + const bool valid = program.validate().first; + + MAGNUM_VERIFY_NO_GL_ERROR(); { #if defined(CORRADE_TARGET_APPLE) && !defined(MAGNUM_TARGET_GLES) CORRADE_EXPECT_FAIL("macOS drivers need insane amount of state to validate properly."); @@ -410,6 +493,46 @@ void AbstractShaderProgramGLTest::linkFailure() { MyPublicShader program; program.attachShaders({shader}); CORRADE_VERIFY(!program.link()); + + Utility::System::sleep(200); + CORRADE_VERIFY(program.isLinkFinished()); +} + +void AbstractShaderProgramGLTest::linkFailureAsync() { + Shader shader( + #ifndef MAGNUM_TARGET_GLES + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + #else + Version::GLES200 + #endif + , Shader::Type::Fragment); + shader.addSource("[fu] bleh error #:! stuff\n"); + + { + Error redirectError{nullptr}; + CORRADE_VERIFY(!shader.compile()); + } + + MyPublicShader program; + program.attachShaders({shader}); + + std::ostringstream out; + Error redirectError{&out}; + program.submitLink(); + + while(!program.isLinkFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(out.str().empty()); + + CORRADE_VERIFY(!program.checkLink()); + CORRADE_VERIFY(program.isLinkFinished()); + CORRADE_COMPARE_AS(out.str(), "GL::AbstractShaderProgram::link(): linking failed with the following message:", + TestSuite::Compare::StringHasPrefix); } void AbstractShaderProgramGLTest::uniformNotFound() { diff --git a/src/Magnum/GL/Test/ShaderGLTest.cpp b/src/Magnum/GL/Test/ShaderGLTest.cpp index 5c7978b77..49ef6cc66 100644 --- a/src/Magnum/GL/Test/ShaderGLTest.cpp +++ b/src/Magnum/GL/Test/ShaderGLTest.cpp @@ -23,8 +23,11 @@ DEALINGS IN THE SOFTWARE. */ +#include #include /** @todo remove once Shader is -free */ +#include #include +#include #include #include "Magnum/GL/Context.h" @@ -55,6 +58,9 @@ struct ShaderGLTest: OpenGLTester { void addSourceNoVersion(); void addFile(); void compile(); + void compileFailure(); + void compileAsync(); + void compileAsyncFailure(); void compileUtf8(); void compileNoVersion(); }; @@ -72,6 +78,9 @@ ShaderGLTest::ShaderGLTest() { &ShaderGLTest::addSourceNoVersion, &ShaderGLTest::addFile, &ShaderGLTest::compile, + &ShaderGLTest::compileFailure, + &ShaderGLTest::compileAsync, + &ShaderGLTest::compileAsyncFailure, &ShaderGLTest::compileUtf8, &ShaderGLTest::compileNoVersion}); } @@ -276,11 +285,84 @@ void ShaderGLTest::compile() { Shader shader(v, Shader::Type::Fragment); shader.addSource("void main() {}\n"); + CORRADE_VERIFY(shader.compile()); + CORRADE_VERIFY(shader.isCompileFinished()); +} + +void ShaderGLTest::compileFailure() { + #ifndef MAGNUM_TARGET_GLES + constexpr Version v = + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + ; + #else + constexpr Version v = Version::GLES200; + #endif + + Shader shader(v, Shader::Type::Fragment); + shader.addSource("[fu] bleh error #:! stuff\n"); + + CORRADE_VERIFY(!shader.compile()); + CORRADE_VERIFY(shader.isCompileFinished()); +} + +void ShaderGLTest::compileAsync() { + #ifndef MAGNUM_TARGET_GLES + constexpr Version v = + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + ; + #else + constexpr Version v = Version::GLES200; + #endif + + Shader shader(v, Shader::Type::Fragment); + shader.addSource("void main() {}\n"); + shader.submitCompile(); + + while(!shader.isCompileFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(shader.checkCompile()); + CORRADE_VERIFY(shader.isCompileFinished()); +} + +void ShaderGLTest::compileAsyncFailure() { + #ifndef MAGNUM_TARGET_GLES + constexpr Version v = + #ifndef CORRADE_TARGET_APPLE + Version::GL210 + #else + Version::GL310 + #endif + ; + #else + constexpr Version v = Version::GLES200; + #endif + + Shader shader(v, Shader::Type::Fragment); + shader.addSource("[fu] bleh error #:! stuff\n"); + + std::ostringstream out; + Error redirectError{&out}; + shader.submitCompile(); + + while(!shader.isCompileFinished()) + Utility::System::sleep(100); + + CORRADE_VERIFY(out.str().empty()); - Shader shader2(v, Shader::Type::Fragment); - shader2.addSource("[fu] bleh error #:! stuff\n"); - CORRADE_VERIFY(!shader2.compile()); + CORRADE_VERIFY(!shader.checkCompile()); + CORRADE_VERIFY(shader.isCompileFinished()); + CORRADE_COMPARE_AS(out.str(), "GL::Shader::compile(): compilation of fragment shader failed with the following message:", + TestSuite::Compare::StringHasPrefix); } void ShaderGLTest::compileUtf8() {