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