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