summaryrefslogtreecommitdiffstats
path: root/src/threed/scene/qglrendersequencer.cpp
blob: 0a739ed0f4aef0f2126dd5de5e79d5499a03c1d7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the QtQuick3D module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this
** file. Please review the following information to ensure the GNU Lesser
** General Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU General
** Public License version 3.0 as published by the Free Software Foundation
** and appearing in the file LICENSE.GPL included in the packaging of this
** file. Please review the following information to ensure the GNU General
** Public License version 3.0 requirements will be met:
** http://www.gnu.org/copyleft/gpl.html.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qglrendersequencer.h"
#include "qglrenderorder.h"
#include "qglpainter.h"
#include "qglrenderordercomparator.h"
#include "qglrenderstate.h"

#include <QtCore/qstack.h>

QT_BEGIN_NAMESPACE

/*!
    \class QGLRenderSequencer
    \brief The QGLRenderSequencer class orders the rendering of QGLSceneNode instances.
    \since 4.8
    \ingroup qt3d
    \ingroup qt3d::scene

    The QGLRenderSequencer class works with the QGLRenderOrderComparator and
    QGLRenderOrder classes to optimize the rendering order of scene nodes.

    In general instances of this class are managed by QGLPainter and it should
    not be necessary to explicitly create or manipulate them.

    The render sequencer works by tracking instances of QGLRenderOrder objects
    in a queue.  As the scene graph is traversed during a call to a top-level
    node's QGLSceneNode::draw() function, the sequencer adds one QGLRenderOrder
    to the queue for each unique combination of rendering attributes.

    The top level scene graph node loops once for each unique combination - it
    does this in QGLSceneNode::draw() by calling nextInSequence().  At
    each iteration, a current QGLRenderOrder is maintained, and only nodes
    matching that order - as determined by \l{QGLRenderOrder::operator==()} -
    are rendered in that pass.  Non-matching nodes are added to a queue in the
    order specified by \l{QGLRenderOrder::operator<()}.


    Once an iteration/pass of
    the scene graph is done, the next order is pulled from the front of the queue
    and the current QGLRenderOrder is set to it.

    Since the rendering attributes at a node are a function both of that node,
    and attributes inherited from its parents, and since a given node may appear
    multiple times at different places in the scene, it can thus have different
    attributes and orders in each place.  So there is no one-to-one mapping
    between nodes and attributes.

    To deal with this, QGLRenderOrder mappings are discovered during rendering.
    There is no discovery pass.  First, the initial QGLRenderOrder is lazily set
    when the first geometry is actually drawn to the GPU - latching in that order
    as the first current order.  From that point, orders discovered that are
    distinct from the current one are skipped in this rendering pass - by returning
    false from renderInSequence() - and are instead added to the queue for rendering
    on a subsequent pass.

    When the final pass has been made, renderInSequence() returns false to the
    top level QGLSceneNode, indicating that looping over passes is complete.

    \sa QGLRenderOrder
*/

class QGLRenderSequencerPrivate
{
public:
    QGLRenderSequencerPrivate(QGLPainter *painter);
    ~QGLRenderSequencerPrivate();
    QGLSceneNode *top;
    QLinkedList<QGLRenderOrder> queue;
    QStack<QGLRenderState> stack;
    QSet<QGLRenderOrder> exclude;
    QGLRenderOrder current;
    QGLPainter *painter;
    QGLRenderOrderComparator *compare;
    bool latched;
};

QGLRenderSequencerPrivate::QGLRenderSequencerPrivate(QGLPainter *painter)
    : top(0)
    , current(QGLRenderOrder())
    , painter(painter)
    , compare(new QGLRenderOrderComparator)
    , latched(false)
{
}

QGLRenderSequencerPrivate::~QGLRenderSequencerPrivate()
{
    delete compare;
}

/*!
    Construct a new QGLRenderSequencer for the \a painter.
*/
QGLRenderSequencer::QGLRenderSequencer(QGLPainter *painter)
    : d(new QGLRenderSequencerPrivate(painter))
{
}

/*!
    Sets the render sequencer to operate on \a painter.
*/
void QGLRenderSequencer::setPainter(QGLPainter *painter)
{
    d->painter = painter;
}

/*!
    Returns the current top node of the rendering tree, or NULL if the
    sequencer has been reset.
*/
QGLSceneNode *QGLRenderSequencer::top() const
{
    return d->top;
}

/*!
    Sets the current top node of the rendering tree to \a top.
*/
void QGLRenderSequencer::setTop(QGLSceneNode *top)
{
    d->top = top;
}

/*!
    Reset this sequencer to start from the top of the scene graph again.
    After this call the top() function will return NULL, and any scene
    node passed to the renderInSequence() function will be treated as the
    top of the scene graph.  Also effects and materials will be ignored
    until latched in - QGLPainter::draw() will call latch() to achieve this.

    \sa top()
*/
void QGLRenderSequencer::reset()
{
    d->top = 0;
    d->latched = false;
    d->exclude.clear();
    d->stack.clear();
    d->current = QGLRenderOrder();
}

