Fix test fails related to QTBUG-22237
[profile/ivi/qtdeclarative.git] / src / declarative / items / qsgspriteengine.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the Declarative module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qsgspriteengine_p.h"
43 #include "qsgsprite_p.h"
44 #include <QDebug>
45 #include <QPainter>
46 #include <QSet>
47 #include <QtGui>
48
49 QT_BEGIN_NAMESPACE
50
51 /* TODO: Split out image logic from stochastic state logic
52    Also make sharable
53    Also solve the state data initialization/transfer issue so as to not need to make friends
54 */
55
56 QSGStochasticEngine::QSGStochasticEngine(QObject *parent) :
57     QObject(parent), m_timeOffset(0)
58 {
59     //Default size 1
60     setCount(1);
61     m_advanceTime.start();
62 }
63
64 QSGStochasticEngine::QSGStochasticEngine(QList<QSGStochasticState*> states, QObject *parent) :
65     QObject(parent), m_states(states), m_timeOffset(0)
66 {
67     //Default size 1
68     setCount(1);
69     m_advanceTime.start();
70 }
71
72 QSGStochasticEngine::~QSGStochasticEngine()
73 {
74 }
75
76 QSGSpriteEngine::QSGSpriteEngine(QObject *parent)
77     : QSGStochasticEngine(parent)
78 {
79 }
80
81 QSGSpriteEngine::QSGSpriteEngine(QList<QSGSprite*> sprites, QObject *parent)
82     : QSGStochasticEngine(parent)
83 {
84     foreach (QSGSprite* sprite, sprites)
85         m_states << (QSGStochasticState*)sprite;
86 }
87
88 QSGSpriteEngine::~QSGSpriteEngine()
89 {
90 }
91
92
93 int QSGSpriteEngine::maxFrames()
94 {
95     return m_maxFrames;
96 }
97
98 /* States too large to fit in one row are split into multiple rows
99    This is more efficient for the implementation, but should remain an implementation detail (invisible from QML)
100    Therefore the below functions abstract sprite from the viewpoint of classes that pass the details onto shaders
101    But States maintain their listed index for internal structures
102 TODO: All these calculations should be pre-calculated and cached during initialization for a significant performance boost
103 TODO: Above idea needs to have the varying duration offset added to it
104 */
105 int QSGSpriteEngine::spriteState(int sprite)
106 {
107     int state = m_things[sprite];
108     if (!m_sprites[state]->m_generatedCount)
109         return state;
110     int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
111     int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
112     return state + extra;
113 }
114
115 int QSGSpriteEngine::spriteStart(int sprite)
116 {
117     int state = m_things[sprite];
118     if (!m_sprites[state]->m_generatedCount)
119         return m_startTimes[sprite];
120     int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
121     int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
122     return state + extra*rowDuration;
123 }
124
125 int QSGSpriteEngine::spriteFrames(int sprite)
126 {
127     int state = m_things[sprite];
128     if (!m_sprites[state]->m_generatedCount)
129         return m_sprites[state]->frames();
130     int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
131     int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
132     if (extra == m_sprites[state]->m_generatedCount - 1)//last state
133         return m_sprites[state]->frames() % m_sprites[state]->m_framesPerRow;
134     else
135         return m_sprites[state]->m_framesPerRow;
136 }
137
138 int QSGSpriteEngine::spriteDuration(int sprite)
139 {
140     int state = m_things[sprite];
141     if (!m_sprites[state]->m_generatedCount)
142         return m_duration[sprite];
143     int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
144     int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
145     if (extra == m_sprites[state]->m_generatedCount - 1)//last state
146         return (m_duration[sprite] * m_sprites[state]->frames()) % rowDuration;
147     else
148         return rowDuration;
149 }
150
151 int QSGSpriteEngine::spriteY(int sprite)
152 {
153     int state = m_things[sprite];
154     if (!m_sprites[state]->m_generatedCount)
155         return m_sprites[state]->m_rowY;
156     int rowDuration = m_duration[sprite] * m_sprites[state]->m_framesPerRow;
157     int extra = (m_timeOffset - m_startTimes[sprite])/rowDuration;
158     return m_sprites[state]->m_rowY + m_sprites[state]->m_frameHeight * extra;
159 }
160
161 int QSGSpriteEngine::spriteWidth(int sprite)
162 {
163     int state = m_things[sprite];
164     return m_sprites[state]->m_frameWidth;
165 }
166
167 int QSGSpriteEngine::spriteHeight(int sprite)
168 {
169     int state = m_things[sprite];
170     return m_sprites[state]->m_frameHeight;
171 }
172
173 int QSGSpriteEngine::spriteCount()//TODO: Actually image state count, need to rename these things to make sense together
174 {
175     return m_imageStateCount;
176 }
177
178 void QSGStochasticEngine::setGoal(int state, int sprite, bool jump)
179 {
180     if (sprite >= m_things.count() || state >= m_states.count())
181         return;
182     if (!jump){
183         m_goals[sprite] = state;
184         return;
185     }
186
187     if (m_things[sprite] == state)
188         return;//Already there
189     m_things[sprite] = state;
190     m_duration[sprite] = m_states[state]->variedDuration();
191     m_goals[sprite] = -1;
192     restart(sprite);
193     emit stateChanged(sprite);
194     emit m_states[state]->entered();
195     return;
196 }
197
198 QImage QSGSpriteEngine::assembledImage()
199 {
200     int h = 0;
201     int w = 0;
202     m_maxFrames = 0;
203     m_imageStateCount = 0;
204     int maxSize = 0;
205
206     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxSize);
207     foreach (QSGStochasticState* s, m_states){
208         QSGSprite* sprite = qobject_cast<QSGSprite*>(s);
209         if (sprite)
210             m_sprites << sprite;
211         else
212             qDebug() << "Error: Non-sprite in QSGSpriteEngine";
213     }
214
215     foreach (QSGSprite* state, m_sprites){
216         if (state->frames() > m_maxFrames)
217             m_maxFrames = state->frames();
218
219         QImage img(state->source().toLocalFile());
220         if (img.isNull()) {
221             qWarning() << "SpriteEngine: loading image failed..." << state->source().toLocalFile();
222             return QImage();
223         }
224
225         //Check that the frame sizes are the same within one engine
226         if (!state->m_frameWidth)
227             state->m_frameWidth = img.width() / state->frames();
228
229         if (!state->m_frameHeight)
230             state->m_frameHeight = img.height();
231
232         if (state->frames() * state->frameWidth() > maxSize){
233             struct helper{
234                 static int divRoundUp(int a, int b){return (a+b-1)/b;}
235             };
236             int rowsNeeded = helper::divRoundUp(state->frames(), helper::divRoundUp(maxSize, state->frameWidth()));
237             if (rowsNeeded * state->frameHeight() > maxSize){
238                 qWarning() << "SpriteEngine: Animation too large to fit in one texture..." << state->source().toLocalFile();
239                 qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
240             }
241             state->m_generatedCount = rowsNeeded;
242             h += state->frameHeight() * rowsNeeded;
243             w = qMax(w, helper::divRoundUp(maxSize, state->frameWidth()));
244             m_imageStateCount += rowsNeeded;
245         }else{
246             h += state->frameHeight();
247             w = qMax(w, state->frameWidth() * state->frames());
248             m_imageStateCount++;
249         }
250     }
251
252     //maxFrames is max number in a line of the texture
253     QImage image(w, h, QImage::Format_ARGB32);
254     image.fill(0);
255     QPainter p(&image);
256     int y = 0;
257     foreach (QSGSprite* state, m_sprites){
258         QImage img(state->source().toLocalFile());
259         int frameWidth = state->m_frameWidth;
260         int frameHeight = state->m_frameHeight;
261         if (img.height() == frameHeight && img.width() <  maxSize){//Simple case
262             p.drawImage(0,y,img);
263             state->m_rowY = y;
264             y += frameHeight;
265         }else{//Chopping up image case
266             state->m_framesPerRow = image.width()/frameWidth;
267             state->m_rowY = y;
268             int x = 0;
269             int curX = 0;
270             int curY = 0;
271             int framesLeft = state->frames();
272             while (framesLeft > 0){
273                 if (image.width() - x + curX <= img.width()){//finish a row in image (dest)
274                     int copied = image.width() - x;
275                     Q_ASSERT(!(copied % frameWidth));//XXX: Just checking
276                     framesLeft -= copied/frameWidth;
277                     p.drawImage(x,y,img.copy(curX,curY,copied,frameHeight));
278                     y += frameHeight;
279                     curX += copied;
280                     x = 0;
281                     if (curX == img.width()){
282                         curX = 0;
283                         curY += frameHeight;
284                     }
285                 }else{//finish a row in img (src)
286                     int copied = img.width() - curX;
287                     Q_ASSERT(!(copied % frameWidth));//XXX: Just checking
288                     framesLeft -= copied/frameWidth;
289                     p.drawImage(x,y,img.copy(curX,curY,copied,frameHeight));
290                     curY += frameHeight;
291                     x += copied;
292                     curX = 0;
293                 }
294             }
295             if (x)
296                 y += frameHeight;
297         }
298     }
299
300     if (image.height() > maxSize){
301         qWarning() << "SpriteEngine: Too many animations to fit in one texture...";
302         qWarning() << "SpriteEngine: Your texture max size today is " << maxSize;
303         return QImage();
304     }
305     return image;
306 }
307
308 void QSGStochasticEngine::setCount(int c)
309 {
310     m_things.resize(c);
311     m_goals.resize(c);
312     m_duration.resize(c);
313     m_startTimes.resize(c);
314 }
315
316 void QSGStochasticEngine::start(int index, int state)
317 {
318     if (index >= m_things.count())
319         return;
320     m_things[index] = state;
321     m_duration[index] = m_states[state]->variedDuration();
322     m_goals[index] = -1;
323     restart(index);
324 }
325
326 void QSGStochasticEngine::stop(int index)
327 {
328     if (index >= m_things.count())
329         return;
330     //Will never change until start is called again with a new state - this is not a 'pause'
331     for (int i=0; i<m_stateUpdates.count(); i++)
332         m_stateUpdates[i].second.removeAll(index);
333 }
334
335 void QSGStochasticEngine::restart(int index)
336 {
337     m_startTimes[index] = m_timeOffset + m_advanceTime.elapsed();
338     int time = m_duration[index] * m_states[m_things[index]]->frames() + m_startTimes[index];
339     for (int i=0; i<m_stateUpdates.count(); i++)
340         m_stateUpdates[i].second.removeAll(index);
341     addToUpdateList(time, index);
342 }
343
344 uint QSGStochasticEngine::updateSprites(uint time)//### would returning a list of changed idxs be faster than signals?
345 {
346     //Sprite State Update;
347     QSet<int> changedIndexes;
348     while (!m_stateUpdates.isEmpty() && time >= m_stateUpdates.first().first){
349         foreach (int idx, m_stateUpdates.first().second){
350             if (idx >= m_things.count())
351                 continue;//TODO: Proper fix(because this does happen and I'm just ignoring it)
352             int stateIdx = m_things[idx];
353             int nextIdx = -1;
354             int goalPath = goalSeek(stateIdx, idx);
355             if (goalPath == -1){//Random
356                 qreal r =(qreal) qrand() / (qreal) RAND_MAX;
357                 qreal total = 0.0;
358                 for (QVariantMap::const_iterator iter=m_states[stateIdx]->m_to.constBegin();
359                     iter!=m_states[stateIdx]->m_to.constEnd(); iter++)
360                     total += (*iter).toReal();
361                 r*=total;
362                 for (QVariantMap::const_iterator iter= m_states[stateIdx]->m_to.constBegin();
363                         iter!=m_states[stateIdx]->m_to.constEnd(); iter++){
364                     if (r < (*iter).toReal()){
365                         bool superBreak = false;
366                         for (int i=0; i<m_states.count(); i++){
367                             if (m_states[i]->name() == iter.key()){
368                                 nextIdx = i;
369                                 superBreak = true;
370                                 break;
371                             }
372                         }
373                         if (superBreak)
374                             break;
375                     }
376                     r -= (*iter).toReal();
377                 }
378             }else{//Random out of shortest paths to goal
379                 nextIdx = goalPath;
380             }
381             if (nextIdx == -1)//No to states means stay here
382                 nextIdx = stateIdx;
383
384             m_things[idx] = nextIdx;
385             m_duration[idx] = m_states[nextIdx]->variedDuration();
386             m_startTimes[idx] = time;
387             if (nextIdx != stateIdx){
388                 changedIndexes << idx;
389                 emit m_states[nextIdx]->entered();
390             }
391             addToUpdateList((m_duration[idx] * m_states[nextIdx]->frames()) + time, idx);
392         }
393         m_stateUpdates.pop_front();
394     }
395
396     m_timeOffset = time;
397     m_advanceTime.start();
398     //TODO: emit this when a psuedostate changes too
399     foreach (int idx, changedIndexes){//Batched so that update list doesn't change midway
400         emit stateChanged(idx);
401     }
402     if (m_stateUpdates.isEmpty())
403         return -1;
404     return m_stateUpdates.first().first;
405 }
406
407 int QSGStochasticEngine::goalSeek(int curIdx, int spriteIdx, int dist)
408 {
409     QString goalName;
410     if (m_goals[spriteIdx] != -1)
411         goalName = m_states[m_goals[spriteIdx]]->name();
412     else
413         goalName = m_globalGoal;
414     if (goalName.isEmpty())
415         return -1;
416     //TODO: caching instead of excessively redoing iterative deepening (which was chosen arbitarily anyways)
417     // Paraphrased - implement in an *efficient* manner
418     for (int i=0; i<m_states.count(); i++)
419         if (m_states[curIdx]->name() == goalName)
420             return curIdx;
421     if (dist < 0)
422         dist = m_states.count();
423     QSGStochasticState* curState = m_states[curIdx];
424     for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
425         iter!=curState->m_to.constEnd(); iter++){
426         if (iter.key() == goalName)
427             for (int i=0; i<m_states.count(); i++)
428                 if (m_states[i]->name() == goalName)
429                     return i;
430     }
431     QSet<int> options;
432     for (int i=1; i<dist; i++){
433         for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
434             iter!=curState->m_to.constEnd(); iter++){
435             int option = -1;
436             for (int j=0; j<m_states.count(); j++)//One place that could be a lot more efficient...
437                 if (m_states[j]->name() == iter.key())
438                     if (goalSeek(j, spriteIdx, i) != -1)
439                         option = j;
440             if (option != -1)
441                 options << option;
442         }
443         if (!options.isEmpty()){
444             if (options.count()==1)
445                 return *(options.begin());
446             int option = -1;
447             qreal r =(qreal) qrand() / (qreal) RAND_MAX;
448             qreal total = 0;
449             for (QSet<int>::const_iterator iter=options.constBegin();
450                 iter!=options.constEnd(); iter++)
451                 total += curState->m_to.value(m_states[(*iter)]->name()).toReal();
452             r *= total;
453             for (QVariantMap::const_iterator iter = curState->m_to.constBegin();
454                 iter!=curState->m_to.constEnd(); iter++){
455                 bool superContinue = true;
456                 for (int j=0; j<m_states.count(); j++)
457                     if (m_states[j]->name() == iter.key())
458                         if (options.contains(j))
459                             superContinue = false;
460                 if (superContinue)
461                     continue;
462                 if (r < (*iter).toReal()){
463                     bool superBreak = false;
464                     for (int j=0; j<m_states.count(); j++){
465                         if (m_states[j]->name() == iter.key()){
466                             option = j;
467                             superBreak = true;
468                             break;
469                         }
470                     }
471                     if (superBreak)
472                         break;
473                 }
474                 r-=(*iter).toReal();
475             }
476             return option;
477         }
478     }
479     return -1;
480 }
481
482 void QSGStochasticEngine::addToUpdateList(uint t, int idx)
483 {
484     for (int i=0; i<m_stateUpdates.count(); i++){
485         if (m_stateUpdates[i].first==t){
486             m_stateUpdates[i].second << idx;
487             return;
488         }else if (m_stateUpdates[i].first > t){
489             QList<int> tmpList;
490             tmpList << idx;
491             m_stateUpdates.insert(i, qMakePair(t, tmpList));
492             return;
493         }
494     }
495     QList<int> tmpList;
496     tmpList << idx;
497     m_stateUpdates << qMakePair(t, tmpList);
498 }
499
500 QT_END_NAMESPACE