#include "tstencilcontrol.h"
#include "tgl.h"
#include "tthreadmessage.h"
#include <stack>
#include <QThreadStorage>
//-----------------------------------------------------------------------------------------
namespace {
// singleton
class StencilControlManager {
QThreadStorage<TStencilControl *> m_storage;
StencilControlManager() {}
public:
static StencilControlManager *instance() {
static StencilControlManager theInstance;
return &theInstance;
}
TStencilControl *getCurrentStencilControl() {
if (!m_storage.hasLocalData()) {
m_storage.setLocalData(new TStencilControl);
}
return m_storage.localData();
}
~StencilControlManager() {}
};
} // Local namespace
//-----------------------------------------------------------------------------------------
class TStencilControl::Imp {
public:
int m_stencilBitCount;
int m_pushCount;
int m_currentWriting; // current stencil bit plane.
// 0 is the first bit plane ; -1 means no stencil mask is writing
int m_virtualState;
// the state of the (eventually virtual) top mask.
// A mask is virtual if overflows stencil buffer
// 0 is closed and disabled, 1 closed and enabled and 2 is opened
#ifdef _DEBUG
std::stack<bool> fullState;
// state of each mask in the stack (except top mask).
// 'true' means opend; 'false' means close and enabled
// Used only for assert
#endif
unsigned char m_writingMask; // bit mask. The i-th bit=1 iff the i-th mask is
// opened to write
unsigned char m_drawOnScreenMask; // bitmsk.The ith bit=1 iff the ith mask
// WRITE also ON SCREEN WHEN WRITE ON
// STENCIL BIT PLANE
unsigned char
m_enabledMask; // bit mask. The i-th bit=1 iff the i-th mask is enabled
unsigned char
m_inOrOutMask; // bit mask. The i-th bit=1 iff the i-th mask is inside
unsigned char m_drawOnlyOnceMask;
Imp();
void updateOpenGlState();
void pushMask();
// make a new stencil plane the current one.
// if there is no plane available, increase a counter and does not push
// (virtual masks)
// So the same number of pop has no effect
void popMask();
// assert if the stencil stack contains only 0
void beginMask(DrawMode drawMode);
// clear the current stencil plane; start writing to it
// if drawOnScreen is not 0, it writes also to the color buffer (or stencil
// plane if another
// mask is open). If drawOnScreen is 2, it drows only once (by stencil buffer)
void endMask();
// end writing to the stencil plane.
void enableMask(MaskType maskType);
void disableMask();
// between enableMask()/disableMask() drawing is filtered by the values of the
// current
// stencil plane
// n.b. enableMask()/disableMask() can be nidified. Between the inner pair
// writing is enabled
// according to the AND of all of them
};
//-----------------------------------------------------------------------------------------
TStencilControl::Imp::Imp()
: m_stencilBitCount(0)
, m_pushCount(1)
, m_currentWriting(-1)
, m_enabledMask(0)
, m_writingMask(0)
, m_inOrOutMask(0)
, m_drawOnScreenMask(0)
, m_drawOnlyOnceMask(0)
, m_virtualState(0) {
glGetIntegerv(GL_STENCIL_BITS, (GLint *)&m_stencilBitCount);
glStencilMask(0xFFFFFFFF);
// glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
}
//---------------------------------------------------------
TStencilControl *TStencilControl::instance() {
StencilControlManager *instance = StencilControlManager::instance();
return instance->getCurrentStencilControl();
}
//---------------------------------------------------------
TStencilControl::TStencilControl() : m_imp(new Imp) {}
//---------------------------------------------------------
TStencilControl::~TStencilControl() {}
//---------------------------------------------------------
void TStencilControl::Imp::updateOpenGlState() {
if (m_currentWriting >= 0) { // writing on stencil buffer
unsigned char currentWritingMask = 1 << m_currentWriting;
bool drawOnlyOnce = (currentWritingMask & m_drawOnlyOnceMask) != 0;
if (currentWritingMask & m_drawOnScreenMask) {
unsigned char lastWritingMask;
int lastWriting = m_currentWriting - 1;
for (; lastWriting >= 0; lastWriting--) {
lastWritingMask = 1 << lastWriting;
if ((lastWritingMask & m_writingMask) == lastWritingMask) break;
}
if (lastWriting < 0) {
// glColorMask(1,1,1,1);
if (drawOnlyOnce)
m_enabledMask |= currentWritingMask;
else
m_enabledMask &= ~currentWritingMask;
} else {
tglMultColorMask(0, 0, 0, 0);
// glDrawBuffer(GL_NONE);
drawOnlyOnce = false; // essendo solo un'effetto visivo, se sto
// scrivendo su una maschera e non a schermo, e'
// inutile
currentWritingMask |= lastWritingMask;
}
} else
tglMultColorMask(0, 0, 0, 0);
// glDrawBuffer(GL_NONE);
glStencilMask(currentWritingMask);
if (drawOnlyOnce) {
glStencilFunc(GL_EQUAL, m_inOrOutMask, m_enabledMask);
glStencilOp(GL_KEEP, GL_INVERT, GL_INVERT);
} else {
glStencilFunc(GL_EQUAL, currentWritingMask | m_inOrOutMask,
m_enabledMask);
glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE);
}
} else { // writing on screen buffer
glStencilMask(0xFFFFFFFF);
glStencilFunc(GL_EQUAL, m_inOrOutMask, m_enabledMask);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
// glColorMask(1,1,1,1);
}
if (!m_enabledMask && m_currentWriting < 0)
glDisable(GL_STENCIL_TEST);
else
glEnable(GL_STENCIL_TEST);
}
//---------------------------------------------------------
void TStencilControl::Imp::pushMask() { m_pushCount++; }
//---------------------------------------------------------
void TStencilControl::Imp::beginMask(DrawMode drawMode) {
m_currentWriting = m_pushCount - 1;
unsigned char currentMask = 1 << m_currentWriting;
m_writingMask |= currentMask;
if (drawMode == DRAW_ALSO_ON_SCREEN) {
m_drawOnScreenMask |= currentMask;
} else if (drawMode == DRAW_ON_SCREEN_ONLY_ONCE) {
m_drawOnScreenMask |= currentMask;
m_drawOnlyOnceMask |= currentMask;
} else {
m_drawOnScreenMask &= ~currentMask;
m_drawOnlyOnceMask &= ~currentMask;
}
glEnable(GL_STENCIL_TEST);
glStencilMask(currentMask); // enabled to modify only current bitPlane
glClear(GL_STENCIL_BUFFER_BIT); // and clear it
updateOpenGlState();
}
//---------------------------------------------------------
void TStencilControl::Imp::endMask() {
assert(m_pushCount - 1 == m_currentWriting);
unsigned char currentMask = 1 << (m_pushCount - 1);
m_writingMask &= ~currentMask; // stop writing
m_enabledMask &= ~currentMask;
m_drawOnScreenMask &= ~currentMask;
m_drawOnlyOnceMask &= ~currentMask;
//----------------------------------------------------------
m_currentWriting--;
for (; m_currentWriting >= 0; m_currentWriting--) {
unsigned char currentWritingMask = 1 << m_currentWriting;
if ((currentWritingMask & m_writingMask) == currentWritingMask) break;
}
updateOpenGlState();
}
//---------------------------------------------------------
void TStencilControl::Imp::enableMask(MaskType maskType) {
unsigned char currentMask = 1 << (m_pushCount - 1);
if ((m_enabledMask & currentMask) == 0) glPushAttrib(GL_ALL_ATTRIB_BITS);
m_enabledMask |= currentMask;
assert(maskType == SHOW_INSIDE || maskType == SHOW_OUTSIDE);
if (maskType == SHOW_INSIDE)
m_inOrOutMask |= currentMask;
else
m_inOrOutMask &= ~currentMask;
updateOpenGlState();
}
//---------------------------------------------------------
void TStencilControl::Imp::disableMask() {
unsigned char currentMask = 1 << (m_pushCount - 1);
m_enabledMask &= ~currentMask;
m_inOrOutMask &= ~currentMask;
updateOpenGlState();
glPopAttrib();
}
//---------------------------------------------------------
void TStencilControl::Imp::popMask() {
--m_pushCount;
assert(m_pushCount > 0); // there is at least one mask in the stack
}
//---------------------------------------------------------
//---------------------------------------------------------
// questo forse e' un po' brutto:
// La maschera e' chiusa.
// Se e' abilitata, open = push+open
// Se e' disabilitata, open = open
void TStencilControl::beginMask(DrawMode drawMode) {
glPushAttrib(GL_ALL_ATTRIB_BITS);
if (m_imp->m_virtualState) // opened or enabled
{
#ifdef _DEBUG
m_imp->fullState.push(m_imp->m_virtualState == 2);
#endif
m_imp->pushMask();
}
m_imp->m_virtualState = 2; // opened
if (m_imp->m_pushCount <= m_imp->m_stencilBitCount)
m_imp->beginMask(drawMode);
}
//---------------------------------------------------------
void TStencilControl::endMask() {
if (!m_imp->m_virtualState) // closed and disabled
{
m_imp->popMask();
#ifdef _DEBUG
assert(m_imp->fullState.top()); // the state before last push must be
// opened
m_imp->fullState.pop();
#endif
}
assert(m_imp->m_virtualState != 1); // yet closed
m_imp->m_virtualState = 0;
if (m_imp->m_pushCount <= m_imp->m_stencilBitCount) m_imp->endMask();
glPopAttrib();
}
//---------------------------------------------------------
void TStencilControl::enableMask(MaskType maskType) {
assert(m_imp->m_virtualState != 2); // cannot enable an opened mask
m_imp->m_virtualState = 1; // enabled
if (m_imp->m_pushCount <= m_imp->m_stencilBitCount)
m_imp->enableMask(maskType);
}
//---------------------------------------------------------
void TStencilControl::disableMask() {
assert(m_imp->m_virtualState != 2); // cannot disable an opened mask
if (!m_imp->m_virtualState) // closed and disabled
{
m_imp->popMask();
#ifdef _DEBUG
assert(
!m_imp->fullState.top()); // the state before last push must be enabled
m_imp->fullState.pop();
#endif
}
m_imp->m_virtualState = 0; // close and disabled
if (m_imp->m_pushCount <= m_imp->m_stencilBitCount) m_imp->disableMask();
}
//---------------------------------------------------------