diff --git a/binaries/data/mods/official/gui/gui.dtd b/binaries/data/mods/official/gui/gui.dtd index 95197a4551..aeadd42fd3 100755 --- a/binaries/data/mods/official/gui/gui.dtd +++ b/binaries/data/mods/official/gui/gui.dtd @@ -161,4 +161,5 @@ diff --git a/source/gui/CGUI.cpp b/source/gui/CGUI.cpp index 1c32f3446b..83148a5670 100755 --- a/source/gui/CGUI.cpp +++ b/source/gui/CGUI.cpp @@ -1569,8 +1569,16 @@ void CGUI::Xeromyces_ReadEffects(XMBElement Element, CXeromyces* pFile, SGUIImag } \ else +#define BOOL(xml, mem) \ + if (attr_name == xml) \ + { \ + effects.m_##mem = true; \ + } \ + else + COLOR("add-color", AddColor) COLOR("multiply-color", MultiplyColor) + BOOL("grayscale", Greyscale) { debug_warn("Oops"); // DTD shouldn't allow this diff --git a/source/gui/CGUISprite.cpp b/source/gui/CGUISprite.cpp index 1c160643a2..95315b23e6 100755 --- a/source/gui/CGUISprite.cpp +++ b/source/gui/CGUISprite.cpp @@ -1,22 +1,6 @@ #include "precompiled.h" #include "CGUISprite.h" -CGUISpriteInstance::CGUISpriteInstance() -{ -} - -CGUISpriteInstance::CGUISpriteInstance(CStr SpriteName) - : m_SpriteName(SpriteName) -{ -} - -CGUISpriteInstance &CGUISpriteInstance::operator=(CStr SpriteName) -{ - m_SpriteName = SpriteName; - Invalidate(); - return *this; -} - void CGUISpriteInstance::Draw(CRect Size, int CellID, std::map &Sprites) { if (m_CachedSize != Size || m_CachedCellID != CellID) @@ -37,3 +21,30 @@ bool CGUISpriteInstance::IsEmpty() const { return m_SpriteName==""; } + +// Plus a load of constructors / assignment operators, which don't copy the +// DrawCall cache (to avoid losing track of who has allocated various bits +// of data): + +CGUISpriteInstance::CGUISpriteInstance() +{ +} + +CGUISpriteInstance::CGUISpriteInstance(CStr SpriteName) +: m_SpriteName(SpriteName) +{ +} + +CGUISpriteInstance::CGUISpriteInstance(const CGUISpriteInstance &Sprite) +: m_SpriteName(Sprite.m_SpriteName) +{ +} + +CGUISpriteInstance &CGUISpriteInstance::operator=(CStr SpriteName) +{ + m_SpriteName = SpriteName; + m_DrawCallCache.clear(); + Invalidate(); + return *this; +} + diff --git a/source/gui/CGUISprite.h b/source/gui/CGUISprite.h index 00a3f1c850..e908e5930a 100755 --- a/source/gui/CGUISprite.h +++ b/source/gui/CGUISprite.h @@ -49,8 +49,10 @@ gee@pyro.nu struct SGUIImageEffects { + SGUIImageEffects() : m_Greyscale(false) {} CColor m_AddColor; CColor m_MultiplyColor; + bool m_Greyscale; }; @@ -141,6 +143,7 @@ class CGUISpriteInstance public: CGUISpriteInstance(); CGUISpriteInstance(CStr SpriteName); + CGUISpriteInstance(const CGUISpriteInstance &Sprite); CGUISpriteInstance &operator=(CStr SpriteName); void Draw(CRect Size, int CellID, std::map &Sprites); void Invalidate(); diff --git a/source/gui/GUIRenderer.cpp b/source/gui/GUIRenderer.cpp index 97b94d00d5..47b518c28c 100644 --- a/source/gui/GUIRenderer.cpp +++ b/source/gui/GUIRenderer.cpp @@ -10,36 +10,178 @@ using namespace GUIRenderer; -// Copyable texture Handle, for use in STL containers where the Handle should -// be freed when it's finished with. -Handle_rfcnt_tex::Handle_rfcnt_tex() -: h(0) +void DrawCalls::clear() +{ + for (iterator it = begin(); it != end(); ++it) + { + delete it->m_Effects; + tex_free(it->m_TexHandle); + } + std::vector::clear(); +} + +DrawCalls::DrawCalls() { } -Handle_rfcnt_tex::Handle_rfcnt_tex(Handle h_) -: h(h_) +// Never copy anything (to avoid losing track of who owns various pointers): + +DrawCalls::DrawCalls(const DrawCalls&) { } -Handle_rfcnt_tex::Handle_rfcnt_tex(const Handle_rfcnt_tex& that) +const DrawCalls& DrawCalls::operator=(const DrawCalls&) { - h = that.h; - if (h) h_add_ref(h); -} - -Handle_rfcnt_tex::~Handle_rfcnt_tex() -{ - if (h) tex_free(h); -} - -Handle_rfcnt_tex& Handle_rfcnt_tex::operator=(Handle h_) -{ - h = h_; return *this; } +DrawCalls::~DrawCalls() +{ + clear(); + std::vector::~vector(); +} + + +// Implementations of graphical effects + +class Effect_AddColor : public IGLState +{ +public: + Effect_AddColor(CColor c) : m_Color(c) {} + ~Effect_AddColor() {} + void Set(Handle tex) + { + glColor4fv(m_Color.FloatArray()); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); + tex_bind(tex); + } + void Unset() + { + } +private: + CColor m_Color; +}; + +class Effect_MultiplyColor : public IGLState +{ +public: + Effect_MultiplyColor(CColor c) : m_Color(c) {} + ~Effect_MultiplyColor() {} + void Set(Handle tex) + { + glColor4fv(m_Color.FloatArray()); + glEnable(GL_TEXTURE_2D); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + tex_bind(tex); + } + void Unset() + { + } +private: + CColor m_Color; +}; + +#define X(n) (n##f/2.0f + 0.5f) +const float GreyscaleDotColor[4] = { X(0.3), X(0.59), X(0.11), 1.0f }; +#undef X +const float GreyscaleInterpColor0[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; +const float GreyscaleInterpColor1[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; + +class Effect_Greyscale : public IGLState +{ +public: + ~Effect_Greyscale() {} + void Set(Handle tex) + { + /* + + For the main conversion, use GL_DOT3_RGB, which is defined as + L = 4 * ((Arg0r - 0.5) * (Arg1r - 0.5)+ + (Arg0g - 0.5) * (Arg1g - 0.5)+ + (Arg0b - 0.5) * (Arg1b - 0.5)) + where each of the RGB components is given the value 'L'. + + Use the magical luminance formula + L = 0.3R + 0.59G + 0.11B + to calculate the greyscale value. + + But to work around the annoying "Arg0-0.5", we need to calculate + Arg0+0.5. But we also need to scale it into the range 0.5-1.0, else + Arg0>0.5 will be clamped to 1.0. So use GL_INTERPOLATE, which outputs: + A0 * A2 + A1 * (1 - A2) + and set A2 = 0.5, A1 = 1.0, and A0 = texture (i.e. interpolating halfway + between the texture and {1,1,1}) giving + A0/2 + 0.5 + and use that as Arg0. + + So L = 4*(A0/2 * (Arg1-.5)) + = 2 (Rx+Gy+Bz) (where Arg1 = {x+0.5, y+0.5, z+0.5}) + = 2x R + 2y G + 2z B + = 0.3R + 0.59G + 0.11B + so e.g. 2y = 0.59 = 2(Arg1g-0.5) => Arg1g = 0.59/2+0.5 + which fortunately doesn't get clamped. + + So, just implement that: + + */ + + // TODO: Render all greyscale objects at the same time, to reduce + // the number of times the following code is called - it looks like + // a rather worrying amount of work for rendering a single button... + + // Texture unit 0: + + glEnable(GL_TEXTURE_2D); + tex_bind(tex); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_TEXTURE); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); + + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); + + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, GreyscaleInterpColor0); + + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB, GL_PREVIOUS); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR); + glColor4fv(GreyscaleInterpColor1); + + // Texture unit 1: + + glActiveTexture(GL_TEXTURE1); + glEnable(GL_TEXTURE_2D); + tex_bind(tex); + + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA); + + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB); + glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE); + + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT); + glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR); + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, GreyscaleDotColor); + + } + void Unset() + { + glDisable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); + } +}; + + + // Functions to perform drawing-related actions: void GUIRenderer::UpdateDrawCallCache(DrawCalls &Calls, CStr &SpriteName, CRect &Size, int CellID, std::map &Sprites) @@ -47,6 +189,7 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls &Calls, CStr &SpriteName, CRect // This is called only when something has changed (like the size of the // sprite), so it doesn't need to be particularly efficient. + // Clean up the old data Calls.clear(); std::map::iterator it (Sprites.find(SpriteName)); @@ -66,7 +209,8 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls &Calls, CStr &SpriteName, CRect Calls.reserve(it->second.m_Images.size()); - // Iterate through all the sprite's images + // Iterate through all the sprite's images, loading the texture and + // calculating the texture coordinates std::vector::const_iterator cit; for (cit = it->second.m_Images.begin(); cit != it->second.m_Images.end(); ++cit) { @@ -100,13 +244,16 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls &Calls, CStr &SpriteName, CRect // TODO: Detect the presence of an alpha channel in a nicer way Call.m_EnableBlending = (TexFormat == GL_RGBA || TexFormat == GL_BGRA); + // Textures are positioned by defining a rectangular block of the // texture (usually the whole texture), and a rectangular block on // the screen. The texture is positioned to make those blocks line up. + // Get the screen's position/size for the block CRect BlockScreen = cit->m_TextureSize.GetClientArea(ObjectSize); + // Get the texture's position/size for the block: CRect BlockTex; @@ -161,13 +308,49 @@ void GUIRenderer::UpdateDrawCallCache(DrawCalls &Calls, CStr &SpriteName, CRect else { Call.m_TexHandle = 0; + // Enable blending if it's transparent (allowing a little error in the calculations) Call.m_EnableBlending = !(fabs(cit->m_BackColor.a - 1.0f) < 0.0000001f); } Call.m_BackColor = cit->m_BackColor; Call.m_BorderColor = cit->m_Border ? cit->m_BorderColor : CColor(); Call.m_DeltaZ = cit->m_DeltaZ; - Call.m_Effects = cit->m_Effects; + + if (cit->m_Effects) + { + if (cit->m_Effects->m_AddColor != CColor()) + Call.m_Effects = new Effect_AddColor(cit->m_Effects->m_AddColor); + + else if (cit->m_Effects->m_MultiplyColor != CColor()) + Call.m_Effects = new Effect_MultiplyColor(cit->m_Effects->m_MultiplyColor); + + else if (cit->m_Effects->m_Greyscale) + Call.m_Effects = new Effect_Greyscale; + + else + /* Slight confusion - why no effects? */ + Call.m_Effects = NULL; + } + else + { + Call.m_Effects = NULL; + +/* TODO: Delete this code + _CrtMemState s; + _CrtMemCheckpoint(&s); + struct ::_CrtMemBlockHeader + { + struct _CrtMemBlockHeader * pBlockHeaderNext; + struct _CrtMemBlockHeader * pBlockHeaderPrev; + char * szFileName; + int nLine; + size_t nDataSize; + int nBlockUse; + long lRequest; + }; + debug_out("%d %s\n", s.pBlockHeader->lRequest, SpriteName.c_str()); +*/ + } Calls.push_back(Call); } @@ -187,34 +370,19 @@ void GUIRenderer::Draw(DrawCalls &Calls) glEnable(GL_BLEND); } - if (cit->m_TexHandle.h) + if (cit->m_TexHandle) { // TODO: Handle the GL state in a nicer way - glEnable(GL_TEXTURE_2D); - - bool done = false; if (cit->m_Effects) + cit->m_Effects->Set(cit->m_TexHandle); + else { - if (cit->m_Effects->m_MultiplyColor != CColor()) - { - glColor4fv(cit->m_Effects->m_MultiplyColor.FloatArray()); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - done = true; - } - else if (cit->m_Effects && cit->m_Effects->m_AddColor != CColor()) - { - glColor4fv(cit->m_Effects->m_AddColor.FloatArray()); - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD); - done = true; - } - } - - if (! done) + glEnable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); - - tex_bind(cit->m_TexHandle.h); - + tex_bind(cit->m_TexHandle); + } + glBegin(GL_QUADS); glTexCoord2f(cit->m_TexCoords.right,cit->m_TexCoords.bottom); @@ -231,6 +399,8 @@ void GUIRenderer::Draw(DrawCalls &Calls) glEnd(); + if (cit->m_Effects) + cit->m_Effects->Unset(); } else { diff --git a/source/gui/GUIRenderer.h b/source/gui/GUIRenderer.h index 02b24584f2..74982300f0 100644 --- a/source/gui/GUIRenderer.h +++ b/source/gui/GUIRenderer.h @@ -10,23 +10,23 @@ struct SGUIImageEffects; namespace GUIRenderer { - struct Handle_rfcnt_tex + class IGLState { - Handle h; - Handle_rfcnt_tex(); - Handle_rfcnt_tex(Handle h_); - Handle_rfcnt_tex(const Handle_rfcnt_tex& that); - ~Handle_rfcnt_tex(); - Handle_rfcnt_tex& operator=(Handle h_); + public: + virtual ~IGLState() {}; + virtual void Set(Handle tex)=0; + virtual void Unset()=0; }; struct SDrawCall { - Handle_rfcnt_tex m_TexHandle; + SDrawCall() : m_TexHandle(0), m_Effects(NULL) {} + + Handle m_TexHandle; bool m_EnableBlending; - SGUIImageEffects* m_Effects; + IGLState* m_Effects; CRect m_Vertices; CRect m_TexCoords; @@ -36,7 +36,15 @@ namespace GUIRenderer CColor m_BackColor; }; - typedef std::vector DrawCalls; + class DrawCalls : public std::vector + { + public: + void clear(); + DrawCalls(); + DrawCalls(const DrawCalls&); + const DrawCalls& DrawCalls::operator=(const DrawCalls&); + ~DrawCalls(); + }; } #include "gui/CGUISprite.h" diff --git a/source/gui/GUIutil.cpp b/source/gui/GUIutil.cpp index 96cf02f3d8..29d906853f 100755 --- a/source/gui/GUIutil.cpp +++ b/source/gui/GUIutil.cpp @@ -466,21 +466,21 @@ PS_RESULT GUI::SetSetting(IGUIObject *pObject, const CStr& Setting, const T & // // If setting was "size", we need to re-cache itself and all children - if (Setting == CStr("size")) + if (Setting == "size") { RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize); } else - if (Setting == CStr("hidden")) - { - // Hiding an object requires us to reset it and all children - QueryResetting(pObject); - //RecurseObject(0, pObject, IGUIObject::ResetStates); - } + if (Setting == "hidden") + { + // Hiding an object requires us to reset it and all children + QueryResetting(pObject); + //RecurseObject(0, pObject, IGUIObject::ResetStates); + } - HandleMessage(pObject, SGUIMessage(GUIM_SETTINGS_UPDATED, Setting)); + HandleMessage(pObject, SGUIMessage(GUIM_SETTINGS_UPDATED, Setting)); - return PS_OK; + return PS_OK; } // Instantiate templated functions: