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 = ⌖
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