From c3bfec239cdc03f8c996b8627d62e734761f5c60 Mon Sep 17 00:00:00 2001 From: skyjake Date: Sun, 21 Apr 2013 16:45:13 +0300 Subject: [PATCH] libgui|GLProgram: Basic implementation of GL programs Binding vertex attributes, applying uniform values, attaching shaders and linking the program. --- doomsday/libgui/include/de/gui/glprogram.h | 60 +++- doomsday/libgui/src/glprogram.cpp | 312 ++++++++++++++++++++- 2 files changed, 368 insertions(+), 4 deletions(-) diff --git a/doomsday/libgui/include/de/gui/glprogram.h b/doomsday/libgui/include/de/gui/glprogram.h index f1c802a204..9bf476e44d 100644 --- a/doomsday/libgui/include/de/gui/glprogram.h +++ b/doomsday/libgui/include/de/gui/glprogram.h @@ -20,21 +20,77 @@ #define LIBGUI_GLPROGRAM_H #include +#include +#include +#include #include "libgui.h" +#include "opengl.h" namespace de { +class GLUniform; +class GLShader; + /** - * GL shader program. + * GL shader program consisting of a vertex and fragment shaders. + * + * GLProgram instances work together with GLUniform to manage the program + * state. To allow a particular uniform to be used in a program, it first + * has to be bound to the program. + * + * When binding texture uniforms, the order of the binding calls is important + * as it determines which texture sampling unit each of the textures is + * allocated: the first bound texture uniform gets unit #0, the second one gets + * unit #1, etc. * * @ingroup gl */ -class LIBGUI_PUBLIC GLProgram +class LIBGUI_PUBLIC GLProgram : public Asset { +public: + /// Failed to allocate a new GL program object. @ingroup errors + DENG2_ERROR(AllocError); + + /// Failed to link the program. @ingroup errors + DENG2_ERROR(LinkerError); + public: GLProgram(); + /** + * Resets the program back to an empty state. All uniform bindings are + * removed. + */ + void clear(); + + /*GLProgram &build(IByteArray const &vertexShaderSource, + IByteArray const &fragmentShaderSource);*/ + + /** + * GLProgram retains a reference to both shaders. + * + * @param vertexShader Vertex shader. + * @param fragmentShader Fragment shader. + * + * @return Reference to this program. + */ + GLProgram &build(GLShader const &vertexShader, GLShader const &fragmentShader); + + GLProgram &operator << (GLUniform const &uniform); + + GLProgram &bind(GLUniform const &uniform); + + GLProgram &unbind(GLUniform const &uniform); + + void beginUse(); + + void endUse(); + + GLuint glName() const; + + int glUniformLocation(char const *uniformName) const; + private: DENG2_PRIVATE(d) }; diff --git a/doomsday/libgui/src/glprogram.cpp b/doomsday/libgui/src/glprogram.cpp index 85168d634f..ee6cf06cb9 100644 --- a/doomsday/libgui/src/glprogram.cpp +++ b/doomsday/libgui/src/glprogram.cpp @@ -17,16 +17,324 @@ */ #include "de/GLProgram" +#include "de/GLUniform" +#include "de/GLBuffer" +#include "de/GLShader" +#include "de/GLTexture" +#include "de/gui/opengl.h" +#include + +#include +#include namespace de { -DENG2_PIMPL(GLProgram) +using namespace internal; + +DENG2_PIMPL(GLProgram), + DENG2_OBSERVES(GLUniform, ValueChange), + DENG2_OBSERVES(GLUniform, Deletion) { - Instance(Public *i) : Base(i) + typedef QSet Uniforms; + typedef QList UniformList; + typedef QSet Shaders; + + Uniforms bound; + Uniforms changed; + UniformList textures; + bool texturesChanged; + + GLuint name; + Shaders shaders; + bool inUse; + + Instance(Public *i) + : Base(i), + texturesChanged(false), + name(0), + inUse(false) {} + + ~Instance() + { + release(); + } + + void alloc() + { + if(!name) + { + name = glCreateProgram(); + if(!name) + { + throw AllocError("GLProgram::alloc", "Failed to create program"); + } + } + } + + void release() + { + self.setState(NotReady); + detachAllShaders(); + unbindAll(); + if(name) + { + glDeleteProgram(name); + name = 0; + } + } + + void attach(GLShader const &shader) + { + DENG2_ASSERT(shader.isReady()); + glAttachShader(name, shader.glName()); + shaders.insert(holdRef(&shader)); + } + + void detach(GLShader const &shader) + { + if(shader.isReady()) + { + glDetachShader(name, shader.glName()); + } + shaders.remove(&shader); + shader.release(); + } + + void detachAllShaders() + { + foreach(GLShader const *shader, shaders) + { + detach(*shader); + } + shaders.clear(); + } + + void unbindAll() + { + foreach(GLUniform const *u, bound) + { + u->audienceForValueChange -= this; + u->audienceForDeletion -= this; + } + texturesChanged = false; + bound.clear(); + textures.clear(); + changed.clear(); + } + + /** + * Bind all known vertex attributes to the indices used by GLBuffer. The + * program is automatically (re)linked after binding the vertex attributes, + * if there are already shaders attached. + */ + void bindVertexAttribs() + { + alloc(); + + // The names of shader attributes are defined here: + static struct { + AttribSpec::Semantic semantic; + char const *varName; + } + const names[] = { + { AttribSpec::Position, "aVertex" }, + { AttribSpec::TexCoord0, "aUV" }, + { AttribSpec::TexCoord1, "aUV2" }, + { AttribSpec::TexCoord2, "aUV3" }, + { AttribSpec::TexCoord3, "aUV4" }, + { AttribSpec::Color, "aColor" }, + { AttribSpec::Normal, "aNormal" }, + { AttribSpec::Tangent, "aTangent" }, + { AttribSpec::Bitangent, "aBitangent" } + }; + + for(int i = 0; sizeof(names)/sizeof(names[0]); ++i) + { + glBindAttribLocation(name, GLuint(names[i].semantic), names[i].varName); + } + + if(!shaders.isEmpty()) + { + link(); + } + } + + void link() + { + DENG2_ASSERT(name != 0); + + glLinkProgram(name); + + // Was linking successful? + GLint ok; + glGetProgramiv(name, GL_LINK_STATUS, &ok); + if(!ok) + { + dint32 logSize; + dint32 count; + glGetProgramiv(name, GL_INFO_LOG_LENGTH, &logSize); + + Block log(logSize); + glGetProgramInfoLog(name, logSize, &count, reinterpret_cast(log.data())); + + throw LinkerError("GLProgram::link", "Linking failed:\n" + log); + } + } + + void updateUniforms() + { + if(changed.isEmpty()) return; + + // Apply the uniform values in this program. + foreach(GLUniform const *u, changed) + { + if(u->type() != GLUniform::Texture2D) + { + u->applyInProgram(self); + } + } + + if(texturesChanged) + { + // Update the sampler uniforms. + for(int unit = 0; unit < textures.size(); ++unit) + { + int loc = self.glUniformLocation(textures[unit]->name().latin1()); + if(loc >= 0) + { + glUniform1i(loc, unit); + } + } + texturesChanged = false; + } + + changed.clear(); + } + + void bindTextures() + { + // Update the sampler uniforms. + for(int unit = textures.size() - 1; unit >= 0; --unit) + { + if(textures[unit]->texture()) + { + textures[unit]->texture()->glBindToUnit(unit); + } + } + } + + void uniformValueChanged(GLUniform &uniform) + { + changed.insert(&uniform); + } + + void uniformDeleted(GLUniform &uniform) + { + self.unbind(uniform); + } }; GLProgram::GLProgram() : d(new Instance(this)) {} +void GLProgram::clear() +{ + d->release(); +} + +GLProgram &GLProgram::build(GLShader const &vertexShader, GLShader const &fragmentShader) +{ + DENG2_ASSERT(vertexShader.isReady()); + DENG2_ASSERT(vertexShader.type() == GLShader::Vertex); + DENG2_ASSERT(fragmentShader.isReady()); + DENG2_ASSERT(fragmentShader.type() == GLShader::Fragment); + + d->detachAllShaders(); + d->attach(vertexShader); + d->attach(fragmentShader); + d->bindVertexAttribs(); + + setState(Ready); + + return *this; +} + +GLProgram &GLProgram::operator << (GLUniform const &uniform) +{ + return bind(uniform); +} + +GLProgram &GLProgram::bind(GLUniform const &uniform) +{ + if(!d->bound.contains(&uniform)) + { + d->bound.insert(&uniform); + d->changed.insert(&uniform); + + uniform.audienceForValueChange += d.get(); + uniform.audienceForDeletion += d.get(); + + if(uniform.type() == GLUniform::Texture2D) + { + d->textures << &uniform; + d->texturesChanged = true; + } + } + return *this; +} + +GLProgram &GLProgram::unbind(GLUniform const &uniform) +{ + if(d->bound.contains(&uniform)) + { + d->bound.remove(&uniform); + d->changed.remove(&uniform); + + uniform.audienceForValueChange -= d.get(); + uniform.audienceForDeletion -= d.get(); + + if(uniform.type() == GLUniform::Texture2D) + { + d->textures.removeOne(&uniform); + d->texturesChanged = true; + } + } + return *this; +} + +void GLProgram::beginUse() +{ + DENG2_ASSERT(isReady()); + DENG2_ASSERT(!d->inUse); + + d->inUse = true; + + // The program is now ready for use. + glUseProgram(d->name); + + d->updateUniforms(); + d->bindTextures(); +} + +void GLProgram::endUse() +{ + DENG2_ASSERT(d->inUse); + + d->inUse = false; + glUseProgram(0); +} + +GLuint GLProgram::glName() const +{ + return d->name; +} + +int GLProgram::glUniformLocation(char const *uniformName) const +{ + GLint loc = glGetUniformLocation(d->name, uniformName); + // Could check loc here for validity. + return loc; +} + } // namespace de