1 #include <memory>
2 
3 #include "ext/meshtexturizer.h"
4 
5 // TnzCore includes
6 #include "tgl.h"  // OpenGL includes
7 
8 // Qt includes
9 #include <QReadWriteLock>
10 #include <QReadLocker>
11 #include <QWriteLocker>
12 
13 // tcg includes
14 #include "tcg/tcg_macros.h"
15 #include "tcg/tcg_list.h"
16 #include "tcg/tcg_misc.h"
17 
18 #include "trop.h"
19 
20 #define COPIED_BORDER 1  // Amount of tile border from the original image
21 #define TRANSP_BORDER 1  // Amount of transparent tile border
22 #define NONPREM_BORDER                                                         \
23   1  // Amount of nonpremultiplied copied transparent border
24 #define TOTAL_BORDER                                                           \
25   (COPIED_BORDER + TRANSP_BORDER)  // Overall border to texture tiles above
26 #define TOTAL_BORDER_2 (2 * TOTAL_BORDER)  // Twice the above
27 
28 TCG_STATIC_ASSERT(COPIED_BORDER >
29                   0);  // Due to GL_LINEAR alpha blending on tile seams
30 TCG_STATIC_ASSERT(TRANSP_BORDER > 0);  // Due to GL_CLAMP beyond tile limits
31 TCG_STATIC_ASSERT(NONPREM_BORDER <=
32                   TRANSP_BORDER);  // The nonpremultiplied border is transparent
33 
34 namespace {
getMaxTextureTileSize()35 int getMaxTextureTileSize() {
36   static int maxTexSize = -1;
37   if (maxTexSize == -1) {
38     glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTexSize);
39     maxTexSize = std::min(maxTexSize, 1 << 11);
40   }
41   return maxTexSize;
42 }
43 };
44 
45 //******************************************************************************************
46 //    MeshTexturizer::Imp  definition
47 //******************************************************************************************
48 
49 class MeshTexturizer::Imp {
50   typedef MeshTexturizer::TextureData TextureData;
51 
52 public:
53   QReadWriteLock m_lock;  //!< Lock for synchronized access
54   tcg::list<std::shared_ptr<TextureData>>
55       m_textureDatas;  //!< Pool of texture datas
56 
57 public:
Imp()58   Imp() : m_lock(QReadWriteLock::Recursive) {}
59 
60   bool testTextureAlloc(int lx, int ly);
61   GLuint textureAlloc(const TRaster32P &ras, const TRaster32P &aux, int x,
62                       int y, int textureLx, int textureLy, bool premultiplied);
63 
64   void allocateTextures(int groupIdx, const TRaster32P &ras,
65                         const TRaster32P &aux, int x, int y, int textureLx,
66                         int textureLy, bool premultiplied);
67 
68   TextureData *getTextureData(int groupIdx);
69 };
70 
71 //---------------------------------------------------------------------------------
72 
testTextureAlloc(int lx,int ly)73 bool MeshTexturizer::Imp::testTextureAlloc(int lx, int ly) {
74   GLuint texName;
75   glEnable(GL_TEXTURE_2D);
76   glGenTextures(1, &texName);
77   glBindTexture(GL_TEXTURE_2D, texName);
78 
79   lx += TOTAL_BORDER_2, ly += TOTAL_BORDER_2;  // Add border
80 
81   int max_texture_size;
82   glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
83 
84   glTexImage2D(GL_PROXY_TEXTURE_2D,
85                0,                 // one level only
86                GL_RGBA,           // number of pixel channels
87                lx,                // width
88                ly,                // height
89                0,                 // border size
90                TGL_FMT,           // pixel format
91                GL_UNSIGNED_BYTE,  // pixel data type
92                0);
93 
94   int outLx;
95   glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &outLx);
96 
97   glDeleteTextures(1, &texName);
98   assert(glGetError() == GL_NO_ERROR);
99   return (lx == outLx);
100 }
101 
102 //---------------------------------------------------------------------------------
103 
textureAlloc(const TRaster32P & ras,const TRaster32P & aux,int x,int y,int textureLx,int textureLy,bool premultiplied)104 GLuint MeshTexturizer::Imp::textureAlloc(const TRaster32P &ras,
105                                          const TRaster32P &aux, int x, int y,
106                                          int textureLx, int textureLy,
107                                          bool premultiplied) {
108   struct locals {
109     static void clearMatte(const TRaster32P &ras, int xBegin, int yBegin,
110                            int xEnd, int yEnd) {
111       for (int y = yBegin; y != yEnd; ++y) {
112         TPixel32 *line = ras->pixels(y), *pixEnd = line + xEnd;
113 
114         for (TPixel32 *pix = line + xBegin; pix != pixEnd; ++pix) pix->m = 0;
115       }
116     }
117 
118     static void clearMatte_border(const TRaster32P &ras, int border0,
119                                   int border1) {
120       assert(border0 < border1);
121 
122       // Horizontal
123       clearMatte(ras, border0, border0, ras->getLx() - border0, border1);
124       clearMatte(ras, border0, ras->getLy() - border1, ras->getLx() - border0,
125                  ras->getLy() - border0);
126 
127       // Vertical
128       clearMatte(ras, border0, border1, border1, ras->getLy() - border1);
129       clearMatte(ras, ras->getLx() - border1, border1, ras->getLx() - border0,
130                  ras->getLy() - border1);
131     }
132 
133   };  // locals
134 
135   // Prepare the texture tile
136   assert(aux->getLx() >= textureLx + TOTAL_BORDER_2 &&
137          aux->getLy() >= textureLy + TOTAL_BORDER_2);
138 
139   TRect rasRect(x, y, x + textureLx - 1, y + textureLy - 1);
140   rasRect = rasRect.enlarge(premultiplied ? COPIED_BORDER
141                                           : COPIED_BORDER + NONPREM_BORDER);
142   rasRect = rasRect * ras->getBounds();
143 
144   TRect auxRect(rasRect - TPoint(x - TOTAL_BORDER, y - TOTAL_BORDER));
145 
146   // An auxiliary raster must be used to supply the transparent border
147   TRaster32P tex(aux->extract(0, 0, textureLx + TOTAL_BORDER_2 - 1,
148                               textureLy + TOTAL_BORDER_2 - 1));
149   tex->clear();
150   aux->extract(auxRect)->copy(ras->extract(rasRect));
151 
152   if (!premultiplied && NONPREM_BORDER > 0) {
153     locals::clearMatte_border(tex, TRANSP_BORDER - NONPREM_BORDER,
154                               TRANSP_BORDER);
155     TRop::expandColor(tex, true);  // precise is always true for now
156   }
157 
158   // Pass the raster into VRAM
159   GLuint texId;
160   glGenTextures(1, &texId);
161   glBindTexture(GL_TEXTURE_2D, texId);
162 
163   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
164                   GL_CLAMP);  // These must be used on a bound texture,
165   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
166                   GL_CLAMP);  // and are remembered in the OpenGL context.
167   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
168                   GL_LINEAR);  // They can be set here, no need for
169   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
170                   GL_LINEAR);  // the user to do it.
171 
172   glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->getWrap());
173   glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
174 
175   glTexImage2D(GL_TEXTURE_2D,
176                0,                 // one level only
177                GL_RGBA,           // pixel channels count
178                tex->getLx(),      // width
179                tex->getLy(),      // height
180                0,                 // border size
181                TGL_FMT,           // pixel format
182                GL_UNSIGNED_BYTE,  // pixel data type
183                (GLvoid *)tex->getRawData());
184   assert(glGetError() == GL_NO_ERROR);
185 
186   return texId;
187 }
188 
189 //---------------------------------------------------------------------------------
190 
allocateTextures(int groupIdx,const TRaster32P & ras,const TRaster32P & aux,int x,int y,int textureLx,int textureLy,bool premultiplied)191 void MeshTexturizer::Imp::allocateTextures(int groupIdx, const TRaster32P &ras,
192                                            const TRaster32P &aux, int x, int y,
193                                            int textureLx, int textureLy,
194                                            bool premultiplied) {
195   TextureData *data = m_textureDatas[groupIdx].get();
196 
197   // Test the specified texture allocation
198   if (testTextureAlloc(textureLx, textureLy)) {
199     TPointD scale(data->m_geom.getLx() / (double)ras->getLx(),
200                   data->m_geom.getLy() / (double)ras->getLy());
201     TRectD tileGeom(TRectD(scale.x * (x - TOTAL_BORDER),
202                            scale.y * (y - TOTAL_BORDER),
203                            scale.x * (x + textureLx + TOTAL_BORDER),
204                            scale.y * (y + textureLy + TOTAL_BORDER)) +
205                     data->m_geom.getP00());
206 
207     GLuint texId =
208         textureAlloc(ras, aux, x, y, textureLx, textureLy, premultiplied);
209 
210     TextureData::TileData td = {texId, tileGeom};
211     data->m_tileDatas.push_back(td);
212 
213     assert(glGetError() == GL_NO_ERROR);
214     return;
215   }
216 
217   if (textureLx <= 1 && textureLy <= 1) return;  // No texture can be allocated
218 
219   // The texture could not be allocated. Then, bisecate and branch.
220   if (textureLx > textureLy) {
221     int textureLx_2 = textureLx >> 1;
222     allocateTextures(groupIdx, ras, aux, x, y, textureLx_2, textureLy,
223                      premultiplied);
224     allocateTextures(groupIdx, ras, aux, x + textureLx_2, y, textureLx_2,
225                      textureLy, premultiplied);
226   } else {
227     int textureLy_2 = textureLy >> 1;
228     allocateTextures(groupIdx, ras, aux, x, y, textureLx, textureLy_2,
229                      premultiplied);
230     allocateTextures(groupIdx, ras, aux, x, y + textureLy_2, textureLx,
231                      textureLy_2, premultiplied);
232   }
233 }
234 
235 //---------------------------------------------------------------------------------
236 
getTextureData(int groupIdx)237 MeshTexturizer::TextureData *MeshTexturizer::Imp::getTextureData(int groupIdx) {
238   typedef MeshTexturizer::TextureData TextureData;
239 
240   // Copy tile datas container
241   return m_textureDatas[groupIdx].get();
242 }
243 
244 //******************************************************************************************
245 //    MeshTexturizer  implementation
246 //******************************************************************************************
247 
MeshTexturizer()248 MeshTexturizer::MeshTexturizer() : m_imp(new Imp) {}
249 
250 //---------------------------------------------------------------------------------
251 
~MeshTexturizer()252 MeshTexturizer::~MeshTexturizer() {}
253 
254 //---------------------------------------------------------------------------------
255 
bindTexture(const TRaster32P & ras,const TRectD & geom,PremultMode premultiplyMode)256 int MeshTexturizer::bindTexture(const TRaster32P &ras, const TRectD &geom,
257                                 PremultMode premultiplyMode) {
258   QWriteLocker locker(&m_imp->m_lock);
259 
260   // Backup the state of some specific OpenGL variables that will be changed
261   // throughout the code
262   int row_length, alignment;
263   glGetIntegerv(GL_UNPACK_ROW_LENGTH, &row_length);
264   glGetIntegerv(GL_UNPACK_ALIGNMENT, &alignment);
265 
266   // Initialize a new texture data
267   int dataIdx =
268       m_imp->m_textureDatas.push_back(std::make_shared<TextureData>(geom));
269 
270   // Textures must have 2-power sizes. So, let's start with the smallest 2 power
271   // >= ras's sizes.
272   int textureLx =
273       tcg::numeric_ops::GE_2Power((unsigned int)ras->getLx() + TOTAL_BORDER_2);
274   int textureLy =
275       tcg::numeric_ops::GE_2Power((unsigned int)ras->getLy() + TOTAL_BORDER_2);
276 
277   // We'll assume a strict granularity max of 2048 x 2048 textures, if it is
278   // possible.
279   textureLx = std::min(textureLx, getMaxTextureTileSize());
280   textureLy = std::min(textureLy, getMaxTextureTileSize());
281 
282   // Allocate a suitable texture raster. The texture will include a transparent
283   // 1-pix border
284   // that is needed to perform texture mapping with GL_CLAMP transparent
285   // wrapping
286   TRaster32P tex(textureLx, textureLy);
287 
288   // Now, let's tile the specified raster. We'll start from the lower-left
289   // corner in case
290   // the raster can be completely included in just one tile
291 
292   int lx = ras->getLx(), ly = ras->getLy();
293   int tileLx = textureLx - TOTAL_BORDER_2,
294       tileLy = textureLy - TOTAL_BORDER_2;  // Texture size without border
295 
296   int xEntireCells = (lx - 1) / tileLx,
297       yEntireCells =
298           (ly - 1) /
299           tileLy;  // +1 so in case l == tileL, we get the remainder case
300 
301   int lastTexLx = tcg::numeric_ops::GE_2Power(
302       (unsigned int)(lx - xEntireCells * tileLx + TOTAL_BORDER_2));
303   int lastTexLy = tcg::numeric_ops::GE_2Power(
304       (unsigned int)(ly - yEntireCells * tileLy + TOTAL_BORDER_2));
305 
306   int lastTileLx = lastTexLx - TOTAL_BORDER_2,
307       lastTileLy = lastTexLy - TOTAL_BORDER_2;
308 
309   bool premultiplied = (premultiplyMode == PREMULTIPLIED);
310 
311   int i, j;
312   for (i = 0; i < yEntireCells; ++i) {
313     for (j = 0; j < xEntireCells; ++j)
314       // Perform a (possibly subdividing) allocation of the specified tile
315       m_imp->allocateTextures(dataIdx, ras, tex, j * tileLx, i * tileLy, tileLx,
316                               tileLy, premultiplied);
317 
318     m_imp->allocateTextures(dataIdx, ras, tex, j * tileLx, i * tileLy,
319                             lastTileLx, tileLy, premultiplied);
320   }
321 
322   for (j = 0; j < xEntireCells; ++j)
323     m_imp->allocateTextures(dataIdx, ras, tex, j * tileLx, i * tileLy, tileLx,
324                             lastTileLy, premultiplied);
325 
326   m_imp->allocateTextures(dataIdx, ras, tex, j * tileLx, i * tileLy, lastTileLx,
327                           lastTileLy, premultiplied);
328 
329   // Restore OpenGL variables
330   glPixelStorei(GL_UNPACK_ROW_LENGTH, row_length);
331   glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
332 
333   return dataIdx;
334 }
335 
336 //---------------------------------------------------------------------------------
337 
rebindTexture(int texId,const TRaster32P & ras,const TRectD & geom,PremultMode premultiplyMode)338 void MeshTexturizer::rebindTexture(int texId, const TRaster32P &ras,
339                                    const TRectD &geom,
340                                    PremultMode premultiplyMode) {
341   QWriteLocker locker(&m_imp->m_lock);
342 
343   unbindTexture(texId);
344   int newTexId = bindTexture(ras, geom, premultiplyMode);
345 
346   assert(texId == newTexId);
347 }
348 
349 //---------------------------------------------------------------------------------
350 
unbindTexture(int texId)351 void MeshTexturizer::unbindTexture(int texId) {
352   QWriteLocker locker(&m_imp->m_lock);
353   m_imp->m_textureDatas.erase(texId);
354 }
355 
356 //---------------------------------------------------------------------------------
357 
getTextureData(int textureId)358 MeshTexturizer::TextureData *MeshTexturizer::getTextureData(int textureId) {
359   QReadLocker locker(&m_imp->m_lock);
360   return m_imp->getTextureData(textureId);
361 }
362