1 /** @file glstate.cpp  GL state.
2  *
3  * @todo This implementation assumes OpenGL drawing occurs only in one thread.
4  * If multithreaded rendering is done at some point in the future, the GL state
5  * stack must be part of the thread-local data.
6  *
7  * @authors Copyright (c) 2013-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
8  *
9  * @par License
10  * LGPL: http://www.gnu.org/licenses/lgpl.html
11  *
12  * <small>This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU Lesser General Public License as published by
14  * the Free Software Foundation; either version 3 of the License, or (at your
15  * option) any later version. This program is distributed in the hope that it
16  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
17  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
18  * General Public License for more details. You should have received a copy of
19  * the GNU Lesser General Public License along with this program; if not, see:
20  * http://www.gnu.org/licenses</small>
21  */
22 
23 #include "de/GLState"
24 #include "de/GLFramebuffer"
25 #include "de/GLWindow"
26 #include "de/graphics/opengl.h"
27 #include <de/GLInfo>
28 #include <de/BitField>
29 #include <de/Log>
30 
31 namespace de {
32 
33 #ifdef DENG2_DEBUG
34 extern int GLDrawQueue_queuedElems;
35 #endif
36 
37 namespace internal
38 {
39     enum Property {
40         CullMode,
41         DepthTest,
42         DepthFunc,
43         DepthWrite,
44         AlphaTest,
45         AlphaLimit,
46         Blend,
47         BlendFuncSrc,
48         BlendFuncDest,
49         BlendOp,
50         ColorMask,
51         Scissor,
52         ScissorX,
53         ScissorY,
54         ScissorWidth,
55         ScissorHeight,
56         ViewportX,
57         ViewportY,
58         ViewportWidth,
59         ViewportHeight,
60         MAX_PROPERTIES
61     };
62 
63     static BitField::Spec const propSpecs[MAX_PROPERTIES] = {
64         { CullMode,       2  },
65         { DepthTest,      1  },
66         { DepthFunc,      3  },
67         { DepthWrite,     1  },
68         { AlphaTest,      1  },
69         { AlphaLimit,     8  },
70         { Blend,          1  },
71         { BlendFuncSrc,   4  },
72         { BlendFuncDest,  4  },
73         { BlendOp,        2  },
74         { ColorMask,      4  },
75         { Scissor,        1  },
76         { ScissorX,       13 }, // 13 bits == 8192 max
77         { ScissorY,       13 },
78         { ScissorWidth,   13 },
79         { ScissorHeight,  13 },
80         { ViewportX,      13 },
81         { ViewportY,      13 },
82         { ViewportWidth,  13 },
83         { ViewportHeight, 13 }
84     };
85     static BitField::Elements const glStateProperties(propSpecs, MAX_PROPERTIES);
86 
87     /// The GL state stack.
88     struct GLStateStack : public QList<GLState *> {
GLStateStackde::internal::GLStateStack89         GLStateStack() {
90             // Initialize with a default state.
91             append(new GLState);
92         }
~GLStateStackde::internal::GLStateStack93         ~GLStateStack() { qDeleteAll(*this); }
94     };
95     static GLStateStack stack;
96 
97     /// Currently applied GL state properties.
98     static BitField currentProps;
99 
100     /// Observes the current target and clears the pointer if it happens to get
101     /// deleted.
102     class CurrentTarget : DENG2_OBSERVES(Asset, Deletion) {
103         GLFramebuffer *_target;
assetBeingDeleted(Asset & asset)104         void assetBeingDeleted(Asset &asset) {
105             if (&asset == _target) {
106                 LOG_AS("GLState");
107                 LOGDEV_GL_NOTE("Current target destroyed, clearing pointer");
108                 _target = 0;
109             }
110         }
111     public:
CurrentTarget()112         CurrentTarget() : _target(0) {}
~CurrentTarget()113         ~CurrentTarget() {
114             set(0);
115         }
set(GLFramebuffer * trg)116         void set(GLFramebuffer *trg) {
117             if (_target) {
118                 _target->audienceForDeletion() -= this;
119             }
120             _target = trg;
121             if (_target) {
122                 _target->audienceForDeletion() += this;
123             }
124         }
operator =(GLFramebuffer * trg)125         CurrentTarget &operator = (GLFramebuffer *trg) {
126             set(trg);
127             return *this;
128         }
operator !=(GLFramebuffer * trg) const129         bool operator != (GLFramebuffer *trg) const {
130             return _target != trg;
131         }
get() const132         GLFramebuffer *get() const {
133             return _target;
134         }
operator GLFramebuffer*() const135         operator GLFramebuffer *() const {
136             return _target;
137         }
138     };
139     static CurrentTarget currentTarget;
140 }
141 
DENG2_PIMPL(GLState)142 DENG2_PIMPL(GLState)
143 {
144     BitField       props;
145     GLFramebuffer *target;
146 
147     Impl(Public *i)
148         : Base(i)
149         , props(internal::glStateProperties)
150         , target(0)
151     {}
152 
153     Impl(Public *i, Impl const &other)
154         : Base(i)
155         , props(other.props)
156         , target(other.target)
157     {}
158 
159     static GLenum glComp(gl::Comparison comp)
160     {
161         switch (comp)
162         {
163         case gl::Never:          return GL_NEVER;
164         case gl::Always:         return GL_ALWAYS;
165         case gl::Equal:          return GL_EQUAL;
166         case gl::NotEqual:       return GL_NOTEQUAL;
167         case gl::Less:           return GL_LESS;
168         case gl::Greater:        return GL_GREATER;
169         case gl::LessOrEqual:    return GL_LEQUAL;
170         case gl::GreaterOrEqual: return GL_GEQUAL;
171         }
172         return GL_NEVER;
173     }
174 
175     static GLenum glBFunc(gl::Blend f)
176     {
177         switch (f)
178         {
179         case gl::Zero:              return GL_ZERO;
180         case gl::One:               return GL_ONE;
181         case gl::SrcColor:          return GL_SRC_COLOR;
182         case gl::OneMinusSrcColor:  return GL_ONE_MINUS_SRC_COLOR;
183         case gl::SrcAlpha:          return GL_SRC_ALPHA;
184         case gl::OneMinusSrcAlpha:  return GL_ONE_MINUS_SRC_ALPHA;
185         case gl::DestColor:         return GL_DST_COLOR;
186         case gl::OneMinusDestColor: return GL_ONE_MINUS_DST_COLOR;
187         case gl::DestAlpha:         return GL_DST_ALPHA;
188         case gl::OneMinusDestAlpha: return GL_ONE_MINUS_DST_ALPHA;
189         }
190         return GL_ZERO;
191     }
192 
193     static gl::Blend fromGlBFunc(GLenum e)
194     {
195         switch (e)
196         {
197         case GL_ZERO: return gl::Zero;
198         case GL_ONE: return gl::One;
199         case GL_SRC_COLOR: return gl::SrcColor;
200         case GL_ONE_MINUS_SRC_COLOR: return gl::OneMinusSrcColor;
201         case GL_SRC_ALPHA: return gl::SrcAlpha;
202         case GL_ONE_MINUS_SRC_ALPHA: return gl::OneMinusSrcAlpha;
203         case GL_DST_COLOR: return gl::DestColor;
204         case GL_ONE_MINUS_DST_COLOR: return gl::OneMinusDestColor;
205         case GL_DST_ALPHA: return gl::DestAlpha;
206         case GL_ONE_MINUS_DST_ALPHA: return gl::OneMinusDestAlpha;
207         }
208         return gl::Zero;
209     }
210 
211     void glApply(internal::Property prop)
212     {
213         switch (prop)
214         {
215         case internal::CullMode:
216             switch (self().cull())
217             {
218             case gl::None:
219                 LIBGUI_GL.glDisable(GL_CULL_FACE);
220                 break;
221             case gl::Front:
222                 LIBGUI_GL.glEnable(GL_CULL_FACE);
223                 LIBGUI_GL.glCullFace(GL_FRONT);
224                 break;
225             case gl::Back:
226                 LIBGUI_GL.glEnable(GL_CULL_FACE);
227                 LIBGUI_GL.glCullFace(GL_BACK);
228                 break;
229             }
230             break;
231 
232         case internal::DepthTest:
233             if (self().depthTest())
234                 LIBGUI_GL.glEnable(GL_DEPTH_TEST);
235             else
236                 LIBGUI_GL.glDisable(GL_DEPTH_TEST);
237             break;
238 
239         case internal::DepthFunc:
240             LIBGUI_GL.glDepthFunc(glComp(self().depthFunc()));
241             break;
242 
243         case internal::DepthWrite:
244             if (self().depthWrite())
245                 LIBGUI_GL.glDepthMask(GL_TRUE);
246             else
247                 LIBGUI_GL.glDepthMask(GL_FALSE);
248             break;
249 
250         case internal::AlphaTest:
251             /*if (self().alphaTest())
252                 LIBGUI_GL.glEnable(GL_ALPHA_TEST);
253             else
254                 LIBGUI_GL.glDisable(GL_ALPHA_TEST);*/
255 
256             // TODO: use a shared GLUniform available to all shaders that need it
257 
258             //qDebug() << "[GLState] Alpha test:" << (self().alphaTest()? "enabled" : "disabled");
259             break;
260 
261         case internal::AlphaLimit:
262             //LIBGUI_GL.glAlphaFunc(GL_GREATER, self().alphaLimit());
263 
264             // TODO: use a shared GLUniform available to all shaders that need it
265 
266             break;
267 
268         case internal::Blend:
269             if (self().blend())
270                 LIBGUI_GL.glEnable(GL_BLEND);
271             else
272                 LIBGUI_GL.glDisable(GL_BLEND);
273             break;
274 
275         case internal::BlendFuncSrc:
276         case internal::BlendFuncDest:
277             //glBlendFunc(glBFunc(self().srcBlendFunc()), glBFunc(self().destBlendFunc()));
278             LIBGUI_GL.glBlendFuncSeparate(glBFunc(self().srcBlendFunc()), glBFunc(self().destBlendFunc()),
279                                           GL_ONE, GL_ONE);
280             break;
281 
282         case internal::BlendOp:
283             switch (self().blendOp())
284             {
285             case gl::Add:
286                 LIBGUI_GL.glBlendEquation(GL_FUNC_ADD);
287                 break;
288             case gl::Subtract:
289                 LIBGUI_GL.glBlendEquation(GL_FUNC_SUBTRACT);
290                 break;
291             case gl::ReverseSubtract:
292                 LIBGUI_GL.glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
293                 break;
294             }
295             break;
296 
297         case internal::ColorMask:
298         {
299             gl::ColorMask const mask = self().colorMask();
300             LIBGUI_GL.glColorMask((mask & gl::WriteRed)   != 0,
301                                   (mask & gl::WriteGreen) != 0,
302                                   (mask & gl::WriteBlue)  != 0,
303                                   (mask & gl::WriteAlpha) != 0);
304             break;
305         }
306 
307         case internal::Scissor:
308         case internal::ScissorX:
309         case internal::ScissorY:
310         case internal::ScissorWidth:
311         case internal::ScissorHeight:
312         {
313             if (self().scissor() || self().target().hasActiveRect())
314             {
315                 LIBGUI_GL.glEnable(GL_SCISSOR_TEST);
316 
317                 Rectangleui origScr;
318                 if (self().scissor())
319                 {
320                     origScr = self().scissorRect();
321                 }
322                 else
323                 {
324                     origScr = Rectangleui::fromSize(self().target().size());
325                 }
326 
327                 Rectangleui const scr = self().target().scaleToActiveRect(origScr);
328                 LIBGUI_GL.glScissor(scr.left(), self().target().size().y - scr.bottom(),
329                                     scr.width(), scr.height());
330             }
331             else
332             {
333                 LIBGUI_GL.glDisable(GL_SCISSOR_TEST);
334             }
335             break;
336         }
337 
338         case internal::ViewportX:
339         case internal::ViewportY:
340         case internal::ViewportWidth:
341         case internal::ViewportHeight:
342         {
343             Rectangleui const vp = self().target().scaleToActiveRect(self().viewport());
344             //qDebug() << "glViewport" << vp.asText();
345 
346             LIBGUI_GL.glViewport(vp.left(), self().target().size().y - vp.bottom(),
347                                  vp.width(), vp.height());
348             break;
349         }
350 
351         default:
352             break;
353         }
354 
355         LIBGUI_ASSERT_GL_OK();
356     }
357 
358     void removeRedundancies(BitField::Ids &changed)
359     {
360         if (changed.contains(internal::BlendFuncSrc) && changed.contains(internal::BlendFuncDest))
361         {
362             changed.remove(internal::BlendFuncDest);
363         }
364 
365         if (changed.contains(internal::ScissorX) || changed.contains(internal::ScissorY) ||
366             changed.contains(internal::ScissorWidth) || changed.contains(internal::ScissorHeight))
367         {
368             changed.insert(internal::ScissorX);
369             changed.remove(internal::ScissorY);
370             changed.remove(internal::ScissorWidth);
371             changed.remove(internal::ScissorHeight);
372         }
373 
374         if (changed.contains(internal::ViewportX) || changed.contains(internal::ViewportY) ||
375             changed.contains(internal::ViewportWidth) || changed.contains(internal::ViewportHeight))
376         {
377             changed.insert(internal::ViewportX);
378             changed.remove(internal::ViewportY);
379             changed.remove(internal::ViewportWidth);
380             changed.remove(internal::ViewportHeight);
381         }
382     }
383 };
384 
GLState()385 GLState::GLState() : d(new Impl(this))
386 {
387     setCull      (gl::None);
388     setDepthTest (false);
389     setDepthFunc (gl::Less);
390     setDepthWrite(true);
391     setAlphaTest (true);
392     setAlphaLimit(0);
393     setBlend     (true);
394     setBlendFunc (gl::One, gl::Zero);
395     setBlendOp   (gl::Add);
396     setColorMask (gl::WriteAll);
397 
398     setDefaultTarget();
399 }
400 
GLState(GLState const & other)401 GLState::GLState(GLState const &other) : d(new Impl(this, *other.d))
402 {}
403 
operator =(GLState const & other)404 GLState &GLState::operator=(GLState const &other)
405 {
406     d.reset(new Impl(this, *other.d));
407     return *this;
408 }
409 
operator ==(const GLState & other)410 bool GLState::operator==(const GLState &other)
411 {
412     return d->target == other.d->target && d->props == other.d->props;
413 }
414 
setCull(gl::Cull mode)415 GLState &GLState::setCull(gl::Cull mode)
416 {
417     d->props.set(internal::CullMode, duint(mode));
418     return *this;
419 }
420 
setDepthTest(bool enable)421 GLState &GLState::setDepthTest(bool enable)
422 {
423     d->props.set(internal::DepthTest, enable);
424     return *this;
425 }
426 
setDepthFunc(gl::Comparison func)427 GLState &GLState::setDepthFunc(gl::Comparison func)
428 {
429     d->props.set(internal::DepthFunc, duint(func));
430     return *this;
431 }
432 
setDepthWrite(bool enable)433 GLState &GLState::setDepthWrite(bool enable)
434 {
435     d->props.set(internal::DepthWrite, enable);
436     return *this;
437 }
438 
setAlphaTest(bool enable)439 GLState &GLState::setAlphaTest(bool enable)
440 {
441     d->props.set(internal::AlphaTest, enable);
442     return *this;
443 }
444 
setAlphaLimit(float greaterThanValue)445 GLState &GLState::setAlphaLimit(float greaterThanValue)
446 {
447     d->props.set(internal::AlphaLimit, unsigned(clamp(0.f, greaterThanValue, 1.f) * 255));
448     return *this;
449 }
450 
setBlend(bool enable)451 GLState &GLState::setBlend(bool enable)
452 {
453     d->props.set(internal::Blend, enable);
454     return *this;
455 }
456 
setBlendFunc(gl::Blend src,gl::Blend dest)457 GLState &GLState::setBlendFunc(gl::Blend src, gl::Blend dest)
458 {
459     d->props.set(internal::BlendFuncSrc,  duint(src));
460     d->props.set(internal::BlendFuncDest, duint(dest));
461     return *this;
462 }
463 
setBlendFunc(gl::BlendFunc func)464 GLState &GLState::setBlendFunc(gl::BlendFunc func)
465 {
466     d->props.set(internal::BlendFuncSrc,  duint(func.first));
467     d->props.set(internal::BlendFuncDest, duint(func.second));
468     return *this;
469 }
470 
setBlendOp(gl::BlendOp op)471 GLState &GLState::setBlendOp(gl::BlendOp op)
472 {
473     d->props.set(internal::BlendOp, duint(op));
474     return *this;
475 }
476 
setColorMask(gl::ColorMask mask)477 GLState &GLState::setColorMask(gl::ColorMask mask)
478 {
479     d->props.set(internal::ColorMask, duint(mask));
480     return *this;
481 }
482 
setTarget(GLFramebuffer & target)483 GLState &GLState::setTarget(GLFramebuffer &target)
484 {
485     d->target = &target;
486     return *this;
487 }
488 
setDefaultTarget()489 GLState &GLState::setDefaultTarget()
490 {
491     d->target = 0;
492     return *this;
493 }
494 
setViewport(Rectanglei const & viewportRect)495 GLState &GLState::setViewport(Rectanglei const &viewportRect)
496 {
497     return setViewport(viewportRect.toRectangleui());
498 }
499 
setViewport(Rectangleui const & viewportRect)500 GLState &GLState::setViewport(Rectangleui const &viewportRect)
501 {
502     d->props.set(internal::ViewportX,      viewportRect.left());
503     d->props.set(internal::ViewportY,      viewportRect.top());
504     d->props.set(internal::ViewportWidth,  viewportRect.width());
505     d->props.set(internal::ViewportHeight, viewportRect.height());
506     return *this;
507 }
508 
setNormalizedViewport(Rectanglef const & normViewportRect)509 GLState &GLState::setNormalizedViewport(Rectanglef const &normViewportRect)
510 {
511     GLFramebuffer::Size const size = target().size();
512     Rectangleui vp(Vector2ui(normViewportRect.left() * size.x,
513                              normViewportRect.top()  * size.y),
514                    Vector2ui(std::ceil(normViewportRect.right()  * size.x),
515                              std::ceil(normViewportRect.bottom() * size.y)));
516     return setViewport(vp);
517 }
518 
setScissor(Rectanglei const & scissorRect)519 GLState &GLState::setScissor(Rectanglei const &scissorRect)
520 {
521     return setScissor(scissorRect.toRectangleui());
522 }
523 
setScissor(Rectangleui const & newScissorRect)524 GLState &GLState::setScissor(Rectangleui const &newScissorRect)
525 {
526     Rectangleui cumulative;
527     if (scissor())
528     {
529         cumulative = scissorRect() & newScissorRect;
530     }
531     else
532     {
533         cumulative = newScissorRect;
534     }
535 
536     d->props.set(internal::Scissor,       true);
537     d->props.set(internal::ScissorX,      cumulative.left());
538     d->props.set(internal::ScissorY,      cumulative.top());
539     d->props.set(internal::ScissorWidth,  cumulative.width());
540     d->props.set(internal::ScissorHeight, cumulative.height());
541     return *this;
542 }
543 
setNormalizedScissor(Rectanglef const & normScissorRect)544 GLState &GLState::setNormalizedScissor(Rectanglef const &normScissorRect)
545 {
546     Rectangleui vp = viewport();
547     Rectanglei scis(Vector2i(normScissorRect.left()   * vp.width(),
548                              normScissorRect.top()    * vp.height()),
549                     Vector2i(std::ceil(normScissorRect.right()  * vp.width()),
550                              std::ceil(normScissorRect.bottom() * vp.height())));
551     return setScissor(scis.moved(vp.topLeft.toVector2i()));
552 }
553 
clearScissor()554 GLState &GLState::clearScissor()
555 {
556     d->props.set(internal::Scissor,       false);
557     d->props.set(internal::ScissorX,      0u);
558     d->props.set(internal::ScissorY,      0u);
559     d->props.set(internal::ScissorWidth,  0u);
560     d->props.set(internal::ScissorHeight, 0u);
561     return *this;
562 }
563 
cull() const564 gl::Cull GLState::cull() const
565 {
566     return d->props.valueAs<gl::Cull>(internal::CullMode);
567 }
568 
depthTest() const569 bool GLState::depthTest() const
570 {
571     return d->props.asBool(internal::DepthTest);
572 }
573 
depthFunc() const574 gl::Comparison GLState::depthFunc() const
575 {
576     return d->props.valueAs<gl::Comparison>(internal::DepthFunc);
577 }
578 
depthWrite() const579 bool GLState::depthWrite() const
580 {
581     return d->props.asBool(internal::DepthWrite);
582 }
583 
alphaTest() const584 bool GLState::alphaTest() const
585 {
586     return d->props.asBool(internal::AlphaTest);
587 }
588 
alphaLimit() const589 float GLState::alphaLimit() const
590 {
591     return float(d->props.asUInt(internal::AlphaLimit)) / 255.f;
592 }
593 
blend() const594 bool GLState::blend() const
595 {
596     return d->props.asBool(internal::Blend);
597 }
598 
srcBlendFunc() const599 gl::Blend GLState::srcBlendFunc() const
600 {
601     return d->props.valueAs<gl::Blend>(internal::BlendFuncSrc);
602 }
603 
destBlendFunc() const604 gl::Blend GLState::destBlendFunc() const
605 {
606     return d->props.valueAs<gl::Blend>(internal::BlendFuncDest);
607 }
608 
blendFunc() const609 gl::BlendFunc GLState::blendFunc() const
610 {
611     return gl::BlendFunc(srcBlendFunc(), destBlendFunc());
612 }
613 
blendOp() const614 gl::BlendOp GLState::blendOp() const
615 {
616     return d->props.valueAs<gl::BlendOp>(internal::BlendOp);
617 }
618 
colorMask() const619 gl::ColorMask GLState::colorMask() const
620 {
621     return d->props.valueAs<gl::ColorMask>(internal::ColorMask);
622 }
623 
target() const624 GLFramebuffer &GLState::target() const
625 {
626     if (d->target)
627     {
628         return *d->target;
629     }
630     return GLWindow::main().framebuffer();
631 }
632 
viewport() const633 Rectangleui GLState::viewport() const
634 {
635     return Rectangleui(d->props[internal::ViewportX],
636                        d->props[internal::ViewportY],
637                        d->props[internal::ViewportWidth],
638                        d->props[internal::ViewportHeight]);
639 }
640 
normalizedViewport() const641 Rectanglef GLState::normalizedViewport() const
642 {
643     GLFramebuffer::Size const size = target().size();
644     Rectangleui const vp = viewport();
645     return Rectanglef(float(vp.left())   / float(size.x),
646                       float(vp.top())    / float(size.y),
647                       float(vp.width())  / float(size.x),
648                       float(vp.height()) / float(size.y));
649 }
650 
scissor() const651 bool GLState::scissor() const
652 {
653     return d->props.asBool(internal::Scissor);
654 }
655 
scissorRect() const656 Rectangleui GLState::scissorRect() const
657 {
658     return Rectangleui(d->props[internal::ScissorX],
659                        d->props[internal::ScissorY],
660                        d->props[internal::ScissorWidth],
661                        d->props[internal::ScissorHeight]);
662 }
663 
apply() const664 void GLState::apply() const
665 {
666     LIBGUI_ASSERT_GL_OK();
667 
668 #ifdef DENG2_DEBUG
669     DENG2_ASSERT(GLDrawQueue_queuedElems == 0);
670 #endif
671 
672     // Actual OpenGL state shouldn't be changed outside the render thread.
673     // The main thread can still manipulate shared OpenGL objects, though.
674     DENG2_ASSERT_IN_RENDER_THREAD();
675 
676     bool forceViewportAndScissor = false;
677 
678     // Update the render target.
679     GLFramebuffer *newTarget = &target();
680     DENG2_ASSERT(newTarget != 0);
681 
682     if (internal::currentTarget != newTarget)
683     {
684         GLFramebuffer const *oldTarget = internal::currentTarget;
685         if (oldTarget)
686         {
687             oldTarget->glRelease();
688         }
689 
690         internal::currentTarget = newTarget;
691         newTarget->glBind();
692 
693         if ((oldTarget && oldTarget->hasActiveRect()) || newTarget->hasActiveRect())
694         {
695             // We can't trust that the viewport or scissor can remain the same
696             // as the active rectangle may have changed.
697             forceViewportAndScissor = true;
698         }
699     }
700 
701     LIBGUI_ASSERT_GL_OK();
702 
703     // Determine which properties have changed.
704     BitField::Ids changed;
705     if (internal::currentProps.isEmpty())
706     {
707         // Apply everything.
708         changed = d->props.elements().ids();
709     }
710     else
711     {
712         // Just apply the changed parts of the state.
713         changed = d->props.delta(internal::currentProps);
714 
715         if (forceViewportAndScissor)
716         {
717             changed.insert(internal::ViewportX);
718             changed.insert(internal::ScissorX);
719         }
720     }
721 
722     if (!changed.isEmpty())
723     {
724         d->removeRedundancies(changed);
725 
726         // Apply the changed properties.
727         foreach (BitField::Id id, changed)
728         {
729             d->glApply(internal::Property(id));
730         }
731         internal::currentProps = d->props;
732     }
733 
734 #if 0
735     // Verify that the state is correct.
736     for (int i = 0; i < d->props.elements().size(); ++i)
737     {
738         auto const &elem = d->props.elements().at(i);
739         int val;
740         switch (elem.id)
741         {
742         case internal::Blend:
743             LIBGUI_GL.glGetIntegerv(GL_BLEND, &val);
744             DENG2_ASSERT(!val == !d->props.asBool(elem.id));
745             break;
746 
747         case internal::BlendFuncSrc:
748             LIBGUI_GL.glGetIntegerv(GL_BLEND_SRC_RGB, &val);
749             DENG2_ASSERT(d->fromGlBFunc(val) == d->props.asUInt(elem.id));
750             break;
751 
752         case internal::BlendFuncDest:
753             LIBGUI_GL.glGetIntegerv(GL_BLEND_DST_RGB, &val);
754             DENG2_ASSERT(d->fromGlBFunc(val) == d->props.asUInt(elem.id));
755             break;
756 
757         case internal::BlendOp:
758             LIBGUI_GL.glGetIntegerv(GL_BLEND_EQUATION_RGB, &val);
759             val = (val == GL_FUNC_ADD? gl::Add :
760                    val == GL_FUNC_SUBTRACT? gl::Subtract :
761                    val == GL_FUNC_REVERSE_SUBTRACT? gl::ReverseSubtract : 0);
762             DENG2_ASSERT(val == d->props.asUInt(elem.id));
763             break;
764         }
765     }
766 #endif
767 }
768 
considerNativeStateUndefined()769 void GLState::considerNativeStateUndefined()
770 {
771     internal::currentProps.clear();
772     internal::currentTarget = 0;
773 }
774 
current()775 GLState &GLState::current()
776 {
777     DENG2_ASSERT(!internal::stack.isEmpty());
778     return *internal::stack.last();
779 }
780 
push()781 GLState &GLState::push()
782 {
783     // Duplicate the topmost state.
784     push(new GLState(current()));
785     return current();
786 }
787 
pop()788 GLState &GLState::pop()
789 {
790     delete take();
791     return current();
792 }
793 
push(GLState * state)794 void GLState::push(GLState *state)
795 {
796     internal::stack.append(state);
797 }
798 
take()799 GLState *GLState::take()
800 {
801     DENG2_ASSERT(internal::stack.size() > 1);
802     return internal::stack.takeLast();
803 }
804 
stackDepth()805 dsize GLState::stackDepth()
806 {
807     return internal::stack.size();
808 }
809 
810 } // namespace de
811