/*!
    Returns true if there is a next rendering state in the queue; and if there
    is a new order will be pulled from the queue, and its rendering attributes
     - materials, effects and so on - will be applied to the painter for this
    sequencer.  Returns false when no more rendering states are queued and
    scene is completely rendered.
*/
bool QGLRenderSequencer::nextInSequence()
{
    bool nextAvailable = true;
    if (d->queue.size() > 0)
    {
        // process thru next render order
        d->current = d->queue.takeFirst();
    }
    else
    {
        // end top level loop
        nextAvailable = false;
    }
    return nextAvailable;
}

/*!
    Returns true, when this \a node should be rendered, in the order dictated
    by QGLRenderOrder objects generated by the current render order Comparator.
    The \a node must be non-NULL.

    When this function returns false, indicating that rendering should be
    skipped for the current pass, a check is made to ensure that a state object
    for this nodes rendering attributes is queued up for a later pass.

    To customize the ordering, reimplement QGLRenderOrder and
    QGLRenderOrderComparator; then set the custom Comparator onto the current
    painter using setcompare().

    \sa reset(), top(), nextInSequence()
*/
bool QGLRenderSequencer::renderInSequence(QGLSceneNode *node)
{
    Q_ASSERT(node);
    Q_ASSERT(d->top);
    bool doRender = true;
    QGLRenderState state;
    if (!d->stack.empty())
        state = d->stack.top();
    QGLRenderOrder o(node, state);
    if (!d->current.isValid())
        d->current = o;
    if (d->latched && !d->compare->isEqualTo(o, d->current))
    {
        if (!d->exclude.contains(o))
            insertNew(o);
        doRender = false;
    }
    else
    {
        if (!d->latched)
            d->exclude.insert(o);
    }
    return doRender;
}

/*!
    Marks the render state for the \a node as started for this rendering pass.  Call
    this before rendering \a node, or any child nodes of \a node.

    Once the rendering state is no longer valid, call endState().

    To actually apply the effective state, as inherited from previous calls to
    beginState() call the applyState() function.

    \sa endState(), applyState()
*/
void QGLRenderSequencer::beginState(QGLSceneNode *node)
{
    QGLRenderState state;
    if (!d->stack.empty())
        state = d->stack.top();
    state.updateFrom(node);
    d->stack.push(state);
}

/*!
    Marks the render state for the \a node as done for this rendering pass.

    If a node has called beginState(), then this function must be called to maintain
    the rendering stack.  If this function call is not matched by a previous
    beginState() call undefined behaviour may result.  In debug mode it may assert.

    \sa beginState(), applyState()
*/
void QGLRenderSequencer::endState(QGLSceneNode *node)
{
#ifndef QT_NO_DEBUG_STREAM
    const QGLSceneNode *n = d->stack.top().node();
    Q_UNUSED(n);
    Q_UNUSED(node);
    Q_ASSERT(n == node);
#endif
    d->stack.pop();
}

/*!
    Applies the current rendering state to the painter for this sequencer.

    \sa beginState(), endState()
*/
void QGLRenderSequencer::applyState()
{
    d->latched = true;
    QGLRenderState s = d->stack.top();
    if (s.hasEffect() && !d->painter->isPicking())
    {
        if (s.userEffect())
        {
            if (d->painter->userEffect() != s.userEffect())
                d->painter->setUserEffect(s.userEffect());
        }
        else
        {
            if (d->painter->userEffect() ||
                    d->painter->standardEffect() != s.standardEffect())
                d->painter->setStandardEffect(s.standardEffect());
        }
    }
    if (s.material() && !d->painter->isPicking())
    {
        QGLMaterial *mat = s.material();
        if (1) //FIXME: d->painter->faceMaterial(QGL::FrontFaces) != mat)
        {
            d->painter->setFaceMaterial(QGL::FrontFaces, mat);
            int texUnit = 0;
            for (int i = 0; i < mat->textureLayerCount(); ++i)
            {
                QGLTexture2D *tex = mat->texture(i);
                if (tex)
                {
                    d->painter->glActiveTexture(GL_TEXTURE0 + texUnit);
                    tex->bind();
                    ++texUnit;
                }
            }
        }
    }
}

void QGLRenderSequencer::insertNew(const QGLRenderOrder &order)
{
    QLinkedList<QGLRenderOrder>::iterator it = d->queue.begin();
    for ( ; it != d->queue.end(); ++it)
    {
        const QGLRenderOrder o = *it;
        if (d->compare->isLessThan(order, o))
            break;
    }
    d->queue.insert(it, order);
    d->exclude.insert(order);
}

/*!
    Returns the current render order comparator.  By default this is an
    instance of the base class QGLRenderOrderComparator.

    \sa setComparator()
*/
QGLRenderOrderComparator *QGLRenderSequencer::comparator() const
{
    return d->compare;
}

/*!
    Sets the current render order \a comparator.  This is only needed if a
    custom rendering order is to be created.  The argument \a comparator
    must be non-null, or undefined behaviour will result.

    Any existing current comparator is destroyed.

    \sa renderInSequence(), comparator()
*/
void QGLRenderSequencer::setComparator(QGLRenderOrderComparator *comparator)
{
    Q_ASSERT(comparator);
    delete d->compare;
    d->compare = comparator;
}

QT_END_NAMESPACE