366 lines
10 KiB
C++
366 lines
10 KiB
C++
|
|
|
|
#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 namepace
|
|
|
|
//-----------------------------------------------------------------------------------------
|
|
|
|
class TStencilControl::Imp {
|
|
public:
|
|
int m_stencilBitCount;
|
|
int m_pushCount;
|
|
int m_currentWriting; // current stencil bit plane.
|
|
// 0 is the first bit plane ; -1 menas 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();
|
|
}
|
|
|
|
//---------------------------------------------------------
|