Group import/export callbacks
[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 void TextureCompressionImport( TextureCompressionFormat& self, int value ){
625         if ( !g_texture_globals.m_bOpenGLCompressionSupported
626                  && g_texture_globals.m_bS3CompressionSupported
627                  && value >= 1 ) {
628                 ++value;
629         }
630         switch ( value )
631         {
632         case 0:
633                 self = TEXTURECOMPRESSION_NONE;
634                 break;
635         case 1:
636                 self = TEXTURECOMPRESSION_RGBA;
637                 break;
638         case 2:
639                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT1;
640                 break;
641         case 3:
642                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT3;
643                 break;
644         case 4:
645                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT5;
646                 break;
647         }
648         Textures_UpdateTextureCompressionFormat();
649 }
650 typedef ReferenceCaller<TextureCompressionFormat, void(int), TextureCompressionImport> TextureCompressionImportCaller;
651
652 void TextureGammaImport( float& self, float value ){
653         if ( self != value ) {
654                 Textures_Unrealise();
655                 self = value;
656                 Textures_Realise();
657         }
658 }
659 typedef ReferenceCaller<float, void(float), TextureGammaImport> TextureGammaImportCaller;
660
661 void TextureModeImport( ETexturesMode& self, int value ){
662         switch ( value )
663         {
664         case 0:
665                 Textures_SetMode( eTextures_NEAREST );
666                 break;
667         case 1:
668                 Textures_SetMode( eTextures_NEAREST_MIPMAP_NEAREST );
669                 break;
670         case 2:
671                 Textures_SetMode( eTextures_LINEAR );
672                 break;
673         case 3:
674                 Textures_SetMode( eTextures_NEAREST_MIPMAP_LINEAR );
675                 break;
676         case 4:
677                 Textures_SetMode( eTextures_LINEAR_MIPMAP_NEAREST );
678                 break;
679         case 5:
680                 Textures_SetMode( eTextures_LINEAR_MIPMAP_LINEAR );
681                 break;
682         case 6:
683                 Textures_SetMode( eTextures_MAX_ANISOTROPY );
684         }
685 }
686 typedef ReferenceCaller<ETexturesMode, void(int), TextureModeImport> TextureModeImportCaller;
687
688 void TextureModeExport( ETexturesMode& self, const ImportExportCallback<int>::Import_t& importer ){
689         switch ( self )
690         {
691         case eTextures_NEAREST:
692                 importer( 0 );
693                 break;
694         case eTextures_NEAREST_MIPMAP_NEAREST:
695                 importer( 1 );
696                 break;
697         case eTextures_LINEAR:
698                 importer( 2 );
699                 break;
700         case eTextures_NEAREST_MIPMAP_LINEAR:
701                 importer( 3 );
702                 break;
703         case eTextures_LINEAR_MIPMAP_NEAREST:
704                 importer( 4 );
705                 break;
706         case eTextures_LINEAR_MIPMAP_LINEAR:
707                 importer( 5 );
708                 break;
709         case eTextures_MAX_ANISOTROPY:
710                 importer( 6 );
711                 break;
712         default:
713                 importer( 4 );
714         }
715 }
716 typedef ReferenceCaller<ETexturesMode, void(const ImportExportCallback<int>::Import_t&), TextureModeExport> TextureModeExportCaller;
717
718 void Textures_constructPreferences( PreferencesPage& page ){
719         {
720                 const char* percentages[] = { "12.5%", "25%", "50%", "100%", };
721                 page.appendRadio(
722                         "Texture Quality",
723                         STRING_ARRAY_RANGE( percentages ),
724                         mkImportExportCallback( g_Textures_textureQuality )
725                         );
726         }
727         page.appendSpinner(
728                 "Texture Gamma",
729                 1.0,
730                 0.0,
731                 1.0,
732                 {ImportExportCallback<float>::Import_t( TextureGammaImportCaller( g_texture_globals.fGamma ) ),
733                 ImportExportCallback<float>::Export_t( FloatExportCaller( g_texture_globals.fGamma ) )}
734                 );
735         {
736                 const char* texture_mode[] = { "Nearest", "Nearest Mipmap", "Linear", "Bilinear", "Bilinear Mipmap", "Trilinear", "Anisotropy" };
737                 page.appendCombo(
738                         "Texture Render Mode",
739                         STRING_ARRAY_RANGE( texture_mode ),
740                         {ImportExportCallback<int>::Import_t( TextureModeImportCaller( g_texture_mode ) ),
741                         ImportExportCallback<int>::Export_t( TextureModeExportCaller( g_texture_mode ) )}
742                         );
743         }
744         {
745                 const char* compression_none[] = { "None" };
746                 const char* compression_opengl[] = { "None", "OpenGL ARB" };
747                 const char* compression_s3tc[] = { "None", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5" };
748                 const char* compression_opengl_s3tc[] = { "None", "OpenGL ARB", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5" };
749                 StringArrayRange compression(
750                         ( g_texture_globals.m_bOpenGLCompressionSupported )
751                         ? ( g_texture_globals.m_bS3CompressionSupported )
752                         ? STRING_ARRAY_RANGE( compression_opengl_s3tc )
753                         : STRING_ARRAY_RANGE( compression_opengl )
754                         : ( g_texture_globals.m_bS3CompressionSupported )
755                         ? STRING_ARRAY_RANGE( compression_s3tc )
756                         : STRING_ARRAY_RANGE( compression_none )
757                         );
758                 page.appendCombo(
759                         "Hardware Texture Compression",
760                         compression,
761                         {TextureCompressionImportCaller( g_texture_globals.m_nTextureCompressionFormat ),
762                         IntExportCaller( reinterpret_cast<int&>( g_texture_globals.m_nTextureCompressionFormat ) )}
763                         );
764         }
765 }
766 void Textures_constructPage( PreferenceGroup& group ){
767         PreferencesPage page( group.createPage( "Textures", "Texture Settings" ) );
768         Textures_constructPreferences( page );
769 }
770 void Textures_registerPreferencesPage(){
771         PreferencesDialog_addDisplayPage( makeCallbackF(Textures_constructPage) );
772 }
773
774 void TextureCompression_importString( const char* string ){
775         g_texture_globals.m_nTextureCompressionFormat = static_cast<TextureCompressionFormat>( atoi( string ) );
776         Textures_UpdateTextureCompressionFormat();
777 }
778 typedef FreeCaller<void(const char*), TextureCompression_importString> TextureCompressionImportStringCaller;
779
780
781 void Textures_Construct(){
782         g_texturesmap = new TexturesMap;
783
784         GlobalPreferenceSystem().registerPreference( "TextureCompressionFormat", TextureCompressionImportStringCaller(), IntExportStringCaller( reinterpret_cast<int&>( g_texture_globals.m_nTextureCompressionFormat ) ) );
785         GlobalPreferenceSystem().registerPreference( "TextureFiltering", IntImportStringCaller( reinterpret_cast<int&>( g_texture_mode ) ), IntExportStringCaller( reinterpret_cast<int&>( g_texture_mode ) ) );
786         GlobalPreferenceSystem().registerPreference( "TextureQuality", IntImportStringCaller( g_Textures_textureQuality.m_latched ), IntExportStringCaller( g_Textures_textureQuality.m_latched ) );
787         GlobalPreferenceSystem().registerPreference( "SI_Gamma", FloatImportStringCaller( g_texture_globals.fGamma ), FloatExportStringCaller( g_texture_globals.fGamma ) );
788
789         g_Textures_textureQuality.useLatched();
790
791         Textures_registerPreferencesPage();
792
793         Textures_ModeChanged();
794 }
795 void Textures_Destroy(){
796         delete g_texturesmap;
797 }
798
799
800 #include "modulesystem/modulesmap.h"
801 #include "modulesystem/singletonmodule.h"
802 #include "modulesystem/moduleregistry.h"
803
804 class TexturesDependencies :
805         public GlobalRadiantModuleRef,
806         public GlobalOpenGLModuleRef,
807         public GlobalPreferenceSystemModuleRef
808 {
809 ImageModulesRef m_image_modules;
810 public:
811 TexturesDependencies() :
812         m_image_modules( GlobalRadiant().getRequiredGameDescriptionKeyValue( "texturetypes" ) ){
813 }
814 ImageModules& getImageModules(){
815         return m_image_modules.get();
816 }
817 };
818
819 class TexturesAPI
820 {
821 TexturesCache* m_textures;
822 public:
823 typedef TexturesCache Type;
824 STRING_CONSTANT( Name, "*" );
825
826 TexturesAPI(){
827         Textures_Construct();
828
829         m_textures = &GetTexturesCache();
830 }
831 ~TexturesAPI(){
832         Textures_Destroy();
833 }
834 TexturesCache* getTable(){
835         return m_textures;
836 }
837 };
838
839 typedef SingletonModule<TexturesAPI, TexturesDependencies> TexturesModule;
840 typedef Static<TexturesModule> StaticTexturesModule;
841 StaticRegisterModule staticRegisterTextures( StaticTexturesModule::instance() );
842
843 ImageModules& Textures_getImageModules(){
844         return StaticTexturesModule::instance().getDependencies().getImageModules();
845 }