1 /** @file texturecontent.cpp  GL-texture content.
2  *
3  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2015 Daniel Swanson <danij@dengine.net>
5  *
6  * @par License
7  * GPL: http://www.gnu.org/licenses/gpl.html
8  *
9  * <small>This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by the
11  * Free Software Foundation; either version 2 of the License, or (at your
12  * option) any later version. This program is distributed in the hope that it
13  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
15  * Public License for more details. You should have received a copy of the GNU
16  * General Public License along with this program; if not, write to the Free
17  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18  * 02110-1301 USA</small>
19  */
20 
21 #include "de_platform.h"
22 #include "gl/texturecontent.h"
23 
24 #include <cstring>
25 #include <doomsday/resource/colorpalettes.h>
26 #include <de/concurrency.h>
27 #include <de/memory.h>
28 #include <de/GLInfo>
29 #include <de/texgamma.h>
30 #include "dd_def.h"  // texGamma
31 #include "dd_main.h"  // App_Resources()
32 #include "sys_system.h"
33 
34 #include "gl/gl_main.h"
35 #include "gl/gl_tex.h"
36 
37 #include "render/rend_main.h"  // misc global vars awaiting new home
38 
39 using namespace de;
40 
BytesPerPixelFmt(dgltexformat_t format)41 static int BytesPerPixelFmt(dgltexformat_t format)
42 {
43     switch (format)
44     {
45     case DGL_LUMINANCE:
46     case DGL_COLOR_INDEX_8:         return 1;
47 
48     case DGL_LUMINANCE_PLUS_A8:
49     case DGL_COLOR_INDEX_8_PLUS_A8: return 2;
50 
51     case DGL_RGB:                   return 3;
52 
53     case DGL_RGBA:                  return 4;
54     default:
55         App_Error("BytesPerPixelFmt: Unknown format %i, don't know pixel size.\n", format);
56         return 0; // Unreachable.
57     }
58 }
59 
60 #if 0
61 /**
62  * Given a pixel format return the number of bytes to store one pixel.
63  * @pre Input data is of GL_UNSIGNED_BYTE type.
64  */
65 static int BytesPerPixel(GLint format)
66 {
67     switch (format)
68     {
69     case GL_COLOR_INDEX:
70     case GL_STENCIL_INDEX:
71     case GL_DEPTH_COMPONENT:
72     case GL_RED:
73     case GL_GREEN:
74     case GL_BLUE:
75     case GL_ALPHA:
76     case GL_LUMINANCE:              return 1;
77 
78     case GL_LUMINANCE_ALPHA:        return 2;
79 
80     case GL_RGB:
81     case GL_RGB8:
82     case GL_BGR:                    return 3;
83 
84     case GL_RGBA:
85     case GL_RGBA8:
86     case GL_BGRA:                   return 4;
87 
88     default:
89         App_Error("BytesPerPixel: Unknown format %i.", (int) format);
90         return 0; // Unreachable.
91     }
92 }
93 #endif
94 
GL_InitTextureContent(texturecontent_t * content)95 void GL_InitTextureContent(texturecontent_t *content)
96 {
97     DENG_ASSERT(content);
98     content->format = dgltexformat_t(0);
99     content->name = 0;
100     content->pixels = 0;
101     content->paletteId = 0;
102     content->width = 0;
103     content->height = 0;
104     content->minFilter = GL_LINEAR;
105     content->magFilter = GL_LINEAR;
106     content->anisoFilter = -1; // Best.
107     content->wrap[0] = GL_CLAMP_TO_EDGE;
108     content->wrap[1] = GL_CLAMP_TO_EDGE;
109     content->grayMipmap = 0;
110     content->flags = 0;
111 }
112 
GL_ConstructTextureContentCopy(texturecontent_t const * other)113 texturecontent_t *GL_ConstructTextureContentCopy(texturecontent_t const *other)
114 {
115     DENG_ASSERT(other);
116 
117     texturecontent_t *c = (texturecontent_t*) M_Malloc(sizeof(*c));
118 
119     std::memcpy(c, other, sizeof(*c));
120 
121     // Duplicate the image buffer.
122     int bytesPerPixel = BytesPerPixelFmt(other->format);
123     size_t bufferSize = bytesPerPixel * other->width * other->height;
124     uint8_t *pixels = (uint8_t*) M_Malloc(bufferSize);
125     std::memcpy(pixels, other->pixels, bufferSize);
126     c->pixels = pixels;
127     return c;
128 }
129 
GL_DestroyTextureContent(texturecontent_t * content)130 void GL_DestroyTextureContent(texturecontent_t *content)
131 {
132     DENG_ASSERT(content);
133     if (content->pixels) M_Free((uint8_t *)content->pixels);
134     M_Free(content);
135 }
136 
137 /**
138  * Prepares the image for use as a GL texture in accordance with the given
139  * specification.
140  *
141  * @param image     The image to prepare (in place).
142  * @param spec      Specification describing any transformations which should
143  *                  be applied to the image.
144  *
145  * @return  The DGL texture format determined for the image.
146  */
prepareImageAsTexture(image_t & image,variantspecification_t const & spec)147 static dgltexformat_t prepareImageAsTexture(image_t &image,
148     variantspecification_t const &spec)
149 {
150     DENG_ASSERT(image.pixels);
151 
152     bool const monochrome = (spec.flags & TSF_MONOCHROME) != 0;
153     bool const scaleSharp = (spec.flags & TSF_UPSCALE_AND_SHARPEN) != 0;
154 
155     if (spec.toAlpha)
156     {
157         if (0 != image.paletteId)
158         {
159             // Paletted.
160             uint8_t *newPixels = GL_ConvertBuffer(image.pixels, image.size.x, image.size.y,
161                                                   ((image.flags & IMGF_IS_MASKED)? 2 : 1),
162                                                   image.paletteId, 3);
163             M_Free(image.pixels);
164             image.pixels = newPixels;
165             image.pixelSize = 3;
166             image.paletteId = 0;
167             image.flags &= ~IMGF_IS_MASKED;
168         }
169 
170         Image_ConvertToLuminance(image, false /*discard alpha*/);
171         long total = image.size.x * image.size.y;
172         for (long i = 0; i < total; ++i)
173         {
174             image.pixels[total + i] = image.pixels[i];
175             image.pixels[i] = 255;
176         }
177         image.pixelSize = 2;
178     }
179     else if (image.paletteId)
180     {
181         if (fillOutlines && (image.flags & IMGF_IS_MASKED))
182         {
183             ColorOutlinesIdx(image.pixels, image.size.x, image.size.y);
184         }
185 
186         if (monochrome && !scaleSharp)
187         {
188             GL_DeSaturatePalettedImage(image.pixels,
189                                        App_Resources().colorPalettes().colorPalette(image.paletteId),
190                                        image.size.x, image.size.y);
191         }
192 
193         if (scaleSharp)
194         {
195             int scaleMethod = GL_ChooseSmartFilter(image.size.x, image.size.y, 0);
196             bool origMasked = (image.flags & IMGF_IS_MASKED) != 0;
197             colorpaletteid_t origPaletteId = image.paletteId;
198 
199             uint8_t *newPixels = GL_ConvertBuffer(image.pixels, image.size.x, image.size.y,
200                                                   ((image.flags & IMGF_IS_MASKED)? 2 : 1),
201                                                   image.paletteId, 4);
202             if (newPixels != image.pixels)
203             {
204                 M_Free(image.pixels);
205                 image.pixels = newPixels;
206                 image.pixelSize = 4;
207                 image.paletteId = 0;
208                 image.flags &= ~IMGF_IS_MASKED;
209             }
210 
211             if (monochrome)
212             {
213                 Desaturate(image.pixels, image.size.x, image.size.y, image.pixelSize);
214             }
215 
216             int newWidth = 0, newHeight = 0;
217             newPixels = GL_SmartFilter(scaleMethod, image.pixels, image.size.x, image.size.y,
218                                        0, &newWidth, &newHeight);
219             image.size = Vector2ui(newWidth, newHeight);
220             if (newPixels != image.pixels)
221             {
222                 M_Free(image.pixels);
223                 image.pixels = newPixels;
224             }
225 
226             EnhanceContrast(image.pixels, image.size.x, image.size.y, image.pixelSize);
227             //SharpenPixels(image.pixels, image.size.x, image.size.y, image.pixelSize);
228             //BlackOutlines(image.pixels, image.size.x, image.size.y, image.pixelSize);
229 
230             // Back to paletted+alpha?
231             if (monochrome)
232             {
233                 // No. We'll convert from RGB(+A) to Luminance(+A) and upload as is.
234                 // Replace the old buffer.
235                 Image_ConvertToLuminance(image);
236                 AmplifyLuma(image.pixels, image.size.x, image.size.y, image.pixelSize == 2);
237             }
238             else
239             {   // Yes. Quantize down from RGA(+A) to Paletted(+A), replacing the old image.
240                 newPixels = GL_ConvertBuffer(image.pixels, image.size.x, image.size.y,
241                                              (origMasked? 2 : 1),
242                                              origPaletteId, 4);
243 
244                 if (newPixels != image.pixels)
245                 {
246                     M_Free(image.pixels);
247                     image.pixels = newPixels;
248                     image.pixelSize = (origMasked? 2 : 1);
249                     image.paletteId = origPaletteId;
250                     if (origMasked)
251                     {
252                         image.flags |= IMGF_IS_MASKED;
253                     }
254                 }
255             }
256         }
257     }
258     else if (image.pixelSize > 2)
259     {
260         if (fillOutlines && image.pixelSize == 4)
261         {
262             ColorOutlinesRGBA(image.pixels, image.size.x, image.size.y);
263         }
264 
265         if (monochrome)
266         {
267             Image_ConvertToLuminance(image);
268             AmplifyLuma(image.pixels, image.size.x, image.size.y, image.pixelSize == 2);
269         }
270     }
271 
272     /*
273      * Choose the final GL texture format.
274      */
275     if (monochrome)
276     {
277         return image.pixelSize == 2? DGL_LUMINANCE_PLUS_A8 : DGL_LUMINANCE;
278     }
279     if (image.paletteId)
280     {
281         return (image.flags & IMGF_IS_MASKED)? DGL_COLOR_INDEX_8_PLUS_A8 : DGL_COLOR_INDEX_8;
282     }
283     return   image.pixelSize == 2 ? DGL_LUMINANCE_PLUS_A8
284            : image.pixelSize == 3 ? DGL_RGB
285            : image.pixelSize == 4 ? DGL_RGBA : DGL_LUMINANCE;
286 }
287 
288 /**
289  * Prepares the image for use as a detail GL texture in accordance with the
290  * given specification.
291  *
292  * @param image     The image to prepare (in place).
293  * @param spec      Specification describing any transformations which should
294  *                  be applied to the image.
295  *
296  * Return values:
297  * @param baMul     Luminance equalization balance factor is written here.
298  * @param hiMul     Luminance equalization white-shift factor is written here.
299  * @param loMul     Luminance equalization black-shift factor is written here.
300  *
301  * @return  The DGL texture format determined for the image.
302  */
prepareImageAsDetailTexture(image_t & image,detailvariantspecification_t const & spec,float * baMul,float * hiMul,float * loMul)303 static dgltexformat_t prepareImageAsDetailTexture(image_t &image,
304     detailvariantspecification_t const &spec, float *baMul, float *hiMul, float *loMul)
305 {
306     DENG_UNUSED(spec);
307 
308     // We want a luminance map.
309     if (image.pixelSize > 2)
310     {
311         Image_ConvertToLuminance(image, false /*discard alpha*/);
312     }
313 
314     // Try to normalize the luminance data so it works expectedly as a detail texture.
315     EqualizeLuma(image.pixels, image.size.x, image.size.y, baMul, hiMul, loMul);
316 
317     return DGL_LUMINANCE;
318 }
319 
GL_PrepareTextureContent(texturecontent_t & c,GLuint glTexName,image_t & image,TextureVariantSpec const & spec,res::TextureManifest const & textureManifest)320 void GL_PrepareTextureContent(texturecontent_t &c,
321                               GLuint glTexName,
322                               image_t &image,
323                               TextureVariantSpec const &spec,
324                               res::TextureManifest const &textureManifest)
325 {
326     DENG_ASSERT(glTexName != 0);
327     DENG_ASSERT(image.pixels != 0);
328 
329     // Initialize and assign a GL name to the content.
330     GL_InitTextureContent(&c);
331     c.name = glTexName;
332 
333     switch (spec.type)
334     {
335     case TST_GENERAL: {
336         variantspecification_t const &vspec = spec.variant;
337         bool const noCompression = (vspec.flags & TSF_NO_COMPRESSION) != 0;
338         // If the Upscale And Sharpen filter is enabled, scaling is applied
339         // implicitly by prepareImageAsTexture(), so don't do it again.
340         bool const noSmartFilter = (vspec.flags & TSF_UPSCALE_AND_SHARPEN) != 0;
341 
342         // Prepare the image for upload.
343         dgltexformat_t dglFormat = prepareImageAsTexture(image, vspec);
344 
345         // Configure the texture content.
346         c.format      = dglFormat;
347         c.width       = image.size.x;
348         c.height      = image.size.y;
349         c.pixels      = image.pixels;
350         c.paletteId   = image.paletteId;
351 
352         if (noCompression || (image.size.x < 128 || image.size.y < 128))
353             c.flags |= TXCF_NO_COMPRESSION;
354         if (vspec.gammaCorrection) c.flags |= TXCF_APPLY_GAMMACORRECTION;
355         if (vspec.noStretch)       c.flags |= TXCF_UPLOAD_ARG_NOSTRETCH;
356         if (vspec.mipmapped)       c.flags |= TXCF_MIPMAP;
357         if (noSmartFilter)         c.flags |= TXCF_UPLOAD_ARG_NOSMARTFILTER;
358 
359         c.magFilter   = vspec.glMagFilter();
360         c.minFilter   = vspec.glMinFilter();
361         c.anisoFilter = vspec.logicalAnisoLevel();
362         c.wrap[0]     = vspec.wrapS;
363         c.wrap[1]     = vspec.wrapT;
364         break; }
365 
366     case TST_DETAIL: {
367         detailvariantspecification_t const &dspec = spec.detailVariant;
368 
369         // Prepare the image for upload.
370         float baMul, hiMul, loMul;
371         dgltexformat_t dglFormat = prepareImageAsDetailTexture(image, dspec, &baMul, &hiMul, &loMul);
372 
373         // Determine the gray mipmap factor.
374         int grayMipmapFactor = dspec.contrast;
375         if (baMul != 1 || hiMul != 1 || loMul != 1)
376         {
377             // Integrate the normalization factor with contrast.
378             float const hiContrast = 1 - 1. / hiMul;
379             float const loContrast = 1 - loMul;
380             float const shift = ((hiContrast + loContrast) / 2);
381 
382             grayMipmapFactor = int(255 * de::clamp(0.f, dspec.contrast / 255.f - shift, 1.f));
383 
384             // Announce the normalization.
385             de::Uri uri = textureManifest.composeUri();
386             LOG_GL_VERBOSE("Normalized detail texture \"%s\" (balance: %f, high amp: %f, low amp: %f)")
387                 << uri << baMul << hiMul << loMul;
388         }
389 
390         // Configure the texture content.
391         c.format      = dglFormat;
392         c.flags       = TXCF_GRAY_MIPMAP | TXCF_UPLOAD_ARG_NOSMARTFILTER;
393 
394         // Disable compression?
395         if (image.size.x < 128 || image.size.y < 128)
396             c.flags |= TXCF_NO_COMPRESSION;
397 
398         c.grayMipmap  = grayMipmapFactor;
399         c.width       = image.size.x;
400         c.height      = image.size.y;
401         c.pixels      = image.pixels;
402         c.anisoFilter = texAniso;
403         c.magFilter   = glmode[texMagMode];
404         c.minFilter   = GL_LINEAR_MIPMAP_LINEAR;
405         c.wrap[0]     = GL_REPEAT;
406         c.wrap[1]     = GL_REPEAT;
407         break; }
408 
409     default:
410         // Invalid spec type.
411         DENG_ASSERT(false);
412     }
413 }
414 
415 /**
416  * Choose an internal texture format.
417  *
418  * @param format  DGL texture format identifier.
419  * @param allowCompression  @c true == use compression if available.
420  * @return  The chosen texture format.
421  */
ChooseTextureFormat(dgltexformat_t format,dd_bool allowCompression)422 static GLint ChooseTextureFormat(dgltexformat_t format, dd_bool allowCompression)
423 {
424 #if defined (DENG_OPENGL_ES)
425 
426     switch (format)
427     {
428     case DGL_RGB:
429     case DGL_COLOR_INDEX_8:
430         return GL_RGB8;
431 
432     case DGL_RGBA:
433     case DGL_COLOR_INDEX_8_PLUS_A8:
434         return GL_RGBA8;
435 
436     default:
437         DENG2_ASSERT(!"ChooseTextureFormat: Invalid texture source format");
438         return 0;
439     }
440 
441 #else
442 
443     dd_bool compress = (allowCompression && GL_state.features.texCompression);
444 
445     switch (format)
446     {
447     case DGL_RGB:
448     case DGL_COLOR_INDEX_8:
449         if (!compress)
450             return GL_RGB8;
451 #if USE_TEXTURE_COMPRESSION_S3
452         if (GLInfo::extensions().EXT_texture_compression_s3tc)
453             return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
454 #endif
455         return GL_COMPRESSED_RGB;
456 
457     case DGL_RGBA:
458     case DGL_COLOR_INDEX_8_PLUS_A8:
459         if (!compress)
460             return GL_RGBA8;
461 #if USE_TEXTURE_COMPRESSION_S3
462         if (GLInfo::extensions().EXT_texture_compression_s3tc)
463             return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
464 #endif
465         return GL_COMPRESSED_RGBA;
466 
467     /*case DGL_LUMINANCE:
468         return !compress ? GL_LUMINANCE : GL_COMPRESSED_LUMINANCE;
469 
470     case DGL_LUMINANCE_PLUS_A8:
471         return !compress ? GL_LUMINANCE_ALPHA : GL_COMPRESSED_LUMINANCE_ALPHA;*/
472 
473     default:
474         DENG2_ASSERT(!"ChooseTextureFormat: Invalid texture source format");
475         return 0; // Unreachable.
476     }
477 
478 #endif
479 }
480 
481 /**
482  * @param glFormat  Identifier of the desired GL texture format.
483  * @param loadFormat  Identifier of the GL texture format used during upload.
484  * @param pixels  Texture pixel data to be uploaded.
485  * @param width  Width of the texture in pixels.
486  * @param height  Height of the texture in pixels.
487  * @param genMipmaps  If negative sets a specific mipmap level, e.g.:
488  *      @c -1, means mipmap level 1.
489  *
490  * @return  @c true iff successful.
491  */
uploadTexture(int glFormat,int loadFormat,const uint8_t * pixels,int width,int height,int genMipmaps)492 static dd_bool uploadTexture(int glFormat, int loadFormat, const uint8_t* pixels,
493     int width,  int height, int genMipmaps)
494 {
495     GLenum const properties[8] =
496     {
497        GL_PACK_ROW_LENGTH,
498        GL_PACK_ALIGNMENT,
499        GL_PACK_SKIP_ROWS,
500        GL_PACK_SKIP_PIXELS,
501        GL_UNPACK_ROW_LENGTH,
502        GL_UNPACK_ALIGNMENT,
503        GL_UNPACK_SKIP_ROWS,
504        GL_UNPACK_SKIP_PIXELS,
505     };
506 
507     const int packRowLength = 0, packAlignment = 1, packSkipRows = 0, packSkipPixels = 0;
508     const int unpackRowLength = 0, unpackAlignment = 1, unpackSkipRows = 0, unpackSkipPixels = 0;
509     int mipLevel = 0;
510     DENG_ASSERT(pixels);
511 
512     if (!(GL_LUMINANCE_ALPHA == loadFormat || GL_LUMINANCE == loadFormat ||
513          GL_RGB == loadFormat || GL_RGBA == loadFormat))
514     {
515         throw Error("texturecontent_t::uploadTexture", "Unsupported load format " + String::number(loadFormat));
516     }
517 
518     // Can't operate on null texture.
519     if (width < 1 || height < 1)
520         return false;
521 
522     // Check that the texture dimensions are valid.
523     if (width > GLInfo::limits().maxTexSize || height > GLInfo::limits().maxTexSize)
524         return false;
525 
526     // Negative indices signify a specific mipmap level is being uploaded.
527     if (genMipmaps < 0)
528     {
529         mipLevel = -genMipmaps;
530         genMipmaps = 0;
531     }
532 
533     //DENG_ASSERT_IN_MAIN_THREAD();
534     DENG_ASSERT_GL_CONTEXT_ACTIVE();
535 
536     auto &GL = LIBGUI_GL;
537 
538     // Automatic mipmap generation?
539     /*if (genMipmaps)
540     {
541         GL.glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
542         LIBGUI_ASSERT_GL_OK();
543     }*/
544 
545     //LIBGUI_GL.glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT);
546     GLint oldPixelStore[8];
547     for (int i = 0; i < 8; ++i)
548     {
549         GL.glGetIntegerv(properties[i], &oldPixelStore[i]);
550         LIBGUI_ASSERT_GL_OK();
551     }
552 
553     GL.glPixelStorei(GL_PACK_ROW_LENGTH, (GLint)packRowLength);
554     LIBGUI_ASSERT_GL_OK();
555     GL.glPixelStorei(GL_PACK_ALIGNMENT, (GLint)packAlignment);
556     LIBGUI_ASSERT_GL_OK();
557     GL.glPixelStorei(GL_PACK_SKIP_ROWS, (GLint)packSkipRows);
558     LIBGUI_ASSERT_GL_OK();
559     GL.glPixelStorei(GL_PACK_SKIP_PIXELS, (GLint)packSkipPixels);
560     LIBGUI_ASSERT_GL_OK();
561     GL.glPixelStorei(GL_UNPACK_ROW_LENGTH, (GLint)unpackRowLength);
562     LIBGUI_ASSERT_GL_OK();
563     GL.glPixelStorei(GL_UNPACK_ALIGNMENT, (GLint)unpackAlignment);
564     LIBGUI_ASSERT_GL_OK();
565     GL.glPixelStorei(GL_UNPACK_SKIP_ROWS, (GLint)unpackSkipRows);
566     LIBGUI_ASSERT_GL_OK();
567     GL.glPixelStorei(GL_UNPACK_SKIP_PIXELS, (GLint)unpackSkipPixels);
568     LIBGUI_ASSERT_GL_OK();
569 
570 #if 0
571     if (genMipmaps && !GLInfo::extensions().SGIS_generate_mipmap)
572     {   // Build all mipmap levels.
573         int neww, newh, bpp, w, h;
574         void* image, *newimage;
575 
576         bpp = BytesPerPixel(loadFormat);
577         if (bpp == 0)
578             throw Error("texturecontent_t::uploadTexture", "Unknown GL format " + String::number(loadFormat));
579 
580         GL_OptimalTextureSize(width, height, false, true, &w, &h);
581 
582         if (w != width || h != height)
583         {
584             // Must rescale image to get "top" mipmap texture image.
585             image = GL_ScaleBufferEx(pixels, width, height, bpp, /*GL_UNSIGNED_BYTE,*/
586                 unpackRowLength, unpackAlignment, unpackSkipRows, unpackSkipPixels,
587                 w, h, /*GL_UNSIGNED_BYTE,*/ packRowLength, packAlignment, packSkipRows,
588                 packSkipPixels);
589             if (!image)
590                 throw Error("texturecontent_t::uploadTexture", "Unknown error resizing mipmap level #0");
591         }
592         else
593         {
594             image = (void*) pixels;
595         }
596 
597         for (;;)
598         {
599             LIBGUI_GL.glTexImage2D(GL_TEXTURE_2D, mipLevel, (GLint)glFormat, w, h, 0, (GLint)loadFormat,
600                 GL_UNSIGNED_BYTE, image);
601 
602             if (w == 1 && h == 1)
603                 break;
604 
605             ++mipLevel;
606             neww = (w < 2) ? 1 : w / 2;
607             newh = (h < 2) ? 1 : h / 2;
608             newimage = GL_ScaleBufferEx(image, w, h, bpp, /*GL_UNSIGNED_BYTE,*/
609                 unpackRowLength, unpackAlignment, unpackSkipRows, unpackSkipPixels,
610                 neww, newh, /*GL_UNSIGNED_BYTE,*/ packRowLength, packAlignment,
611                 packSkipRows, packSkipPixels);
612             if (!newimage)
613                 throw Error("texturecontent_t::uploadTexture", "Unknown error resizing mipmap level #" + String::number(mipLevel));
614 
615             if (image != pixels)
616                 M_Free(image);
617             image = newimage;
618 
619             w = neww;
620             h = newh;
621         }
622 
623         if (image != pixels)
624             M_Free(image);
625     }
626     else
627 #endif
628     {
629         LIBGUI_GL.glTexImage2D(GL_TEXTURE_2D, mipLevel, GLint(glFormat),
630                                GLsizei(width), GLsizei(height), 0,
631                                GLenum(loadFormat), GL_UNSIGNED_BYTE, pixels);
632         LIBGUI_ASSERT_GL_OK();
633 
634         if (genMipmaps)
635         {
636             LIBGUI_GL.glGenerateMipmap(GL_TEXTURE_2D);
637         }
638     }
639 
640     //LIBGUI_GL.glPopClientAttrib();
641 
642     for (int i = 0; i < 8; ++i)
643     {
644         GL.glPixelStorei(properties[i], oldPixelStore[i]);
645         LIBGUI_ASSERT_GL_OK();
646     }
647 
648     DENG_ASSERT(!Sys_GLCheckError());
649 
650     return true;
651 }
652 
653 /**
654  * @param glFormat  Identifier of the desired GL texture format.
655  * @param loadFormat  Identifier of the GL texture format used during upload.
656  * @param pixels  Texture pixel data to be uploaded.
657  * @param width  Width of the texture in pixels.
658  * @param height  Height of the texture in pixels.
659  * @param grayFactor  Strength of the blend where @c 0:none @c 1:full.
660  *
661  * @return  @c true iff successful.
662  */
uploadTextureGrayMipmap(int glFormat,int loadFormat,const uint8_t * pixels,int width,int height,float grayFactor)663 static dd_bool uploadTextureGrayMipmap(int glFormat, int loadFormat, const uint8_t* pixels,
664     int width, int height, float grayFactor)
665 {
666     int i, w, h, numpels = width * height, numLevels, pixelSize;
667     uint8_t* image, *faded, *out;
668     const uint8_t* in;
669     float invFactor;
670     DENG_ASSERT(pixels);
671 
672     if (!(GL_RGB == loadFormat || GL_LUMINANCE == loadFormat))
673     {
674         throw Error("texturecontent_t::uploadTextureGrayMipmap", "Unsupported load format " + String::number(loadFormat));
675     }
676 
677     pixelSize = (loadFormat == GL_LUMINANCE? 1 : 3);
678 
679     // Can't operate on null texture.
680     if (width < 1 || height < 1)
681         return false;
682 
683     // Check that the texture dimensions are valid.
684     if (width > GLInfo::limits().maxTexSize || height > GLInfo::limits().maxTexSize)
685         return false;
686 
687     numLevels = GL_NumMipmapLevels(width, height);
688     grayFactor = MINMAX_OF(0, grayFactor, 1);
689     invFactor = 1 - grayFactor;
690 
691     // Buffer used for the faded texture.
692     faded = (uint8_t*) M_Malloc(numpels / 4);
693     image = (uint8_t*) M_Malloc(numpels);
694 
695     // Initial fading.
696     in = pixels;
697     out = image;
698     for (i = 0; i < numpels; ++i)
699     {
700         *out++ = (uint8_t) MINMAX_OF(0, (*in * grayFactor + 127 * invFactor), 255);
701         in += pixelSize;
702     }
703 
704     // Upload the first level right away.
705     LIBGUI_GL.glTexImage2D(GL_TEXTURE_2D, 0, glFormat, width, height, 0, (GLint)loadFormat,
706         GL_UNSIGNED_BYTE, image);
707 
708     // Generate all mipmaps levels.
709     w = width;
710     h = height;
711     for (i = 0; i < numLevels; ++i)
712     {
713         GL_DownMipmap8(image, faded, w, h, (i * 1.75f) / numLevels);
714 
715         // Go down one level.
716         if (w > 1)
717             w /= 2;
718         if (h > 1)
719             h /= 2;
720 
721         LIBGUI_GL.glTexImage2D(GL_TEXTURE_2D, i + 1, glFormat, w, h, 0, (GLint)loadFormat,
722             GL_UNSIGNED_BYTE, faded);
723     }
724 
725     // Do we need to free the temp buffer?
726     M_Free(faded);
727     M_Free(image);
728 
729     DENG_ASSERT(!Sys_GLCheckError());
730     return true;
731 }
732 
733 /// @note Texture parameters will NOT be set here!
GL_UploadTextureContent(texturecontent_t const & content,gl::UploadMethod method)734 void GL_UploadTextureContent(texturecontent_t const &content, gl::UploadMethod method)
735 {
736     if (method == gl::Deferred)
737     {
738         GL_DeferTextureUpload(&content);
739         return;
740     }
741 
742     if (novideo) return;
743 
744     // Do this right away. No need to take a copy.
745     bool generateMipmaps = (content.flags & (TXCF_MIPMAP|TXCF_GRAY_MIPMAP)) != 0;
746     bool applyTexGamma   = (content.flags & TXCF_APPLY_GAMMACORRECTION)     != 0;
747     bool noCompression   = (content.flags & TXCF_NO_COMPRESSION)            != 0;
748     bool noSmartFilter   = (content.flags & TXCF_UPLOAD_ARG_NOSMARTFILTER)  != 0;
749     bool noStretch       = (content.flags & TXCF_UPLOAD_ARG_NOSTRETCH)      != 0;
750 
751     int loadWidth             = content.width;
752     int loadHeight            = content.height;
753     uint8_t const *loadPixels = content.pixels;
754     dgltexformat_t dglFormat  = content.format;
755 
756     // Convert a paletted source image to truecolor.
757     if (dglFormat == DGL_COLOR_INDEX_8 || dglFormat == DGL_COLOR_INDEX_8_PLUS_A8)
758     {
759         uint8_t *newPixels = GL_ConvertBuffer(loadPixels, loadWidth, loadHeight,
760                                               dglFormat == DGL_COLOR_INDEX_8_PLUS_A8 ? 2 : 1,
761                                               content.paletteId,
762                                               dglFormat == DGL_COLOR_INDEX_8_PLUS_A8 ? 4 : 3);
763         if (loadPixels != content.pixels)
764         {
765             M_Free(const_cast<uint8_t *>(loadPixels));
766         }
767         loadPixels = newPixels;
768         dglFormat = (dglFormat == DGL_COLOR_INDEX_8_PLUS_A8 ? DGL_RGBA : DGL_RGB);
769     }
770 
771     // Gamma adjustment and smart filtering.
772     if (dglFormat == DGL_RGBA || dglFormat == DGL_RGB)
773     {
774         int comps = (dglFormat == DGL_RGBA ? 4 : 3);
775 
776         if (applyTexGamma && texGamma > .0001f)
777         {
778             uint8_t* dst, *localBuffer = 0;
779             long const numPels = loadWidth * loadHeight;
780 
781             uint8_t const *src = loadPixels;
782             if (loadPixels == content.pixels)
783             {
784                 localBuffer = (uint8_t *) M_Malloc(comps * numPels);
785                 dst = localBuffer;
786             }
787             else
788             {
789                 dst = const_cast<uint8_t *>(loadPixels);
790             }
791 
792             for (long i = 0; i < numPels; ++i)
793             {
794                 dst[CR] = R_TexGammaLut(src[CR]);
795                 dst[CG] = R_TexGammaLut(src[CG]);
796                 dst[CB] = R_TexGammaLut(src[CB]);
797                 if (comps == 4)
798                     dst[CA] = src[CA];
799 
800                 dst += comps;
801                 src += comps;
802             }
803 
804             if (localBuffer)
805             {
806                 if (loadPixels != content.pixels)
807                 {
808                     M_Free(const_cast<uint8_t *>(loadPixels));
809                 }
810                 loadPixels = localBuffer;
811             }
812         }
813 
814         if (useSmartFilter && !noSmartFilter)
815         {
816             if (comps == 3)
817             {
818                 // Need to add an alpha channel.
819                 uint8_t *newPixels = GL_ConvertBuffer(loadPixels, loadWidth, loadHeight, 3, 0, 4);
820                 if (loadPixels != content.pixels)
821                 {
822                     M_Free(const_cast<uint8_t *>(loadPixels));
823                 }
824                 loadPixels = newPixels;
825                 dglFormat = DGL_RGBA;
826             }
827 
828             uint8_t *filtered = GL_SmartFilter(GL_ChooseSmartFilter(loadWidth, loadHeight, 0),
829                                                loadPixels, loadWidth, loadHeight,
830                                                ICF_UPSCALE_SAMPLE_WRAP,
831                                                &loadWidth, &loadHeight);
832             if (filtered != loadPixels)
833             {
834                 if (loadPixels != content.pixels)
835                 {
836                     M_Free(const_cast<uint8_t *>(loadPixels));
837                 }
838                 loadPixels = filtered;
839             }
840         }
841     }
842 
843     if (dglFormat == DGL_LUMINANCE && (content.flags & TXCF_CONVERT_8BIT_TO_ALPHA))
844     {
845         // Needs converting. This adds some overhead.
846         long const numPixels = content.width * content.height;
847         uint8_t *localBuffer = (uint8_t *) M_Malloc(4 * numPixels);
848 
849         // Move the average color to the alpha channel, make the actual color white.
850         uint8_t *pixel = localBuffer;
851         for (long i = 0; i < numPixels; ++i)
852         {
853             *pixel++ = 255;
854             *pixel++ = 255;
855             *pixel++ = 255;
856             *pixel++ = loadPixels[i];
857         }
858 
859         if (loadPixels != content.pixels)
860         {
861             M_Free(const_cast<uint8_t *>(loadPixels));
862         }
863         loadPixels = localBuffer;
864         dglFormat = DGL_RGBA;
865     }
866     else if (dglFormat == DGL_LUMINANCE)
867     {
868         // Needs converting. This adds some overhead.
869         long const numPixels = content.width * content.height;
870         uint8_t *localBuffer = (uint8_t *) M_Malloc(3 * numPixels);
871 
872         // Move the average color to the alpha channel, make the actual color white.
873         uint8_t *pixel = localBuffer;
874         for (long i = 0; i < numPixels; ++i)
875         {
876             *pixel++ = loadPixels[i];
877             *pixel++ = loadPixels[i];
878             *pixel++ = loadPixels[i];
879         }
880 
881         if (loadPixels != content.pixels)
882         {
883             M_Free(const_cast<uint8_t *>(loadPixels));
884         }
885         loadPixels = localBuffer;
886         dglFormat = DGL_RGB;
887     }
888 
889     if (dglFormat == DGL_LUMINANCE_PLUS_A8)
890     {
891         // Needs converting. This adds some overhead.
892         long const numPixels = content.width * content.height;
893         uint8_t *localBuffer = (uint8_t *) M_Malloc(4 * numPixels);
894 
895         uint8_t *pixel = localBuffer;
896         for (long i = 0; i < numPixels; ++i)
897         {
898             *pixel++ = loadPixels[i];
899             *pixel++ = loadPixels[i];
900             *pixel++ = loadPixels[i];
901             *pixel++ = loadPixels[numPixels + i];
902         }
903 
904         if (loadPixels != content.pixels)
905         {
906             M_Free(const_cast<uint8_t *>(loadPixels));
907         }
908         loadPixels = localBuffer;
909         dglFormat = DGL_RGBA;
910     }
911 
912     // Calculate the final dimensions for the texture, as required by
913     // the graphics hardware and/or engine configuration.
914     int width = loadWidth, height = loadHeight;
915 
916     noStretch = GL_OptimalTextureSize(width, height, noStretch, generateMipmaps,
917                                       &loadWidth, &loadHeight);
918 
919     // Do we need to resize?
920     if (width != loadWidth || height != loadHeight)
921     {
922         int comps = BytesPerPixelFmt(dglFormat);
923 
924         if (noStretch)
925         {
926             // Copy the texture into a power-of-two canvas.
927             uint8_t *localBuffer = (uint8_t *) M_Calloc(comps * loadWidth * loadHeight);
928 
929             // Copy line by line.
930             for (int i = 0; i < height; ++i)
931             {
932                 std::memcpy(localBuffer + loadWidth * comps * i,
933                             loadPixels  + width     * comps * i, comps * width);
934             }
935 
936             if (loadPixels != content.pixels)
937             {
938                 M_Free(const_cast<uint8_t *>(loadPixels));
939             }
940             loadPixels = localBuffer;
941         }
942         else
943         {
944             // Stretch into a new power-of-two texture.
945             uint8_t *newPixels = GL_ScaleBuffer(loadPixels, width, height, comps,
946                                                 loadWidth, loadHeight);
947             if (loadPixels != content.pixels)
948             {
949                 M_Free(const_cast<uint8_t *>(loadPixels));
950             }
951             loadPixels = newPixels;
952         }
953     }
954 
955     //DENG_ASSERT_IN_MAIN_THREAD();
956     DENG_ASSERT_GL_CONTEXT_ACTIVE();
957 
958     LIBGUI_GL.glBindTexture(GL_TEXTURE_2D, content.name);
959     LIBGUI_GL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, content.minFilter);
960     LIBGUI_GL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, content.magFilter);
961     LIBGUI_GL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,     content.wrap[0]);
962     LIBGUI_GL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,     content.wrap[1]);
963     if (GL_state.features.texFilterAniso)
964         LIBGUI_GL.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, GL_GetTexAnisoMul(content.anisoFilter));
965 
966     DENG2_ASSERT(dglFormat == DGL_RGB || dglFormat == DGL_RGBA);
967 
968     if (!(content.flags & TXCF_GRAY_MIPMAP))
969     {
970         GLint loadFormat;
971         switch (dglFormat)
972         {
973         //case DGL_LUMINANCE_PLUS_A8: loadFormat = GL_LUMINANCE_ALPHA; break;
974         //case DGL_LUMINANCE:         loadFormat = GL_LUMINANCE;       break;
975         case DGL_RGB:               loadFormat = GL_RGB;             break;
976         case DGL_RGBA:              loadFormat = GL_RGBA;            break;
977         default:
978             throw Error("GL_UploadTextureContent", QString("Unknown format %1").arg(int(dglFormat)));
979         }
980 
981         GLint glFormat = ChooseTextureFormat(dglFormat, !noCompression);
982 
983         if (!uploadTexture(glFormat, loadFormat, loadPixels, loadWidth, loadHeight,
984                           generateMipmaps ? true : false))
985         {
986             throw Error("GL_UploadTextureContent", QString("TexImage failed (%1:%2 fmt%3)")
987                                                        .arg(content.name)
988                                                        .arg(Vector2i(loadWidth, loadHeight).asText())
989                                                        .arg(int(dglFormat)));
990         }
991     }
992     else
993     {
994         // Special fade-to-gray luminance texture (used for details).
995         GLint glFormat, loadFormat;
996 
997         switch (dglFormat)
998         {
999         //case DGL_LUMINANCE:         loadFormat = GL_LUMINANCE; break;
1000         case DGL_RGB:               loadFormat = GL_RGB;       break;
1001         default:
1002             throw Error("GL_UploadTextureContent", QString("Unknown format %1").arg(int(dglFormat)));
1003         }
1004 
1005         glFormat = ChooseTextureFormat(dglFormat, !noCompression);
1006 
1007         if (!uploadTextureGrayMipmap(glFormat, loadFormat, loadPixels, loadWidth, loadHeight,
1008                                      content.grayMipmap * reciprocal255))
1009         {
1010             throw Error("GL_UploadTextureContent", QString("TexImageGrayMipmap failed (%1:%2 fmt%3)")
1011                                                        .arg(content.name)
1012                                                        .arg(Vector2i(loadWidth, loadHeight).asText())
1013                                                        .arg(int(dglFormat)));
1014         }
1015     }
1016 
1017     if (loadPixels != content.pixels)
1018     {
1019         M_Free(const_cast<uint8_t *>(loadPixels));
1020     }
1021 }
1022