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