Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async GL shader compilation #576

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
51c8ba7
Basic async shader compilation for FlatGL
dranikpg Jul 8, 2022
1ae63af
Merge branch 'master' into async-shader-compile
dranikpg Jul 18, 2022
37b47f1
Refactor FlatGL::CompileState
dranikpg Jul 25, 2022
66015a2
Move shader compilation/linking to member functions
dranikpg Jul 25, 2022
8bcd04a
Refactor FlatGL::CompileState
dranikpg Jul 27, 2022
89582ef
Update doc comments
dranikpg Jul 27, 2022
05d49d6
Implement completion status fallback & fixes
dranikpg Jul 27, 2022
fa26cfc
More small fixes
dranikpg Jul 28, 2022
0acc265
Fix wrong access
dranikpg Jul 29, 2022
6fe87cc
Implement tests for compile/link/flat
dranikpg Aug 1, 2022
1a1c045
Fixes: docs, tests
dranikpg Aug 6, 2022
fefb99d
VectorGL & tests
dranikpg Aug 9, 2022
e1ec1a6
MeshVisualizerGL3D & tests
dranikpg Aug 11, 2022
3c899c1
Small fixes
dranikpg Aug 13, 2022
cf9b90f
PhongGL & tests
dranikpg Aug 13, 2022
09ab0ef
Fixes
dranikpg Aug 13, 2022
fdb1dca
VertexColor & tests
dranikpg Aug 15, 2022
e3e05cd
Small fix in PhongGL construct
dranikpg Aug 15, 2022
511d869
DistanceFieldVector & tests. Fix deprecated references
dranikpg Aug 21, 2022
1d6c99d
Move FlatGL impls from header
dranikpg Aug 23, 2022
b086162
Move VectorGL impls from header
dranikpg Aug 23, 2022
b5da1e2
Move VertexColorGL impls from header
dranikpg Aug 23, 2022
3ab4b6f
Refactor tests
dranikpg Aug 23, 2022
b592fcb
Fix formatting
dranikpg Aug 24, 2022
dd51e27
Fix MeshVisualizer & tests
dranikpg Aug 24, 2022
9a0af4d
Fix unused vars
dranikpg Aug 24, 2022
1e363fd
Fix MeshVisualizer test case
dranikpg Aug 24, 2022
ebbed67
Fixes
dranikpg Sep 3, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 47 additions & 41 deletions src/Magnum/GL/AbstractShaderProgram.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -585,53 +585,55 @@ void AbstractShaderProgram::transformFeedbackVaryingsImplementationDanglingWorka

bool AbstractShaderProgram::link() { return link({*this}); }

bool AbstractShaderProgram::link(std::initializer_list<Containers::Reference<AbstractShaderProgram>> 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<Containers::Reference<AbstractShaderProgram>> 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)
Expand All @@ -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<const char> name) {
const GLint location = glGetUniformLocation(_id, name);
if(location == -1)
Expand Down
53 changes: 43 additions & 10 deletions src/Magnum/GL/AbstractShaderProgram.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
dranikpg marked this conversation as resolved.
Show resolved Hide resolved

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<Containers::Reference<AbstractShaderProgram>> shaders);

Expand Down Expand Up @@ -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<Containers::Reference<AbstractShaderProgram>>)
* Calls @ref submitLink(), then @ref checkLink().
* If possible, prefer to link multiple shaders at once using
* @ref link(std::initializer_list<Containers::Reference<AbstractShaderProgram>>)
* 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();
dranikpg marked this conversation as resolved.
Show resolved Hide resolved

