Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
libgui|GLState: Basic implementation of stacked GL state manipulation
GLStates can either be created on demand with GLState::push(), or
one can keep a GLState instance around for repeating use.

(Note that Direct3D uses an object-based state system compatible with
GLState.)
  • Loading branch information
skyjake committed Apr 23, 2013
1 parent b530de6 commit 2362049
Show file tree
Hide file tree
Showing 3 changed files with 336 additions and 0 deletions.
3 changes: 3 additions & 0 deletions doomsday/libgui/include/de/gui/glbuffer.h
Expand Up @@ -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
Expand Down
21 changes: 21 additions & 0 deletions doomsday/libgui/include/de/gui/glstate.h
Expand Up @@ -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);
Expand All @@ -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
Expand Down
312 changes: 312 additions & 0 deletions 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 <jaakko.keranen@iki.fi>
*
Expand All @@ -17,16 +21,324 @@
*/

#include "de/GLState"
#include "de/gui/opengl.h"
#include <de/BitField>

namespace de {

namespace internal
{
enum Property {
CullMode,
DepthTest,
DepthFunc,
DepthWrite,
Blend,
BlendFuncSrc,
BlendFuncDest,
BlendOp,
MAX_PROPERTIES
};

/// The GL state stack.
struct GLStateStack : public QList<GLState *> {
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<gl::Cull>(CullMode);
}

bool GLState::depthTest() const
{
return d->props.asBool(DepthTest);
}

gl::Comparison GLState::depthFunc() const
{
return d->props.valueAs<gl::Comparison>(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<gl::Blend>(BlendFuncSrc);
}

gl::Blend GLState::destBlendFunc() const
{
return d->props.valueAs<gl::Blend>(BlendFuncDest);
}

gl::BlendFunc GLState::blendFunc() const
{
return gl::BlendFunc(srcBlendFunc(), destBlendFunc());
}

gl::BlendOp GLState::blendOp() const
{
return d->props.valueAs<gl::BlendOp>(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

0 comments on commit 2362049

Please sign in to comment.