diff --git a/doomsday/libgui/include/de/gui/glbuffer.h b/doomsday/libgui/include/de/gui/glbuffer.h index 2c65949be7..618cebd632 100644 --- a/doomsday/libgui/include/de/gui/glbuffer.h +++ b/doomsday/libgui/include/de/gui/glbuffer.h @@ -101,6 +101,9 @@ namespace gl * * @note Compatible with OpenGL ES 2.0. * + * @todo Add a method for replacing a portion of the existing data in the buffer + * (using glBufferSubData). + * * @ingroup gl */ class LIBGUI_PUBLIC GLBuffer : public Asset diff --git a/doomsday/libgui/include/de/gui/glstate.h b/doomsday/libgui/include/de/gui/glstate.h index 620d08323f..0c56e8027a 100644 --- a/doomsday/libgui/include/de/gui/glstate.h +++ b/doomsday/libgui/include/de/gui/glstate.h @@ -70,12 +70,26 @@ namespace gl /** * GL state. * + * All manipulation of OpenGL state must occur through this class. If OpenGL + * state is changed manually, it will result in GLState not knowing about it, + * potentially leading to the incorrect state being in effect later on. + * + * GLState instances can either be created on demand with GLState::push(), or + * one can keep a GLState instance around for repeating use. The stack exists + * to aid structured drawing: it is not required to use the stack for all + * drawing. GLState::apply() can be called for any GLState instance to use it + * as the current GL state. + * * @ingroup gl */ class LIBGUI_PUBLIC GLState { public: + /** + * Constructs a GL state with the default values for all properties. + */ GLState(); + GLState(GLState const &other); void setCull(gl::Cull mode); @@ -97,6 +111,13 @@ class LIBGUI_PUBLIC GLState gl::BlendFunc blendFunc() const; gl::BlendOp blendOp() const; + /** + * Updates the OpenGL state to match this GLState. Until this is called no + * changes occur in the OpenGL state. Calling this more than once is + * allowed; the subsequent calls do nothing. + */ + void apply() const; + public: /** * Pushes a copy of the current state onto the current thread's GL state diff --git a/doomsday/libgui/src/glstate.cpp b/doomsday/libgui/src/glstate.cpp index 05642c2444..9f79bc3d1e 100644 --- a/doomsday/libgui/src/glstate.cpp +++ b/doomsday/libgui/src/glstate.cpp @@ -1,4 +1,8 @@ /** @file glstate.cpp GL state. + * + * @todo This implementation assumes OpenGL drawing occurs only in one thread. + * If multithreaded rendering is done at some point in the future, the GL state + * stack must be part of the thread-local data. * * @authors Copyright (c) 2013 Jaakko Keränen * @@ -17,16 +21,324 @@ */ #include "de/GLState" +#include "de/gui/opengl.h" +#include namespace de { +namespace internal +{ + enum Property { + CullMode, + DepthTest, + DepthFunc, + DepthWrite, + Blend, + BlendFuncSrc, + BlendFuncDest, + BlendOp, + MAX_PROPERTIES + }; + + /// The GL state stack. + struct GLStateStack : public QList { + GLStateStack() { + // Initialize with a default state. + append(new GLState); + } + ~GLStateStack() { qDeleteAll(*this); } + }; + static GLStateStack stack; + + /// Currently applied GL state properties. + static BitField currentProps; +} + +using namespace internal; + DENG2_PIMPL(GLState) { + BitField props; + Instance(Public *i) : Base(i) + { + static BitField::Spec const propSpecs[MAX_PROPERTIES] = { + { CullMode, 2 }, + { DepthTest, 1 }, + { DepthFunc, 3 }, + { DepthWrite, 1 }, + { Blend, 1 }, + { BlendFuncSrc, 4 }, + { BlendFuncDest, 4 }, + { BlendOp, 2 } + }; + props.addElements(propSpecs, MAX_PROPERTIES); + } + + Instance(Public *i, Instance const &other) + : Base(i), props(other.props) {} + + static GLenum glComp(gl::Comparison comp) + { + switch(comp) + { + case gl::Never: return GL_NEVER; + case gl::Always: return GL_ALWAYS; + case gl::Equal: return GL_EQUAL; + case gl::NotEqual: return GL_NOTEQUAL; + case gl::Less: return GL_LESS; + case gl::Greater: return GL_GREATER; + case gl::LessOrEqual: return GL_LEQUAL; + case gl::GreaterOrEqual: return GL_GEQUAL; + } + return GL_NEVER; + } + + static GLenum glBFunc(gl::Blend f) + { + switch(f) + { + case gl::Zero: return GL_ZERO; + case gl::One: return GL_ONE; + case gl::SrcColor: return GL_SRC_COLOR; + case gl::OneMinusSrcColor: return GL_ONE_MINUS_SRC_COLOR; + case gl::SrcAlpha: return GL_SRC_ALPHA; + case gl::OneMinusSrcAlpha: return GL_ONE_MINUS_SRC_ALPHA; + case gl::DestColor: return GL_DST_COLOR; + case gl::OneMinusDestColor: return GL_ONE_MINUS_DST_COLOR; + case gl::DestAlpha: return GL_DST_ALPHA; + case gl::OneMinusDestAlpha: return GL_ONE_MINUS_DST_ALPHA; + } + return GL_ZERO; + } + + void glApply(Property prop) + { + switch(prop) + { + case CullMode: + switch(self.cull()) + { + case gl::None: + glDisable(GL_CULL_FACE); + break; + case gl::Front: + glEnable(GL_CULL_FACE); + glCullFace(GL_FRONT); + break; + case gl::Back: + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + break; + } + break; + + case DepthTest: + if(self.depthTest()) + glEnable(GL_DEPTH_TEST); + else + glDisable(GL_DEPTH_TEST); + break; + + case DepthFunc: + glDepthFunc(glComp(self.depthFunc())); + break; + + case DepthWrite: + if(self.depthWrite()) + glDepthMask(GL_TRUE); + else + glDepthMask(GL_FALSE); + break; + + case Blend: + if(self.blend()) + glEnable(GL_BLEND); + else + glDisable(GL_BLEND); + break; + + case BlendFuncSrc: + case BlendFuncDest: + glBlendFunc(glBFunc(self.srcBlendFunc()), glBFunc(self.destBlendFunc())); + break; + + case BlendOp: + switch(self.blendOp()) + { + case gl::Add: + glBlendEquation(GL_FUNC_ADD); + break; + case gl::Subtract: + glBlendEquation(GL_FUNC_SUBTRACT); + break; + case gl::ReverseSubtract: + glBlendEquation(GL_FUNC_REVERSE_SUBTRACT); + break; + } + break; + + default: + break; + } + } }; GLState::GLState() : d(new Instance(this)) +{ + setDepthWrite(true); + setDepthFunc (gl::Less); + setBlendFunc (gl::One, gl::Zero); +} + +GLState::GLState(GLState const &other) : d(new Instance(this, *other.d)) {} +void GLState::setCull(gl::Cull mode) +{ + d->props.set(CullMode, duint(mode)); +} + +void GLState::setDepthTest(bool enable) +{ + d->props.set(DepthTest, enable); +} + +void GLState::setDepthFunc(gl::Comparison func) +{ + d->props.set(DepthFunc, duint(func)); +} + +void GLState::setDepthWrite(bool enable) +{ + d->props.set(DepthWrite, enable); +} + +void GLState::setBlend(bool enable) +{ + d->props.set(Blend, enable); +} + +void GLState::setBlendFunc(gl::Blend src, gl::Blend dest) +{ + d->props.set(BlendFuncSrc, duint(src)); + d->props.set(BlendFuncDest, duint(dest)); +} + +void GLState::setBlendFunc(gl::BlendFunc func) +{ + d->props.set(BlendFuncSrc, duint(func.first)); + d->props.set(BlendFuncDest, duint(func.second)); +} + +void GLState::setBlendOp(gl::BlendOp op) +{ + d->props.set(BlendOp, duint(op)); +} + +gl::Cull GLState::cull() const +{ + return d->props.valueAs(CullMode); +} + +bool GLState::depthTest() const +{ + return d->props.asBool(DepthTest); +} + +gl::Comparison GLState::depthFunc() const +{ + return d->props.valueAs(DepthFunc); +} + +bool GLState::depthWrite() const +{ + return d->props.asBool(DepthWrite); +} + +bool GLState::blend() const +{ + return d->props.asBool(Blend); +} + +gl::Blend GLState::srcBlendFunc() const +{ + return d->props.valueAs(BlendFuncSrc); +} + +gl::Blend GLState::destBlendFunc() const +{ + return d->props.valueAs(BlendFuncDest); +} + +gl::BlendFunc GLState::blendFunc() const +{ + return gl::BlendFunc(srcBlendFunc(), destBlendFunc()); +} + +gl::BlendOp GLState::blendOp() const +{ + return d->props.valueAs(BlendOp); +} + +void GLState::apply() const +{ + BitField::Ids changed; + + if(!currentProps.size()) + { + // Apply everything. + changed = d->props.elementIds(); + } + else + { + // Just apply the changed parts of the state. + changed = d->props.delta(currentProps); + } + + if(!changed.isEmpty()) + { + currentProps = d->props; + + // The blend func only needs to be set once. + if(changed.contains(BlendFuncSrc) && changed.contains(BlendFuncDest)) + { + changed.remove(BlendFuncDest); + } + + // Apply the changed properties. + foreach(BitField::Id id, changed) + { + d->glApply(Property(id)); + } + } +} + +GLState &GLState::push() +{ + DENG2_ASSERT(!stack.isEmpty()); + + // Duplicate the topmost state. + GLState *top = new GLState(*stack.last()); + pushState(top); + return *top; +} + +void GLState::pop() +{ + delete takeState(); +} + +void GLState::pushState(GLState *state) +{ + stack.append(state); +} + +GLState *GLState::takeState() +{ + DENG2_ASSERT(stack.size() > 1); + return stack.takeLast(); +} + } // namespace de