LunaSysMgr
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
pixpager.h
Go to the documentation of this file.
1 /* @@@LICENSE
2 *
3 * Copyright (c) 2010-2012 Hewlett-Packard Development Company, L.P.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 * LICENSE@@@ */
18 
19 
20 
21  /*
22  * This is a simple implementation of what is known colloquially as a "pixmap atlas"
23  * It also manages something similar to the QPixmapCache, except it operates on PixmapObjects instead
24  * (see pixmapobject.h/cpp)
25  * The general idea is that these operate on two levels. Small pixmaps are aggregated into "atlas" pages
26  * so that they can be accessed faster. The atlas pages along with larger pixmaps that won't fit onto atlas pages
27  * are cached into a pixmap cache
28  *
29  * a word on the atlas pages:
30  * Atlases were written up by many others (Nvidia most prominently) as a way to contain small, tile or icon -type
31  * pixmaps in a larger pixmap which could be held in (gpu) memory. This way, fragment shaders and textures don't need
32  * to be swapped in and out of memory; One simply provides the coordinate in the big texture of the smaller texture
33  * desired. This can be expressed very efficiently in the shader program as just a texture mapping matrix
34  *
35  * In this implementation, I'll use the same basic idea with a few tweaks. First, because my atlas is geared towards
36  * basically just storing icons and things of that nature, and never larger items, it can have some constraints. Most
37  * icons are either 16x16,32x32,48x48,64x64,128x128,or 256x256. These can then be partitioned into separate pages so that
38  * the icons can be packed within w/ minimal waste of space. So e.g. one page is designated the "32 page" and only
39  * holds 32x32 icons, and these can all be packed neatly into row-columns since they're the same, square size.
40  * Each page that is an atlas page has a directory member that is a hash keyed on the individual icon's uid,
41  * and holds a value that's a rectangle R and a size S. The R is the coordinate rect inside the pixmap of that page
42  * (i.e. the whole atlas page with all the icons inside it) that describes where the icon actually is, and S is the
43  * size of the *original* icon, before it was placed inside the atlas page. The reason for S is that under a certain
44  * optimization scenario, an icon that is just slightly different in size from the size of the icons that a particular
45  * atlas page supports, then the icon will be scaled up/down to match the size. When it is retrieved, this has to be
46  * reversed, so the original size is needed. e.g. a 30x30 icon could be stored in a 32x32-designated page.
47  * If the scaling factor is small (especially if it's a scale-up), then the negative visual effects will be minimal
48  *
49  * NOT THREAD SAFE! among other things, the most blatant problem is that getPixmap()'s returned QPixmap must remain
50  * valid throughout the function that made the call. This will not be guaranteed with multiple threads, as a cache purge
51  * triggered by another thread could wipe out that QPixmap
52  */
53 
54 #ifndef PIXPAGER_H_
55 #define PIXPAGER_H_
56 
57 #include <QObject>
58 #include <QPointer>
59 #include <QUuid>
60 #include <QRect>
61 #include "dimensionsglobal.h"
62 #include <QHash>
63 #include <QMap>
64 #include <QPair>
65 #include <QList>
66 
68 
69 class PixPager;
70 class PixmapObject;
71 class QPixmap;
72 
73 /*
74  * PixPagerPage is not meant to be used externally; it's for PixPager use only
75  *
76  * PixPagerPage shouldn't do anything 'smart' on its own. It's simply to contain data for
77  * PixPager to manage, and to notify pixpager when stuff happens (like the pixmap deleted by someone else;
78  * which should be kept to a minimum with proper pixpager usage)
79  *
80  */
81 
82 class PixPagerPage : public QObject
83 {
84  Q_OBJECT
85 public:
86 
88  {
89  public:
91  PixmapRects(const QRect& r,const QSize& s)
92  : coordinateRect(r) , originalSize(s) {}
94  QSize originalSize; //will be different than the size of the coordinateRect if the pixmap was
95  //scaled to fit into the atlas page (see explanation of atlas pages at the
96  //top of this file)
97  };
98 
99  PixPagerPage(PixPager * p_pager);
100  virtual ~PixPagerPage();
101 
102  QPointer<PixPager> m_pager;
103  QPointer<PixmapObject> m_data;
104  bool m_pinned;
105  quint32 m_sizeInBytes;
106 
108  qreal m_accessFrequency; //a proportion of accesses to this page vs the total of accesses for all pages
109 
111 
112 public Q_SLOTS:
113 
114  virtual void slotPixmapObjectDeleted();
115 
116 };
117 
119 {
120  Q_OBJECT
121 public:
122 
123  PixPagerAtlasPage(PixPager * p_pager,quint32 sizeDesignation,QHash<QUuid,PixmapRects> * p_directory = 0);
124  virtual ~PixPagerAtlasPage();
125 
126  //NOTE: the way to determine the occupancy (and thus free space) of this atlas page is:
127  // free cells = m_rows * m_columns - m_p_directory->size()
128  quint32 freeSpace() const
129  {
130  return (m_rows * m_columns - m_directory.size());
131  }
132  qreal occupancyRate() const
133  {
134  if ((quint32)(m_directory.size()) == m_rows*m_columns)
135  return 1.0; //make sure that below doesn't return something dumb like 0.99999999....
136  return (qreal)(m_directory.size())/(qreal)(m_rows*m_columns);
137  }
138 
139  bool getFromFreelist(QPair<quint32,quint32>& r_location)
140  {
141  if (m_freeList.empty())
142  return false;
143  r_location = m_freeList.takeFirst();
144  return true;
145  }
146  bool unoccupiedLocation(QPair<quint32,quint32>& r_location)
147  {
149  return getFromFreelist(r_location);
150  //go sequentially
151  if ((m_lastPositionAllocated.first == m_columns-1) && (m_lastPositionAllocated.second == m_rows-1))
152  return getFromFreelist(r_location);
153  else if (m_lastPositionAllocated.first == m_columns-1)
154  {
155  //next row, col=0
156  r_location = QPair<quint32,quint32>(0,m_lastPositionAllocated.second+1);
157  }
158  else
159  {
160  //next column, same row
161  r_location = QPair<quint32,quint32>(m_lastPositionAllocated.first+1,m_lastPositionAllocated.second);
162  }
163  return true;
164  }
165 
166  QRect targetRectForGridCoordinates(const QPair<quint32,quint32>& gridCoordinates) const;
167 
168  QHash<QUuid,PixmapRects> m_directory; //keeps the locations of the contained
169  //mini-pm's keyed on their uids null if the page isn't an atlas
170  quint32 m_pixmapSqSize; //the size designation for this atlas page...32 -> 32x32 sq. pixmaps
171  //this is also the classId passed back to the add functions
172  QList<QPair<quint32,quint32> > m_freeList;
173 
174  quint32 m_rows;
175  quint32 m_columns;
182  QPair<quint32,quint32> m_lastPositionAllocated;
183  static QPair<quint32,quint32> INVALID_LOCATION;
184 };
185 
186 namespace PageOpsReturnCode
187 {
188  enum Enum
189  {
190  OK,
194  };
195 }
196 
197 class PixPager : public QObject
198 {
199  Q_OBJECT
200 
201 public:
202  PixPager();
203  virtual ~PixPager();
204 
205  //general function; will lookup both atlas and non-atlas based uids
206  PixmapObject * getPixmap(const QUuid& uid,QRect& r_coordRect,QSize& r_originalSize);
207 
208  //returns a uid as a handle, which will not be valid if the add failed
209  // * pinPage will make sure that the page in question in never expunged from the cache.
210  QUuid addPixmap(QPixmap * p_pixmap,bool pinPage=false);
211 
212  /*
213  *
214  * allowScale will try and scale the pixmap up or down to fit on an existing atlas page. The allowed scale amounts
215  * are in the gfxsettings file. It will try to scale up first, because it's usually less lossy when it is scaled back
216  * down later to return it to normal
217  * allowPageCreation will allow a new atlas page to be created if none is found to fit this pixmap (even w/ scaling,
218  * if it was allowed). If it is false, and a page can't be found, then the add will fail
219  *
220  * if both settings are true, a scaling will be tried, followed by the creation of a page
221  *
222  * by default, atlas pages are always pinned and will never be expunged by automatic replacement means,
223  * so use allowPageCreation carefully
224  */
225  QUuid addPixmapToAtlasPage(QPixmap * p_pixmap,bool allowScale=true,bool allowPageCreation=false);
226 
227  friend class PixPagerPage;
228  friend class PixPagerDebugger;
229 
230 private:
231 
232  void _pagePixmapDeleted(PixPagerPage * p_page);
233  //returns # of pages expunged
234  quint32 _findAndExpunge(quint32 minSize);
235  void _deleteRegularPage(PixPagerPage * p_page);
236  // try and figure out if the pixmap can be "squarified" to an existing atlas page size requirement
237  // given the current atlas page size designations and the allowed scale factors in the gfxsettings
238  // file. If yes, the new square size is returned (i.e. the quint returned is the length of a side
239  // of the square.
240  // If no, then just the existing size is returned
241  quint32 _determineSquareSize(QPixmap * p_pixmap,bool allowScale) const;
242  quint32 _determineSquareSizeFromRectPixmap(QPixmap * p_pixmap,bool allowScale) const;
243  quint32 _forceSquarify(QPixmap * p_pixmap,bool maxRect=false) const;
244 
245  static bool _isSquarePix(QPixmap * p_pixmap);
246 
247  PageOpsReturnCode::Enum _addAtlasPixmapEntry(PixPagerAtlasPage& page,QPixmap * p_pixmap,quint32 sqSize,const QPair<quint32,quint32>& targetGridLocation,
248  QUuid& r_insertedPixmapUid);
249  PageOpsReturnCode::Enum _createAndAddAtlasPageWithInitialEntry(QPixmap * p_pixmap,quint32 sqSize,quint32 initialRows,quint32 initialColumns,
250  QUuid& r_pageUid,QUuid& r_insertedPixmapUid,bool allowExpunge=true);
251  PageOpsReturnCode::Enum _copyAndExpandAtlasPage(PixPagerAtlasPage * p_atlasPage,quint32 numRows,
252  quint32 numColumns,bool makeSquarePage=false,QPair<quint32,quint32> * r_p_nextAvailableLocation=0);
253  static quint32 nextpwr2(quint32 t);
254 
255  inline quint32 _spaceRemaining() const
256  { return (m_maxSizeInBytes <= m_currentSizeInBytes ? 0 : (m_maxSizeInBytes - m_currentSizeInBytes)); }
257  inline quint32 _expungeAmountForMinsize(quint32 minSize) const
258  {
259  //since maxSizeInBytes is not a hard limit (it can be exceeded slightly on a temporary basis), max - current = s , s < 0 is possible
260  //this convenience function will take that negative s and add it to minSize so that the minimal amount that needs to be
261  //expunged to allocate a minSize page is returned
262  return ((m_maxSizeInBytes <= m_currentSizeInBytes) ? (minSize+(m_currentSizeInBytes-m_maxSizeInBytes))
263  : ((m_maxSizeInBytes-m_currentSizeInBytes >= minSize) ? 0
264  :(minSize - (m_maxSizeInBytes-m_currentSizeInBytes))));
265  }
266 
267  quint32 m_maxSizeInBytes;
268  quint32 m_currentSizeInBytes;
269  quint32 m_accessCounter; //getPixmap access for all pages
270 
271  //keyed by the size of the small pixmaps within ...so 64 -> 64x64 pixmaps
272  //alias hashes, so they don't own the pixpagerpages
273  // there may be multiple entries with the same size (using insertMulti)
274  QMap<quint32,PixPagerPage *> m_atlasPages_alias;
275  QHash<QUuid,PixPagerPage *> m_atlasPagesByIndividualUids_alias; //key: the little pixmap inside the atlas's uid,
276  //value: the pixpagerpage that contains the atlas for it
277 
278  //this one owns the pages
279  QHash<QUuid,PixPagerPage *> m_pageCache;
280 
281 };
282 
283 #endif /* PIXPAGER_H_ */