1 /** @file gltexture.cpp GL texture atlas.
2 *
3 * @authors Copyright (c) 2013-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 *
5 * @par License
6 * LGPL: http://www.gnu.org/licenses/lgpl.html
7 *
8 * <small>This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or (at your
11 * option) any later version. This program is distributed in the hope that it
12 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
13 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
14 * General Public License for more details. You should have received a copy of
15 * the GNU Lesser General Public License along with this program; if not, see:
16 * http://www.gnu.org/licenses</small>
17 */
18
19 #include "de/GLTexture"
20 #include "de/GLInfo"
21 #include "de/graphics/opengl.h"
22
23 namespace de {
24
25 namespace internal
26 {
27 enum TextureFlag {
28 AutoMips = 0x1,
29 MipmapAvailable = 0x2,
30 ParamsChanged = 0x4
31 };
32 Q_DECLARE_FLAGS(TextureFlags, TextureFlag)
33 }
34
35 Q_DECLARE_OPERATORS_FOR_FLAGS(internal::TextureFlags)
36
37 using namespace internal;
38 using namespace gl;
39
DENG2_PIMPL(GLTexture)40 DENG2_PIMPL(GLTexture)
41 {
42 Size size;
43 Image::Format format;
44 GLuint name;
45 GLenum texTarget;
46 Filter minFilter;
47 Filter magFilter;
48 MipFilter mipFilter;
49 Wraps wrap;
50 dfloat maxAnisotropy;
51 dfloat maxLevel;
52 TextureFlags flags;
53
54 Impl(Public *i)
55 : Base(i)
56 , format(Image::Unknown)
57 , name(0)
58 , texTarget(GL_TEXTURE_2D)
59 , minFilter(Linear), magFilter(Linear), mipFilter(MipNone)
60 , wrap(Wraps(Repeat, Repeat))
61 , maxAnisotropy(1.0f)
62 , maxLevel(1000.f)
63 , flags(ParamsChanged)
64 {}
65
66 ~Impl()
67 {
68 release();
69 }
70
71 void alloc()
72 {
73 if (!name)
74 {
75 LIBGUI_GL.glGenTextures(1, &name);
76 }
77 }
78
79 void release()
80 {
81 if (name)
82 {
83 LIBGUI_GL.glDeleteTextures(1, &name);
84 name = 0;
85 }
86 }
87
88 void clear()
89 {
90 release();
91 size = Size();
92 texTarget = GL_TEXTURE_2D;
93 flags |= ParamsChanged;
94 self().setState(NotReady);
95 }
96
97 bool isCube() const
98 {
99 return texTarget == GL_TEXTURE_CUBE_MAP;
100 }
101
102 static GLenum glWrap(gl::Wrapping w)
103 {
104 switch (w)
105 {
106 case Repeat: return GL_REPEAT;
107 case RepeatMirrored: return GL_MIRRORED_REPEAT;
108 case ClampToEdge: return GL_CLAMP_TO_EDGE;
109 }
110 return GL_REPEAT;
111 }
112
113 static GLenum glMinFilter(gl::Filter min, gl::MipFilter mip)
114 {
115 if (mip == MipNone)
116 {
117 if (min == Nearest) return GL_NEAREST;
118 if (min == Linear) return GL_LINEAR;
119 }
120 else if (mip == MipNearest)
121 {
122 if (min == Nearest) return GL_NEAREST_MIPMAP_NEAREST;
123 if (min == Linear) return GL_LINEAR_MIPMAP_NEAREST;
124 }
125 else // MipLinear
126 {
127 if (min == Nearest) return GL_NEAREST_MIPMAP_LINEAR;
128 if (min == Linear) return GL_LINEAR_MIPMAP_LINEAR;
129 }
130 return GL_NEAREST;
131 }
132
133 static GLenum glFace(gl::CubeFace face)
134 {
135 switch (face)
136 {
137 case PositiveX: return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
138 case PositiveY: return GL_TEXTURE_CUBE_MAP_POSITIVE_Y;
139 case PositiveZ: return GL_TEXTURE_CUBE_MAP_POSITIVE_Z;
140 case NegativeX: return GL_TEXTURE_CUBE_MAP_NEGATIVE_X;
141 case NegativeY: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Y;
142 case NegativeZ: return GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
143 }
144 return GL_TEXTURE_CUBE_MAP_POSITIVE_X;
145 }
146
147 void glBind() const
148 {
149 //DENG2_ASSERT(name != 0);
150 LIBGUI_GL.glBindTexture(texTarget, name); LIBGUI_ASSERT_GL_OK();
151 }
152
153 void glUnbind() const
154 {
155 LIBGUI_GL.glBindTexture(texTarget, 0);
156 }
157
158 /**
159 * Update the OpenGL texture parameters. You must bind the texture before
160 * calling.
161 */
162 void glUpdateParamsOfBoundTexture()
163 {
164 LIBGUI_GL.glTexParameteri(texTarget, GL_TEXTURE_WRAP_S, glWrap(wrap.x));
165 LIBGUI_GL.glTexParameteri(texTarget, GL_TEXTURE_WRAP_T, glWrap(wrap.y));
166 LIBGUI_GL.glTexParameteri(texTarget, GL_TEXTURE_MAG_FILTER, magFilter == Nearest? GL_NEAREST : GL_LINEAR);
167 LIBGUI_GL.glTexParameteri(texTarget, GL_TEXTURE_MIN_FILTER, glMinFilter(minFilter, mipFilter));
168 LIBGUI_GL.glTexParameterf(texTarget, GL_TEXTURE_MAX_LEVEL, maxLevel);
169
170 if (GLInfo::extensions().EXT_texture_filter_anisotropic)
171 {
172 LIBGUI_GL.glTexParameterf(texTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy);
173 }
174
175 LIBGUI_ASSERT_GL_OK();
176
177 flags &= ~ParamsChanged;
178 }
179
180 void glImage(int level, Size const &size, GLPixelFormat const &glFormat,
181 void const *data, CubeFace face = PositiveX)
182 {
183 /// @todo GLES2: Check for the BGRA extension.
184
185 // Choose suitable informal format.
186 GLenum const internalFormat =
187 (glFormat.format == GL_BGRA? GL_RGBA :
188 glFormat.format == GL_DEPTH_STENCIL? GL_DEPTH24_STENCIL8 :
189 glFormat.format);
190
191 /*qDebug() << "glTexImage2D:" << name << (isCube()? glFace(face) : texTarget)
192 << level << internalFormat << size.x << size.y << 0
193 << glFormat.format << glFormat.type << data;*/
194
195 if (data) LIBGUI_GL.glPixelStorei(GL_UNPACK_ALIGNMENT, GLint(glFormat.rowAlignment));
196 LIBGUI_GL.glTexImage2D(isCube()? glFace(face) : texTarget,
197 level, internalFormat, size.x, size.y, 0,
198 glFormat.format, glFormat.type, data);
199
200 LIBGUI_ASSERT_GL_OK();
201 }
202
203 void glSubImage(int level, Vector2i const &pos, Size const &size,
204 GLPixelFormat const &glFormat, void const *data, CubeFace face = PositiveX)
205 {
206 if (data) LIBGUI_GL.glPixelStorei(GL_UNPACK_ALIGNMENT, GLint(glFormat.rowAlignment));
207 LIBGUI_GL.glTexSubImage2D(isCube()? glFace(face) : texTarget,
208 level, pos.x, pos.y, size.x, size.y,
209 glFormat.format, glFormat.type, data);
210
211 LIBGUI_ASSERT_GL_OK();
212 }
213
214 void glSubImage(int level, Rectanglei const &rect, Image const &image,
215 CubeFace face = PositiveX)
216 {
217 auto const &glFormat = image.glFormat();
218
219 LIBGUI_GL.glPixelStorei(GL_UNPACK_ALIGNMENT, GLint(glFormat.rowAlignment));
220 LIBGUI_GL.glPixelStorei(GL_UNPACK_ROW_LENGTH, GLint(image.width()));
221
222 int const bytesPerPixel = image.depth() / 8;
223
224 LIBGUI_GL.glTexSubImage2D(isCube()? glFace(face) : texTarget,
225 level, rect.left(), rect.top(), rect.width(), rect.height(),
226 glFormat.format, glFormat.type,
227 static_cast<dbyte const *>(image.bits()) +
228 bytesPerPixel * rect.left() + image.stride() * rect.top());
229
230 LIBGUI_GL.glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
231
232 LIBGUI_ASSERT_GL_OK();
233 }
234 };
235
GLTexture()236 GLTexture::GLTexture() : d(new Impl(this))
237 {}
238
GLTexture(GLuint existingTexture,Size const & size)239 GLTexture::GLTexture(GLuint existingTexture, Size const &size) : d(new Impl(this))
240 {
241 d->size = size;
242 d->name = existingTexture;
243 d->flags |= ParamsChanged;
244 }
245
clear()246 void GLTexture::clear()
247 {
248 d->clear();
249 }
250
setMagFilter(Filter magFilter)251 void GLTexture::setMagFilter(Filter magFilter)
252 {
253 d->magFilter = magFilter;
254 d->flags |= ParamsChanged;
255 }
256
setMinFilter(Filter minFilter,MipFilter mipFilter)257 void GLTexture::setMinFilter(Filter minFilter, MipFilter mipFilter)
258 {
259 d->minFilter = minFilter;
260 d->mipFilter = mipFilter;
261 d->flags |= ParamsChanged;
262 }
263
setWrapS(Wrapping mode)264 void GLTexture::setWrapS(Wrapping mode)
265 {
266 d->wrap.x = mode;
267 d->flags |= ParamsChanged;
268 }
269
setWrapT(Wrapping mode)270 void GLTexture::setWrapT(Wrapping mode)
271 {
272 d->wrap.y = mode;
273 d->flags |= ParamsChanged;
274 }
275
setMaxAnisotropy(dfloat maxAnisotropy)276 void GLTexture::setMaxAnisotropy(dfloat maxAnisotropy)
277 {
278 d->maxAnisotropy = maxAnisotropy;
279 d->flags |= ParamsChanged;
280 }
281
setMaxLevel(dfloat maxLevel)282 void GLTexture::setMaxLevel(dfloat maxLevel)
283 {
284 d->maxLevel = maxLevel;
285 d->flags |= ParamsChanged;
286 }
287
minFilter() const288 Filter GLTexture::minFilter() const
289 {
290 return d->minFilter;
291 }
292
magFilter() const293 Filter GLTexture::magFilter() const
294 {
295 return d->magFilter;
296 }
297
mipFilter() const298 MipFilter GLTexture::mipFilter() const
299 {
300 return d->mipFilter;
301 }
302
wrapS() const303 Wrapping GLTexture::wrapS() const
304 {
305 return d->wrap.x;
306 }
307
wrapT() const308 Wrapping GLTexture::wrapT() const
309 {
310 return d->wrap.y;
311 }
312
wrap() const313 GLTexture::Wraps GLTexture::wrap() const
314 {
315 return d->wrap;
316 }
317
maxAnisotropy() const318 dfloat GLTexture::maxAnisotropy() const
319 {
320 return d->maxAnisotropy;
321 }
322
maxLevel() const323 dfloat GLTexture::maxLevel() const
324 {
325 return d->maxLevel;
326 }
327
isCubeMap() const328 bool GLTexture::isCubeMap() const
329 {
330 return d->isCube();
331 }
332
setAutoGenMips(bool genMips)333 void GLTexture::setAutoGenMips(bool genMips)
334 {
335 if (genMips)
336 d->flags |= AutoMips;
337 else
338 d->flags &= ~AutoMips;
339 }
340
autoGenMips() const341 bool GLTexture::autoGenMips() const
342 {
343 return d->flags.testFlag(AutoMips);
344 }
345
setUndefinedImage(GLTexture::Size const & size,Image::Format format,int level)346 void GLTexture::setUndefinedImage(GLTexture::Size const &size, Image::Format format, int level)
347 {
348 d->texTarget = GL_TEXTURE_2D;
349 d->size = size;
350 d->format = format;
351
352 d->alloc();
353 d->glBind();
354 d->glImage(level, size, Image::glFormat(format), NULL);
355 d->glUnbind();
356
357 setState(Ready);
358 }
359
setUndefinedImage(CubeFace face,GLTexture::Size const & size,Image::Format format,int level)360 void GLTexture::setUndefinedImage(CubeFace face, GLTexture::Size const &size,
361 Image::Format format, int level)
362 {
363 d->texTarget = GL_TEXTURE_CUBE_MAP;
364 d->size = size;
365 d->format = format;
366
367 d->alloc();
368 d->glBind();
369 d->glImage(level, size, Image::glFormat(format), NULL, face);
370 d->glUnbind();
371
372 setState(Ready);
373 }
374
setUndefinedContent(Size const & size,GLPixelFormat const & glFormat,int level)375 void GLTexture::setUndefinedContent(Size const &size, GLPixelFormat const &glFormat, int level)
376 {
377 d->texTarget = GL_TEXTURE_2D;
378 d->size = size;
379 d->format = Image::Unknown;
380
381 d->alloc();
382 d->glBind();
383 d->glImage(level, size, glFormat, NULL);
384 d->glUnbind();
385
386 setState(Ready);
387 }
388
setUndefinedContent(CubeFace face,Size const & size,GLPixelFormat const & glFormat,int level)389 void GLTexture::setUndefinedContent(CubeFace face, Size const &size, GLPixelFormat const &glFormat, int level)
390 {
391 d->texTarget = GL_TEXTURE_CUBE_MAP;
392 d->size = size;
393 d->format = Image::Unknown;
394
395 d->alloc();
396 d->glBind();
397 d->glImage(level, size, glFormat, NULL, face);
398 d->glUnbind();
399
400 setState(Ready);
401 }
402
setDepthStencilContent(Size const & size)403 void GLTexture::setDepthStencilContent(Size const &size)
404 {
405 setUndefinedContent(size, GLPixelFormat(GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8));
406 }
407
setImage(Image const & image,int level)408 void GLTexture::setImage(Image const &image, int level)
409 {
410 d->texTarget = GL_TEXTURE_2D;
411 d->size = image.size();
412 d->format = image.format();
413
414 d->alloc();
415 d->glBind();
416 d->glImage(level, image.size(), image.glFormat(), image.bits());
417 d->glUnbind();
418
419 if (!level && d->flags.testFlag(AutoMips))
420 {
421 generateMipmap();
422 }
423
424 setState(Ready);
425 }
426
setImage(CubeFace face,Image const & image,int level)427 void GLTexture::setImage(CubeFace face, Image const &image, int level)
428 {
429 d->texTarget = GL_TEXTURE_CUBE_MAP;
430 d->size = image.size();
431 d->format = image.format();
432
433 d->alloc();
434 d->glBind();
435 d->glImage(level, image.size(), image.glFormat(), image.bits(), face);
436 d->glUnbind();
437
438 if (!level && d->flags.testFlag(AutoMips))
439 {
440 generateMipmap();
441 }
442
443 setState(Ready);
444 }
445
setSubImage(Image const & image,Vector2i const & pos,int level)446 void GLTexture::setSubImage(Image const &image, Vector2i const &pos, int level)
447 {
448 d->texTarget = GL_TEXTURE_2D;
449
450 d->alloc();
451 d->glBind();
452 d->glSubImage(level, pos, image.size(), image.glFormat(), image.bits());
453 d->glUnbind();
454
455 if (!level && d->flags.testFlag(AutoMips))
456 {
457 generateMipmap();
458 }
459 }
460
setSubImage(Image const & image,Rectanglei const & rect,int level)461 void GLTexture::setSubImage(Image const &image, Rectanglei const &rect, int level)
462 {
463 d->texTarget = GL_TEXTURE_2D;
464
465 d->alloc();
466 d->glBind();
467 d->glSubImage(level, rect, image);
468 d->glUnbind();
469
470 if (!level && d->flags.testFlag(AutoMips))
471 {
472 generateMipmap();
473 }
474 }
475
setSubImage(CubeFace face,Image const & image,Vector2i const & pos,int level)476 void GLTexture::setSubImage(CubeFace face, Image const &image, Vector2i const &pos, int level)
477 {
478 d->texTarget = GL_TEXTURE_CUBE_MAP;
479
480 d->alloc();
481 d->glBind();
482 d->glSubImage(level, pos, image.size(), image.glFormat(), image.bits(), face);
483 d->glUnbind();
484
485 if (!level && d->flags.testFlag(AutoMips))
486 {
487 generateMipmap();
488 }
489 }
490
setSubImage(CubeFace face,Image const & image,Rectanglei const & rect,int level)491 void GLTexture::setSubImage(CubeFace face, Image const &image, Rectanglei const &rect, int level)
492 {
493 d->texTarget = GL_TEXTURE_CUBE_MAP;
494
495 d->alloc();
496 d->glBind();
497 d->glSubImage(level, rect, image, face);
498 d->glUnbind();
499
500 if (!level && d->flags.testFlag(AutoMips))
501 {
502 generateMipmap();
503 }
504 }
505
generateMipmap()506 void GLTexture::generateMipmap()
507 {
508 if (d->name)
509 {
510 d->glBind();
511 LIBGUI_GL.glGenerateMipmap(d->texTarget); LIBGUI_ASSERT_GL_OK();
512 d->glUnbind();
513
514 d->flags |= MipmapAvailable;
515 }
516 }
517
size() const518 GLTexture::Size GLTexture::size() const
519 {
520 return d->size;
521 }
522
mipLevels() const523 int GLTexture::mipLevels() const
524 {
525 if (!isReady()) return 0;
526 return d->flags.testFlag(MipmapAvailable)? levelsForSize(d->size) : 1;
527 }
528
levelSize(int level) const529 GLTexture::Size GLTexture::levelSize(int level) const
530 {
531 if (level < 0) return Size();
532 return levelSize(d->size, level);
533 }
534
glName() const535 GLuint GLTexture::glName() const
536 {
537 return d->name;
538 }
539
glBindToUnit(int unit) const540 void GLTexture::glBindToUnit(int unit) const
541 {
542 LIBGUI_GL.glActiveTexture(GL_TEXTURE0 + unit);
543
544 aboutToUse();
545
546 d->glBind();
547
548 if (d->flags.testFlag(ParamsChanged))
549 {
550 d->glUpdateParamsOfBoundTexture();
551 }
552 }
553
glApplyParameters()554 void GLTexture::glApplyParameters()
555 {
556 if (d->flags.testFlag(ParamsChanged))
557 {
558 d->glBind();
559 d->glUpdateParamsOfBoundTexture();
560 d->glUnbind();
561 }
562 }
563
imageFormat() const564 Image::Format GLTexture::imageFormat() const
565 {
566 return d->format;
567 }
568
maximumSize()569 GLTexture::Size GLTexture::maximumSize()
570 {
571 int v = 0;
572 LIBGUI_GL.glGetIntegerv(GL_MAX_TEXTURE_SIZE, &v); LIBGUI_ASSERT_GL_OK();
573 return Size(v, v);
574 }
575
aboutToUse() const576 void GLTexture::aboutToUse() const
577 {
578 // nothing to do
579 }
580
levelsForSize(GLTexture::Size const & size)581 int GLTexture::levelsForSize(GLTexture::Size const &size)
582 {
583 int mipLevels = 0;
584 duint w = size.x;
585 duint h = size.y;
586 while (w > 1 || h > 1)
587 {
588 w = de::max(1u, w >> 1);
589 h = de::max(1u, h >> 1);
590 mipLevels++;
591 }
592 return mipLevels;
593 }
594
levelSize(GLTexture::Size const & size0,int level)595 GLTexture::Size GLTexture::levelSize(GLTexture::Size const &size0, int level)
596 {
597 Size s = size0;
598 for (int i = 0; i < level; ++i)
599 {
600 s.x = de::max(1u, s.x >> 1);
601 s.y = de::max(1u, s.y >> 1);
602 }
603 return s;
604 }
605
606 } // namespace de
607