]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/textures.cpp
Merge branch 'fix-fast' into 'master'
[xonotic/netradiant.git] / radiant / textures.cpp
1 /*
2    Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3    For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5    This file is part of GtkRadiant.
6
7    GtkRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    GtkRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with GtkRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22 #include "textures.h"
23
24 #include "debugging/debugging.h"
25 #include "warnings.h"
26
27 #include "itextures.h"
28 #include "igl.h"
29 #include "preferencesystem.h"
30 #include "qgl.h"
31
32 #include "texturelib.h"
33 #include "container/hashfunc.h"
34 #include "container/cache.h"
35 #include "generic/callback.h"
36 #include "stringio.h"
37
38 #include "image.h"
39 #include "texmanip.h"
40 #include "preferences.h"
41
42
43 enum ETexturesMode {
44     eTextures_NEAREST = 0,
45     eTextures_NEAREST_MIPMAP_NEAREST = 1,
46     eTextures_NEAREST_MIPMAP_LINEAR = 2,
47     eTextures_LINEAR = 3,
48     eTextures_LINEAR_MIPMAP_NEAREST = 4,
49     eTextures_LINEAR_MIPMAP_LINEAR = 5,
50     eTextures_MAX_ANISOTROPY = 6,
51 };
52
53 enum TextureCompressionFormat {
54     TEXTURECOMPRESSION_NONE = 0,
55     TEXTURECOMPRESSION_RGBA = 1,
56     TEXTURECOMPRESSION_RGBA_S3TC_DXT1 = 2,
57     TEXTURECOMPRESSION_RGBA_S3TC_DXT3 = 3,
58     TEXTURECOMPRESSION_RGBA_S3TC_DXT5 = 4,
59 };
60
61 struct texture_globals_t {
62     // RIANT
63     // texture compression format
64     TextureCompressionFormat m_nTextureCompressionFormat;
65
66     float fGamma;
67
68     bool bTextureCompressionSupported; // is texture compression supported by hardware?
69     GLint texture_components;
70
71     // temporary values that should be initialised only once at run-time
72     bool m_bOpenGLCompressionSupported;
73     bool m_bS3CompressionSupported;
74
75     texture_globals_t(GLint components) :
76             m_nTextureCompressionFormat(TEXTURECOMPRESSION_NONE),
77             fGamma(1.0f),
78             bTextureCompressionSupported(false),
79             texture_components(components),
80             m_bOpenGLCompressionSupported(false),
81             m_bS3CompressionSupported(false)
82     {
83     }
84 };
85
86 texture_globals_t g_texture_globals(GL_RGBA);
87
88 void SetTexParameters(ETexturesMode mode)
89 {
90     float maxAniso = QGL_maxTextureAnisotropy();
91     if (maxAniso > 1) {
92         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f);
93     } else if (mode == eTextures_MAX_ANISOTROPY) {
94         mode = eTextures_LINEAR_MIPMAP_LINEAR;
95     }
96
97     switch (mode) {
98         case eTextures_NEAREST:
99             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
100             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
101             break;
102         case eTextures_NEAREST_MIPMAP_NEAREST:
103             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
104             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
105             break;
106         case eTextures_NEAREST_MIPMAP_LINEAR:
107             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
108             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
109             break;
110         case eTextures_LINEAR:
111             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
112             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
113             break;
114         case eTextures_LINEAR_MIPMAP_NEAREST:
115             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
116             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
117             break;
118         case eTextures_LINEAR_MIPMAP_LINEAR:
119             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
120             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
121             break;
122         case eTextures_MAX_ANISOTROPY:
123             glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAniso);
124             break;
125         default:
126             globalOutputStream() << "invalid texture mode\n";
127     }
128 }
129
130 ETexturesMode g_texture_mode = eTextures_LINEAR_MIPMAP_LINEAR;
131
132
133 byte g_gammatable[256];
134
135 void ResampleGamma(float fGamma)
136 {
137     int i, inf;
138     if (fGamma == 1.0) {
139         for (i = 0; i < 256; i++) {
140             g_gammatable[i] = i;
141         }
142     } else {
143         for (i = 0; i < 256; i++) {
144             inf = (int) (255 * pow(static_cast<double>((i + 0.5) / 255.5 ), static_cast<double>( fGamma )) + 0.5);
145             if (inf < 0) {
146                 inf = 0;
147             }
148             if (inf > 255) {
149                 inf = 255;
150             }
151             g_gammatable[i] = inf;
152         }
153     }
154 }
155
156 inline const int &min_int(const int &left, const int &right)
157 {
158     return std::min(left, right);
159 }
160
161 int max_tex_size = 0;
162 const int max_texture_quality = 3;
163 LatchedValue<int> g_Textures_textureQuality(3, "Texture Quality");
164
165 /// \brief This function does the actual processing of raw RGBA data into a GL texture.
166 /// It will also resample to power-of-two dimensions, generate the mipmaps and adjust gamma.
167 void LoadTextureRGBA(qtexture_t *q, unsigned char *pPixels, int nWidth, int nHeight)
168 {
169     static float fGamma = -1;
170     float total[3];
171     byte *outpixels = 0;
172     int nCount = nWidth * nHeight;
173
174     if (fGamma != g_texture_globals.fGamma) {
175         fGamma = g_texture_globals.fGamma;
176         ResampleGamma(fGamma);
177     }
178
179     q->width = nWidth;
180     q->height = nHeight;
181
182     total[0] = total[1] = total[2] = 0.0f;
183
184     // resample texture gamma according to user settings
185     for (int i = 0; i < (nCount * 4); i += 4) {
186         for (int j = 0; j < 3; j++) {
187             total[j] += (pPixels + i)[j];
188             byte b = (pPixels + i)[j];
189             (pPixels + i)[j] = g_gammatable[b];
190         }
191     }
192
193     q->color[0] = total[0] / (nCount * 255);
194     q->color[1] = total[1] / (nCount * 255);
195     q->color[2] = total[2] / (nCount * 255);
196
197     glGenTextures(1, &q->texture_number);
198
199     glBindTexture(GL_TEXTURE_2D, q->texture_number);
200
201     SetTexParameters(g_texture_mode);
202
203     int gl_width = 1;
204     while (gl_width < nWidth) {
205         gl_width <<= 1;
206     }
207
208     int gl_height = 1;
209     while (gl_height < nHeight) {
210         gl_height <<= 1;
211     }
212
213     bool resampled = false;
214     if (!(gl_width == nWidth && gl_height == nHeight)) {
215         resampled = true;
216         outpixels = (byte *) malloc(gl_width * gl_height * 4);
217         R_ResampleTexture(pPixels, nWidth, nHeight, outpixels, gl_width, gl_height, 4);
218     } else {
219         outpixels = pPixels;
220     }
221
222     int quality_reduction = max_texture_quality - g_Textures_textureQuality.m_value;
223     int target_width = min_int(gl_width >> quality_reduction, max_tex_size);
224     int target_height = min_int(gl_height >> quality_reduction, max_tex_size);
225
226     while (gl_width > target_width || gl_height > target_height) {
227         GL_MipReduce(outpixels, outpixels, gl_width, gl_height, target_width, target_height);
228
229         if (gl_width > target_width) {
230             gl_width >>= 1;
231         }
232         if (gl_height > target_height) {
233             gl_height >>= 1;
234         }
235     }
236
237     int mip = 0;
238     glTexImage2D(GL_TEXTURE_2D, mip++, g_texture_globals.texture_components, gl_width, gl_height, 0, GL_RGBA,
239                  GL_UNSIGNED_BYTE, outpixels);
240     while (gl_width > 1 || gl_height > 1) {
241         GL_MipReduce(outpixels, outpixels, gl_width, gl_height, 1, 1);
242
243         if (gl_width > 1) {
244             gl_width >>= 1;
245         }
246         if (gl_height > 1) {
247             gl_height >>= 1;
248         }
249
250         glTexImage2D(GL_TEXTURE_2D, mip++, g_texture_globals.texture_components, gl_width, gl_height, 0, GL_RGBA,
251                      GL_UNSIGNED_BYTE, outpixels);
252     }
253
254     glBindTexture(GL_TEXTURE_2D, 0);
255     if (resampled) {
256         free(outpixels);
257     }
258 }
259
260 #if 0
261 /*
262    ==============
263    Texture_InitPalette
264    ==============
265  */
266 void Texture_InitPalette( byte *pal ){
267     int r,g,b;
268     int i;
269     int inf;
270     byte gammatable[256];
271     float gamma;
272
273     gamma = g_texture_globals.fGamma;
274
275     if ( gamma == 1.0 ) {
276         for ( i = 0 ; i < 256 ; i++ )
277             gammatable[i] = i;
278     }
279     else
280     {
281         for ( i = 0 ; i < 256 ; i++ )
282         {
283             inf = (int)( 255 * pow( ( i + 0.5 ) / 255.5, gamma ) + 0.5 );
284             if ( inf < 0 ) {
285                 inf = 0;
286             }
287             if ( inf > 255 ) {
288                 inf = 255;
289             }
290             gammatable[i] = inf;
291         }
292     }
293
294     for ( i = 0 ; i < 256 ; i++ )
295     {
296         r = gammatable[pal[0]];
297         g = gammatable[pal[1]];
298         b = gammatable[pal[2]];
299         pal += 3;
300
301         //v = (r<<24) + (g<<16) + (b<<8) + 255;
302         //v = BigLong (v);
303
304         //tex_palette[i] = v;
305         tex_palette[i * 3 + 0] = r;
306         tex_palette[i * 3 + 1] = g;
307         tex_palette[i * 3 + 2] = b;
308     }
309 }
310 #endif
311
312 #if 0
313 class TestHashtable
314 {
315 public:
316 TestHashtable(){
317     HashTable<CopiedString, CopiedString, HashStringNoCase, StringEqualNoCase> strings;
318     strings["Monkey"] = "bleh";
319     strings["MonkeY"] = "blah";
320 }
321 };
322
323 const TestHashtable g_testhashtable;
324
325 #endif
326
327 typedef std::pair<LoadImageCallback, CopiedString> TextureKey;
328
329 void qtexture_realise(qtexture_t &texture, const TextureKey &key)
330 {
331     texture.texture_number = 0;
332     if (!string_empty(key.second.c_str())) {
333         Image *image = key.first.loadImage(key.second.c_str());
334         if (image != 0) {
335             LoadTextureRGBA(&texture, image->getRGBAPixels(), image->getWidth(), image->getHeight());
336             texture.surfaceFlags = image->getSurfaceFlags();
337             texture.contentFlags = image->getContentFlags();
338             texture.value = image->getValue();
339             image->release();
340             globalOutputStream() << "Loaded Texture: \"" << key.second.c_str() << "\"\n";
341             GlobalOpenGL_debugAssertNoErrors();
342         } else {
343             globalErrorStream() << "Texture load failed: \"" << key.second.c_str() << "\"\n";
344         }
345     }
346 }
347
348 void qtexture_unrealise(qtexture_t &texture)
349 {
350     if (GlobalOpenGL().contextValid && texture.texture_number != 0) {
351         glDeleteTextures(1, &texture.texture_number);
352         GlobalOpenGL_debugAssertNoErrors();
353     }
354 }
355
356 class TextureKeyEqualNoCase {
357 public:
358     bool operator()(const TextureKey &key, const TextureKey &other) const
359     {
360         return key.first == other.first && string_equal_nocase(key.second.c_str(), other.second.c_str());
361     }
362 };
363
364 class TextureKeyHashNoCase {
365 public:
366     typedef hash_t hash_type;
367
368     hash_t operator()(const TextureKey &key) const
369     {
370         return hash_combine(string_hash_nocase(key.second.c_str()), pod_hash(key.first));
371     }
372 };
373
374 #define DEBUG_TEXTURES 0
375
376 class TexturesMap : public TexturesCache {
377     class TextureConstructor {
378         TexturesMap *m_cache;
379     public:
380         explicit TextureConstructor(TexturesMap *cache)
381                 : m_cache(cache)
382         {
383         }
384
385         qtexture_t *construct(const TextureKey &key)
386         {
387             qtexture_t *texture = new qtexture_t(key.first, key.second.c_str());
388             if (m_cache->realised()) {
389                 qtexture_realise(*texture, key);
390             }
391             return texture;
392         }
393
394         void destroy(qtexture_t *texture)
395         {
396             if (m_cache->realised()) {
397                 qtexture_unrealise(*texture);
398             }
399             delete texture;
400         }
401     };
402
403     typedef HashedCache<TextureKey, qtexture_t, TextureKeyHashNoCase, TextureKeyEqualNoCase, TextureConstructor> qtextures_t;
404     qtextures_t m_qtextures;
405     TexturesCacheObserver *m_observer;
406     std::size_t m_unrealised;
407
408 public:
409     virtual ~TexturesMap() = default;
410
411     TexturesMap() : m_qtextures(TextureConstructor(this)), m_observer(0), m_unrealised(1)
412     {
413     }
414
415     typedef qtextures_t::iterator iterator;
416
417     iterator begin()
418     {
419         return m_qtextures.begin();
420     }
421
422     iterator end()
423     {
424         return m_qtextures.end();
425     }
426
427     LoadImageCallback defaultLoader() const
428     {
429         return LoadImageCallback(0, QERApp_LoadImage);
430     }
431
432     Image *loadImage(const char *name)
433     {
434         return defaultLoader().loadImage(name);
435     }
436
437     qtexture_t *capture(const char *name)
438     {
439         return capture(defaultLoader(), name);
440     }
441
442     qtexture_t *capture(const LoadImageCallback &loader, const char *name)
443     {
444 #if DEBUG_TEXTURES
445         globalOutputStream() << "textures capture: " << makeQuoted( name ) << '\n';
446 #endif
447         return m_qtextures.capture(TextureKey(loader, name)).get();
448     }
449
450     void release(qtexture_t *texture)
451     {
452 #if DEBUG_TEXTURES
453         globalOutputStream() << "textures release: " << makeQuoted( texture->name ) << '\n';
454 #endif
455         m_qtextures.release(TextureKey(texture->load, texture->name));
456     }
457
458     void attach(TexturesCacheObserver &observer)
459     {
460         ASSERT_MESSAGE(m_observer == 0, "TexturesMap::attach: cannot attach observer");
461         m_observer = &observer;
462     }
463
464     void detach(TexturesCacheObserver &observer)
465     {
466         ASSERT_MESSAGE(m_observer == &observer, "TexturesMap::detach: cannot detach observer");
467         m_observer = 0;
468     }
469
470     void realise()
471     {
472         if (--m_unrealised == 0) {
473             g_texture_globals.bTextureCompressionSupported = false;
474
475             if (GlobalOpenGL().ARB_texture_compression()) {
476                 g_texture_globals.bTextureCompressionSupported = true;
477                 g_texture_globals.m_bOpenGLCompressionSupported = true;
478             }
479
480             if (GlobalOpenGL().EXT_texture_compression_s3tc()) {
481                 g_texture_globals.bTextureCompressionSupported = true;
482                 g_texture_globals.m_bS3CompressionSupported = true;
483             }
484
485             switch (g_texture_globals.texture_components) {
486                 case GL_RGBA:
487                     break;
488                 case GL_COMPRESSED_RGBA_ARB:
489                     if (!g_texture_globals.m_bOpenGLCompressionSupported) {
490                         globalOutputStream()
491                                 << "OpenGL extension GL_ARB_texture_compression not supported by current graphics drivers\n";
492                         g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
493                         g_texture_globals.texture_components = GL_RGBA;
494                     }
495                     break;
496                 case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
497                 case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
498                 case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
499                     if (!g_texture_globals.m_bS3CompressionSupported) {
500                         globalOutputStream()
501                                 << "OpenGL extension GL_EXT_texture_compression_s3tc not supported by current graphics drivers\n";
502                         if (g_texture_globals.m_bOpenGLCompressionSupported) {
503                             g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_RGBA;
504                             g_texture_globals.texture_components = GL_COMPRESSED_RGBA_ARB;
505                         } else {
506                             g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
507                             g_texture_globals.texture_components = GL_RGBA;
508                         }
509                     }
510                     break;
511                 default:
512                     globalOutputStream() << "Unknown texture compression selected, reverting\n";
513                     g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
514                     g_texture_globals.texture_components = GL_RGBA;
515                     break;
516             }
517
518
519             glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size);
520             if (max_tex_size == 0) {
521                 max_tex_size = 1024;
522             }
523
524             for (qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i) {
525                 if (!(*i).value.empty()) {
526                     qtexture_realise(*(*i).value, (*i).key);
527                 }
528             }
529             if (m_observer != 0) {
530                 m_observer->realise();
531             }
532         }
533     }
534
535     void unrealise()
536     {
537         if (++m_unrealised == 1) {
538             if (m_observer != 0) {
539                 m_observer->unrealise();
540             }
541             for (qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i) {
542                 if (!(*i).value.empty()) {
543                     qtexture_unrealise(*(*i).value);
544                 }
545             }
546         }
547     }
548
549     bool realised()
550     {
551         return m_unrealised == 0;
552     }
553 };
554
555 TexturesMap *g_texturesmap;
556
557 TexturesCache &GetTexturesCache()
558 {
559     return *g_texturesmap;
560 }
561
562
563 void Textures_Realise()
564 {
565     g_texturesmap->realise();
566 }
567
568 void Textures_Unrealise()
569 {
570     g_texturesmap->unrealise();
571 }
572
573
574 Callback<void()> g_texturesModeChangedNotify;
575
576 void Textures_setModeChangedNotify(const Callback<void()> &notify)
577 {
578     g_texturesModeChangedNotify = notify;
579 }
580
581 void Textures_ModeChanged()
582 {
583     if (g_texturesmap->realised()) {
584         SetTexParameters(g_texture_mode);
585
586         for (TexturesMap::iterator i = g_texturesmap->begin(); i != g_texturesmap->end(); ++i) {
587             glBindTexture(GL_TEXTURE_2D, (*i).value->texture_number);
588             SetTexParameters(g_texture_mode);
589         }
590
591         glBindTexture(GL_TEXTURE_2D, 0);
592     }
593     g_texturesModeChangedNotify();
594 }
595
596 void Textures_SetMode(ETexturesMode mode)
597 {
598     if (g_texture_mode != mode) {
599         g_texture_mode = mode;
600
601         Textures_ModeChanged();
602     }
603 }
604
605 void Textures_setTextureComponents(GLint texture_components)
606 {
607     if (g_texture_globals.texture_components != texture_components) {
608         Textures_Unrealise();
609         g_texture_globals.texture_components = texture_components;
610         Textures_Realise();
611     }
612 }
613
614 void Textures_UpdateTextureCompressionFormat()
615 {
616     GLint texture_components = GL_RGBA;
617
618     switch (g_texture_globals.m_nTextureCompressionFormat) {
619         case (TEXTURECOMPRESSION_NONE): {
620             texture_components = GL_RGBA;
621             break;
622         }
623         case (TEXTURECOMPRESSION_RGBA): {
624             texture_components = GL_COMPRESSED_RGBA_ARB;
625             break;
626         }
627         case (TEXTURECOMPRESSION_RGBA_S3TC_DXT1): {
628             texture_components = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
629             break;
630         }
631         case (TEXTURECOMPRESSION_RGBA_S3TC_DXT3): {
632             texture_components = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
633             break;
634         }
635         case (TEXTURECOMPRESSION_RGBA_S3TC_DXT5): {
636             texture_components = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
637             break;
638         }
639     }
640
641     Textures_setTextureComponents(texture_components);
642 }
643
644 struct TextureCompression {
645     static void Export(const TextureCompressionFormat &self, const Callback<void(int)> &returnz)
646     {
647         returnz(self);
648     }
649
650     static void Import(TextureCompressionFormat &self, int value)
651     {
652         if (!g_texture_globals.m_bOpenGLCompressionSupported
653             && g_texture_globals.m_bS3CompressionSupported
654             && value >= 1) {
655             ++value;
656         }
657         switch (value) {
658             case 0:
659                 self = TEXTURECOMPRESSION_NONE;
660                 break;
661             case 1:
662                 self = TEXTURECOMPRESSION_RGBA;
663                 break;
664             case 2:
665                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT1;
666                 break;
667             case 3:
668                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT3;
669                 break;
670             case 4:
671                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT5;
672                 break;
673         }
674         Textures_UpdateTextureCompressionFormat();
675     }
676 };
677
678 struct TextureGamma {
679     static void Export(const float &self, const Callback<void(float)> &returnz)
680     {
681         returnz(self);
682     }
683
684     static void Import(float &self, float value)
685     {
686         if (value != self) {
687             Textures_Unrealise();
688             self = value;
689             Textures_Realise();
690         }
691     }
692 };
693
694 struct TextureMode {
695     static void Export(const ETexturesMode &self, const Callback<void(int)> &returnz)
696     {
697         switch (self) {
698             case eTextures_NEAREST:
699                 returnz(0);
700                 break;
701             case eTextures_NEAREST_MIPMAP_NEAREST:
702                 returnz(1);
703                 break;
704             case eTextures_LINEAR:
705                 returnz(2);
706                 break;
707             case eTextures_NEAREST_MIPMAP_LINEAR:
708                 returnz(3);
709                 break;
710             case eTextures_LINEAR_MIPMAP_NEAREST:
711                 returnz(4);
712                 break;
713             case eTextures_LINEAR_MIPMAP_LINEAR:
714                 returnz(5);
715                 break;
716             case eTextures_MAX_ANISOTROPY:
717                 returnz(6);
718                 break;
719             default:
720                 returnz(4);
721         }
722     }
723
724     static void Import(ETexturesMode &self, int value)
725     {
726         switch (value) {
727             case 0:
728                 Textures_SetMode(eTextures_NEAREST);
729                 break;
730             case 1:
731                 Textures_SetMode(eTextures_NEAREST_MIPMAP_NEAREST);
732                 break;
733             case 2:
734                 Textures_SetMode(eTextures_LINEAR);
735                 break;
736             case 3:
737                 Textures_SetMode(eTextures_NEAREST_MIPMAP_LINEAR);
738                 break;
739             case 4:
740                 Textures_SetMode(eTextures_LINEAR_MIPMAP_NEAREST);
741                 break;
742             case 5:
743                 Textures_SetMode(eTextures_LINEAR_MIPMAP_LINEAR);
744                 break;
745             case 6:
746                 Textures_SetMode(eTextures_MAX_ANISOTROPY);
747         }
748     }
749 };
750
751 void Textures_constructPreferences(PreferencesPage &page)
752 {
753     {
754         const char *percentages[] = {"12.5%", "25%", "50%", "100%",};
755         page.appendRadio(
756                 "Texture Quality",
757                 STRING_ARRAY_RANGE(percentages),
758                 make_property(g_Textures_textureQuality)
759         );
760     }
761     page.appendSpinner(
762             "Texture Gamma",
763             1.0,
764             0.0,
765             1.0,
766             make_property<TextureGamma>(g_texture_globals.fGamma)
767     );
768     {
769         const char *texture_mode[] = {"Nearest", "Nearest Mipmap", "Linear", "Bilinear", "Bilinear Mipmap", "Trilinear",
770                                       "Anisotropy"};
771         page.appendCombo(
772                 "Texture Render Mode",
773                 STRING_ARRAY_RANGE(texture_mode),
774                 make_property<TextureMode>(g_texture_mode)
775         );
776     }
777     {
778         const char *compression_none[] = {"None"};
779         const char *compression_opengl[] = {"None", "OpenGL ARB"};
780         const char *compression_s3tc[] = {"None", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5"};
781         const char *compression_opengl_s3tc[] = {"None", "OpenGL ARB", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5"};
782         StringArrayRange compression(
783                 (g_texture_globals.m_bOpenGLCompressionSupported)
784                 ? (g_texture_globals.m_bS3CompressionSupported)
785                   ? STRING_ARRAY_RANGE(compression_opengl_s3tc)
786                   : STRING_ARRAY_RANGE(compression_opengl)
787                 : (g_texture_globals.m_bS3CompressionSupported)
788                   ? STRING_ARRAY_RANGE(compression_s3tc)
789                   : STRING_ARRAY_RANGE(compression_none)
790         );
791         page.appendCombo(
792                 "Hardware Texture Compression",
793                 compression,
794                 make_property<TextureCompression>(g_texture_globals.m_nTextureCompressionFormat)
795         );
796     }
797 }
798
799 void Textures_constructPage(PreferenceGroup &group)
800 {
801     PreferencesPage page(group.createPage("Textures", "Texture Settings"));
802     Textures_constructPreferences(page);
803 }
804
805 void Textures_registerPreferencesPage()
806 {
807     PreferencesDialog_addDisplayPage(makeCallbackF(Textures_constructPage));
808 }
809
810 struct TextureCompressionPreference {
811     static void Export(const Callback<void(int)> &returnz)
812     {
813         returnz(g_texture_globals.m_nTextureCompressionFormat);
814     }
815
816     static void Import(int value)
817     {
818         g_texture_globals.m_nTextureCompressionFormat = static_cast<TextureCompressionFormat>( value );
819         Textures_UpdateTextureCompressionFormat();
820     }
821 };
822
823 void Textures_Construct()
824 {
825     g_texturesmap = new TexturesMap;
826
827     GlobalPreferenceSystem().registerPreference("TextureCompressionFormat",
828                                                 make_property_string<TextureCompressionPreference>());
829     GlobalPreferenceSystem().registerPreference("TextureFiltering",
830                                                 make_property_string(reinterpret_cast<int &>( g_texture_mode )));
831     GlobalPreferenceSystem().registerPreference("TextureQuality",
832                                                 make_property_string(g_Textures_textureQuality.m_latched));
833     GlobalPreferenceSystem().registerPreference("SI_Gamma", make_property_string(g_texture_globals.fGamma));
834
835     g_Textures_textureQuality.useLatched();
836
837     Textures_registerPreferencesPage();
838
839     Textures_ModeChanged();
840 }
841
842 void Textures_Destroy()
843 {
844     delete g_texturesmap;
845 }
846
847
848 #include "modulesystem/modulesmap.h"
849 #include "modulesystem/singletonmodule.h"
850 #include "modulesystem/moduleregistry.h"
851
852 class TexturesDependencies :
853         public GlobalRadiantModuleRef,
854         public GlobalOpenGLModuleRef,
855         public GlobalPreferenceSystemModuleRef {
856     ImageModulesRef m_image_modules;
857 public:
858     TexturesDependencies() :
859             m_image_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("texturetypes"))
860     {
861     }
862
863     ImageModules &getImageModules()
864     {
865         return m_image_modules.get();
866     }
867 };
868
869 class TexturesAPI {
870     TexturesCache *m_textures;
871 public:
872     typedef TexturesCache Type;
873
874     STRING_CONSTANT(Name, "*");
875
876     TexturesAPI()
877     {
878         Textures_Construct();
879
880         m_textures = &GetTexturesCache();
881     }
882
883     ~TexturesAPI()
884     {
885         Textures_Destroy();
886     }
887
888     TexturesCache *getTable()
889     {
890         return m_textures;
891     }
892 };
893
894 typedef SingletonModule<TexturesAPI, TexturesDependencies> TexturesModule;
895 typedef Static<TexturesModule> StaticTexturesModule;
896 StaticRegisterModule staticRegisterTextures(StaticTexturesModule::instance());
897
898 ImageModules &Textures_getImageModules()
899 {
900     return StaticTexturesModule::instance().getDependencies().getImageModules();
901 }