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