/**
* @brief Get uniform location
* @param name Uniform name
Expand Down Expand Up @@ -1668,6 +1699,8 @@ class MAGNUM_GL_EXPORT AbstractShaderProgram: public AbstractObject {
static MAGNUM_GL_LOCAL void cleanLogImplementationAngle(std::string& message);
#endif

static MAGNUM_GL_LOCAL void completionStatusImplementationFallback(GLuint, GLenum, GLint*);

MAGNUM_GL_LOCAL static void use(GLuint id);
void use();

Expand Down
7 changes: 7 additions & 0 deletions src/Magnum/GL/Implementation/ShaderProgramState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ ShaderProgramState::ShaderProgramState(Context& context, Containers::StaticArray
cleanLogImplementation = &AbstractShaderProgram::cleanLogImplementationNoOp;
}

if(context.isExtensionSupported<Extensions::KHR::parallel_shader_compile>()) {
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
Expand Down
1 change: 1 addition & 0 deletions src/Magnum/GL/Implementation/ShaderProgramState.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ struct ShaderProgramState {
void(AbstractShaderProgram::*transformFeedbackVaryingsImplementation)(Containers::ArrayView<const std::string>, AbstractShaderProgram::TransformFeedbackBufferMode);
#endif
void(*cleanLogImplementation)(std::string&);
void(*completionStatusImplementation)(GLuint, GLenum, GLint* value);

#ifndef MAGNUM_TARGET_WEBGL
void(APIENTRY *uniform1fvImplementation)(GLuint, GLint, GLsizei, const GLfloat*);
Expand Down
12 changes: 8 additions & 4 deletions src/Magnum/GL/Implementation/ShaderState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Implementation::ExtensionCount, const char*>):
ShaderState::ShaderState(Context& context, Containers::StaticArrayView<Implementation::ExtensionCount, const char*> 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{},
Expand Down Expand Up @@ -68,9 +69,12 @@ ShaderState::ShaderState(Context& context, Containers::StaticArrayView<Implement
cleanLogImplementation = &Shader::cleanLogImplementationNoOp;
}

/* Needed only if neither of these ifdefs above hits, but I won't bother
crafting the preprocessor logic for this. */
static_cast<void>(context);
if(context.isExtensionSupported<GL::Extensions::KHR::parallel_shader_compile>()) {
extensions[Extensions::KHR::parallel_shader_compile::Index] = Extensions::KHR::parallel_shader_compile::string();
completionStatusImplementation = glGetShaderiv;
} else {
completionStatusImplementation = &Shader::completionStatusImplementationFallback;
}
}

}}}
1 change: 1 addition & 0 deletions src/Magnum/GL/Implementation/ShaderState.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ struct ShaderState {

void(Shader::*addSourceImplementation)(std::string);
void(*cleanLogImplementation)(std::string&);
void(*completionStatusImplementation)(GLuint, GLenum, GLint* value);
dranikpg marked this conversation as resolved.
Show resolved Hide resolved

GLint maxVertexOutputComponents,
maxFragmentInputComponents;
Expand Down
113 changes: 56 additions & 57 deletions src/Magnum/GL/Shader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -749,74 +749,69 @@ Shader& Shader::addFile(const std::string& filename) {

bool Shader::compile() { return compile({*this}); }

bool Shader::compile(std::initializer_list<Containers::Reference<Shader>> 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<const GLchar*> pointers(maxSourceCount);
Containers::Array<GLint> sizes(maxSourceCount);
Containers::Array<const GLchar*> pointers(_sources.size());
Containers::Array<GLint> sizes(_sources.size());
dranikpg marked this conversation as resolved.
Show resolved Hide resolved

/* Upload sources of all shaders */
for(Shader& shader: shaders) {
for(std::size_t i = 0; i != shader._sources.size(); ++i) {
pointers[i] = static_cast<const GLchar*>(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<const GLchar*>(_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<Containers::Reference<Shader>> 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;
}
dranikpg marked this conversation as resolved.
Show resolved Hide resolved

void Shader::cleanLogImplementationNoOp(std::string&) {}

#if defined(CORRADE_TARGET_WINDOWS) && !defined(MAGNUM_TARGET_GLES)
Expand All @@ -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;
Expand Down
Loading