]> git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/textures.cpp
Inject OpenGLBinding instead of using GlobalOpenGL() everywhere
[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(OpenGLBinding &GL, 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(OpenGLBinding &GL, 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(GL, 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(OpenGLBinding &GL, 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(GL, &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(OpenGLBinding &GL, qtexture_t &texture)
349 {
350     if (GL.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             OpenGLBinding &GL = GlobalOpenGL();
388             qtexture_t *texture = new qtexture_t(key.first, key.second.c_str());
389             if (m_cache->realised()) {
390                 qtexture_realise(GL, *texture, key);
391             }
392             return texture;
393         }
394
395         void destroy(qtexture_t *texture)
396         {
397             OpenGLBinding &GL = GlobalOpenGL();
398             if (m_cache->realised()) {
399                 qtexture_unrealise(GL, *texture);
400             }
401             delete texture;
402         }
403     };
404
405     typedef HashedCache<TextureKey, qtexture_t, TextureKeyHashNoCase, TextureKeyEqualNoCase, TextureConstructor> qtextures_t;
406     qtextures_t m_qtextures;
407     TexturesCacheObserver *m_observer;
408     std::size_t m_unrealised;
409
410 public:
411     virtual ~TexturesMap() = default;
412
413     TexturesMap() : m_qtextures(TextureConstructor(this)), m_observer(0), m_unrealised(1)
414     {
415     }
416
417     typedef qtextures_t::iterator iterator;
418
419     iterator begin()
420     {
421         return m_qtextures.begin();
422     }
423
424     iterator end()
425     {
426         return m_qtextures.end();
427     }
428
429     LoadImageCallback defaultLoader() const
430     {
431         return LoadImageCallback(0, QERApp_LoadImage);
432     }
433
434     Image *loadImage(const char *name)
435     {
436         return defaultLoader().loadImage(name);
437     }
438
439     qtexture_t *capture(const char *name)
440     {
441         return capture(defaultLoader(), name);
442     }
443
444     qtexture_t *capture(const LoadImageCallback &loader, const char *name)
445     {
446 #if DEBUG_TEXTURES
447         globalOutputStream() << "textures capture: " << makeQuoted( name ) << '\n';
448 #endif
449         return m_qtextures.capture(TextureKey(loader, name)).get();
450     }
451
452     void release(qtexture_t *texture)
453     {
454 #if DEBUG_TEXTURES
455         globalOutputStream() << "textures release: " << makeQuoted( texture->name ) << '\n';
456 #endif
457         m_qtextures.release(TextureKey(texture->load, texture->name));
458     }
459
460     void attach(TexturesCacheObserver &observer)
461     {
462         ASSERT_MESSAGE(m_observer == 0, "TexturesMap::attach: cannot attach observer");
463         m_observer = &observer;
464     }
465
466     void detach(TexturesCacheObserver &observer)
467     {
468         ASSERT_MESSAGE(m_observer == &observer, "TexturesMap::detach: cannot detach observer");
469         m_observer = 0;
470     }
471
472     void realise(OpenGLBinding &GL)
473     {
474         if (--m_unrealised == 0) {
475             g_texture_globals.bTextureCompressionSupported = false;
476
477             if (GL.ARB_texture_compression()) {
478                 g_texture_globals.bTextureCompressionSupported = true;
479                 g_texture_globals.m_bOpenGLCompressionSupported = true;
480             }
481
482             if (GL.EXT_texture_compression_s3tc()) {
483                 g_texture_globals.bTextureCompressionSupported = true;
484                 g_texture_globals.m_bS3CompressionSupported = true;
485             }
486
487             switch (g_texture_globals.texture_components) {
488                 case GL_RGBA:
489                     break;
490                 case GL_COMPRESSED_RGBA_ARB:
491                     if (!g_texture_globals.m_bOpenGLCompressionSupported) {
492                         globalOutputStream()
493                                 << "OpenGL extension GL_ARB_texture_compression not supported by current graphics drivers\n";
494                         g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
495                         g_texture_globals.texture_components = GL_RGBA;
496                     }
497                     break;
498                 case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
499                 case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
500                 case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
501                     if (!g_texture_globals.m_bS3CompressionSupported) {
502                         globalOutputStream()
503                                 << "OpenGL extension GL_EXT_texture_compression_s3tc not supported by current graphics drivers\n";
504                         if (g_texture_globals.m_bOpenGLCompressionSupported) {
505                             g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_RGBA;
506                             g_texture_globals.texture_components = GL_COMPRESSED_RGBA_ARB;
507                         } else {
508                             g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
509                             g_texture_globals.texture_components = GL_RGBA;
510                         }
511                     }
512                     break;
513                 default:
514                     globalOutputStream() << "Unknown texture compression selected, reverting\n";
515                     g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
516                     g_texture_globals.texture_components = GL_RGBA;
517                     break;
518             }
519
520
521             glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size);
522             if (max_tex_size == 0) {
523                 max_tex_size = 1024;
524             }
525
526             for (qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i) {
527                 if (!(*i).value.empty()) {
528                     qtexture_realise(GL, *(*i).value, (*i).key);
529                 }
530             }
531             if (m_observer != 0) {
532                 m_observer->realise();
533             }
534         }
535     }
536
537     void unrealise(OpenGLBinding &GL)
538     {
539         if (++m_unrealised == 1) {
540             if (m_observer != 0) {
541                 m_observer->unrealise();
542             }
543             for (qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i) {
544                 if (!(*i).value.empty()) {
545                     qtexture_unrealise(GL, *(*i).value);
546                 }
547             }
548         }
549     }
550
551     bool realised()
552     {
553         return m_unrealised == 0;
554     }
555 };
556
557 TexturesMap *g_texturesmap;
558
559 TexturesCache &GetTexturesCache()
560 {
561     return *g_texturesmap;
562 }
563
564
565 void Textures_Realise(OpenGLBinding &GL)
566 {
567     g_texturesmap->realise(GL);
568 }
569
570 void Textures_Unrealise(OpenGLBinding &GL)
571 {
572     g_texturesmap->unrealise(GL);
573 }
574
575
576 Callback<void()> g_texturesModeChangedNotify;
577
578 void Textures_setModeChangedNotify(const Callback<void()> &notify)
579 {
580     g_texturesModeChangedNotify = notify;
581 }
582
583 void Textures_ModeChanged(OpenGLBinding &GL)
584 {
585     if (g_texturesmap->realised()) {
586         SetTexParameters(GL, g_texture_mode);
587
588         for (TexturesMap::iterator i = g_texturesmap->begin(); i != g_texturesmap->end(); ++i) {
589             glBindTexture(GL_TEXTURE_2D, (*i).value->texture_number);
590             SetTexParameters(GL, g_texture_mode);
591         }
592
593         glBindTexture(GL_TEXTURE_2D, 0);
594     }
595     g_texturesModeChangedNotify();
596 }
597
598 void Textures_SetMode(OpenGLBinding &GL, ETexturesMode mode)
599 {
600     if (g_texture_mode != mode) {
601         g_texture_mode = mode;
602
603         Textures_ModeChanged(GL);
604     }
605 }
606
607 void Textures_setTextureComponents(OpenGLBinding &GL, GLint texture_components)
608 {
609     if (g_texture_globals.texture_components != texture_components) {
610         Textures_Unrealise(GL);
611         g_texture_globals.texture_components = texture_components;
612         Textures_Realise(GL);
613     }
614 }
615
616 void Textures_UpdateTextureCompressionFormat()
617 {
618     OpenGLBinding &GL = GlobalOpenGL();
619     GLint texture_components = GL_RGBA;
620
621     switch (g_texture_globals.m_nTextureCompressionFormat) {
622         case (TEXTURECOMPRESSION_NONE): {
623             texture_components = GL_RGBA;
624             break;
625         }
626         case (TEXTURECOMPRESSION_RGBA): {
627             texture_components = GL_COMPRESSED_RGBA_ARB;
628             break;
629         }
630         case (TEXTURECOMPRESSION_RGBA_S3TC_DXT1): {
631             texture_components = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
632             break;
633         }
634         case (TEXTURECOMPRESSION_RGBA_S3TC_DXT3): {
635             texture_components = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
636             break;
637         }
638         case (TEXTURECOMPRESSION_RGBA_S3TC_DXT5): {
639             texture_components = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
640             break;
641         }
642     }
643
644     Textures_setTextureComponents(GL, texture_components);
645 }
646
647 struct TextureCompression {
648     static void Export(const TextureCompressionFormat &self, const Callback<void(int)> &returnz)
649     {
650         returnz(self);
651     }
652
653     static void Import(TextureCompressionFormat &self, int value)
654     {
655         if (!g_texture_globals.m_bOpenGLCompressionSupported
656             && g_texture_globals.m_bS3CompressionSupported
657             && value >= 1) {
658             ++value;
659         }
660         switch (value) {
661             case 0:
662                 self = TEXTURECOMPRESSION_NONE;
663                 break;
664             case 1:
665                 self = TEXTURECOMPRESSION_RGBA;
666                 break;
667             case 2:
668                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT1;
669                 break;
670             case 3:
671                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT3;
672                 break;
673             case 4:
674                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT5;
675                 break;
676         }
677         Textures_UpdateTextureCompressionFormat();
678     }
679 };
680
681 struct TextureGamma {
682     static void Export(const float &self, const Callback<void(float)> &returnz)
683     {
684         returnz(self);
685     }
686
687     static void Import(float &self, float value)
688     {
689         OpenGLBinding &GL = GlobalOpenGL();
690         if (value != self) {
691             Textures_Unrealise(GL);
692             self = value;
693             Textures_Realise(GL);
694         }
695     }
696 };
697
698 struct TextureMode {
699     static void Export(const ETexturesMode &self, const Callback<void(int)> &returnz)
700     {
701         switch (self) {
702             case eTextures_NEAREST:
703                 returnz(0);
704                 break;
705             case eTextures_NEAREST_MIPMAP_NEAREST:
706                 returnz(1);
707                 break;
708             case eTextures_LINEAR:
709                 returnz(2);
710                 break;
711             case eTextures_NEAREST_MIPMAP_LINEAR:
712                 returnz(3);
713                 break;
714             case eTextures_LINEAR_MIPMAP_NEAREST:
715                 returnz(4);
716                 break;
717             case eTextures_LINEAR_MIPMAP_LINEAR:
718                 returnz(5);
719                 break;
720             case eTextures_MAX_ANISOTROPY:
721                 returnz(6);
722                 break;
723             default:
724                 returnz(4);
725         }
726     }
727
728     static void Import(ETexturesMode &self, int value)
729     {
730         OpenGLBinding &GL = GlobalOpenGL();
731         switch (value) {
732             case 0:
733                 Textures_SetMode(GL, eTextures_NEAREST);
734                 break;
735             case 1:
736                 Textures_SetMode(GL, eTextures_NEAREST_MIPMAP_NEAREST);
737                 break;
738             case 2:
739                 Textures_SetMode(GL, eTextures_LINEAR);
740                 break;
741             case 3:
742                 Textures_SetMode(GL, eTextures_NEAREST_MIPMAP_LINEAR);
743                 break;
744             case 4:
745                 Textures_SetMode(GL, eTextures_LINEAR_MIPMAP_NEAREST);
746                 break;
747             case 5:
748                 Textures_SetMode(GL, eTextures_LINEAR_MIPMAP_LINEAR);
749                 break;
750             case 6:
751                 Textures_SetMode(GL, eTextures_MAX_ANISOTROPY);
752         }
753     }
754 };
755
756 void Textures_constructPreferences(PreferencesPage &page)
757 {
758     {
759         const char *percentages[] = {"12.5%", "25%", "50%", "100%",};
760         page.appendRadio(
761                 "Texture Quality",
762                 STRING_ARRAY_RANGE(percentages),
763                 make_property(g_Textures_textureQuality)
764         );
765     }
766     page.appendSpinner(
767             "Texture Gamma",
768             1.0,
769             0.0,
770             1.0,
771             make_property<TextureGamma>(g_texture_globals.fGamma)
772     );
773     {
774         const char *texture_mode[] = {"Nearest", "Nearest Mipmap", "Linear", "Bilinear", "Bilinear Mipmap", "Trilinear",
775                                       "Anisotropy"};
776         page.appendCombo(
777                 "Texture Render Mode",
778                 STRING_ARRAY_RANGE(texture_mode),
779                 make_property<TextureMode>(g_texture_mode)
780         );
781     }
782     {
783         const char *compression_none[] = {"None"};
784         const char *compression_opengl[] = {"None", "OpenGL ARB"};
785         const char *compression_s3tc[] = {"None", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5"};
786         const char *compression_opengl_s3tc[] = {"None", "OpenGL ARB", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5"};
787         StringArrayRange compression(
788                 (g_texture_globals.m_bOpenGLCompressionSupported)
789                 ? (g_texture_globals.m_bS3CompressionSupported)
790                   ? STRING_ARRAY_RANGE(compression_opengl_s3tc)
791                   : STRING_ARRAY_RANGE(compression_opengl)
792                 : (g_texture_globals.m_bS3CompressionSupported)
793                   ? STRING_ARRAY_RANGE(compression_s3tc)
794                   : STRING_ARRAY_RANGE(compression_none)
795         );
796         page.appendCombo(
797                 "Hardware Texture Compression",
798                 compression,
799                 make_property<TextureCompression>(g_texture_globals.m_nTextureCompressionFormat)
800         );
801     }
802 }
803
804 void Textures_constructPage(PreferenceGroup &group)
805 {
806     PreferencesPage page(group.createPage("Textures", "Texture Settings"));
807     Textures_constructPreferences(page);
808 }
809
810 void Textures_registerPreferencesPage()
811 {
812     PreferencesDialog_addDisplayPage(makeCallbackF(Textures_constructPage));
813 }
814
815 struct TextureCompressionPreference {
816     static void Export(const Callback<void(int)> &returnz)
817     {
818         returnz(g_texture_globals.m_nTextureCompressionFormat);
819     }
820
821     static void Import(int value)
822     {
823         g_texture_globals.m_nTextureCompressionFormat = static_cast<TextureCompressionFormat>( value );
824         Textures_UpdateTextureCompressionFormat();
825     }
826 };
827
828 void Textures_Construct(OpenGLBinding &GL)
829 {
830     g_texturesmap = new TexturesMap;
831
832     GlobalPreferenceSystem().registerPreference("TextureCompressionFormat",
833                                                 make_property_string<TextureCompressionPreference>());
834     GlobalPreferenceSystem().registerPreference("TextureFiltering",
835                                                 make_property_string(reinterpret_cast<int &>( g_texture_mode )));
836     GlobalPreferenceSystem().registerPreference("TextureQuality",
837                                                 make_property_string(g_Textures_textureQuality.m_latched));
838     GlobalPreferenceSystem().registerPreference("SI_Gamma", make_property_string(g_texture_globals.fGamma));
839
840     g_Textures_textureQuality.useLatched();
841
842     Textures_registerPreferencesPage();
843
844     Textures_ModeChanged(GL);
845 }
846
847 void Textures_Destroy()
848 {
849     delete g_texturesmap;
850 }
851
852
853 #include "modulesystem/modulesmap.h"
854 #include "modulesystem/singletonmodule.h"
855 #include "modulesystem/moduleregistry.h"
856
857 class TexturesDependencies :
858         public GlobalRadiantModuleRef,
859         public GlobalOpenGLModuleRef,
860         public GlobalPreferenceSystemModuleRef {
861     ImageModulesRef m_image_modules;
862 public:
863     TexturesDependencies() :
864             m_image_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("texturetypes"))
865     {
866     }
867
868     ImageModules &getImageModules()
869     {
870         return m_image_modules.get();
871     }
872 };
873
874 class TexturesAPI {
875     TexturesCache *m_textures;
876 public:
877     typedef TexturesCache Type;
878
879     STRING_CONSTANT(Name, "*");
880
881     TexturesAPI()
882     {
883         OpenGLBinding &GL = GlobalOpenGL();
884         Textures_Construct(GL);
885
886         m_textures = &GetTexturesCache();
887     }
888
889     ~TexturesAPI()
890     {
891         Textures_Destroy();
892     }
893
894     TexturesCache *getTable()
895     {
896         return m_textures;
897     }
898 };
899
900 typedef SingletonModule<TexturesAPI, TexturesDependencies> TexturesModule;
901 typedef Static<TexturesModule> StaticTexturesModule;
902 StaticRegisterModule staticRegisterTextures(StaticTexturesModule::instance());
903
904 ImageModules &Textures_getImageModules()
905 {
906     return StaticTexturesModule::instance().getDependencies().getImageModules();
907 }