Blob Blame Raw


#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();
}

//---------------------------------------------------------