]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/textures.cpp
Remove some dead/debug code
[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 LatchedInt 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 typedef std::pair<LoadImageCallback, CopiedString> TextureKey;
268
269 void qtexture_realise( qtexture_t& texture, const TextureKey& key ){
270         texture.texture_number = 0;
271         if ( !string_empty( key.second.c_str() ) ) {
272                 Image* image = key.first.loadImage( key.second.c_str() );
273                 if ( image != 0 ) {
274                         LoadTextureRGBA( &texture, image->getRGBAPixels(), image->getWidth(), image->getHeight() );
275                         texture.surfaceFlags = image->getSurfaceFlags();
276                         texture.contentFlags = image->getContentFlags();
277                         texture.value = image->getValue();
278                         image->release();
279                         globalOutputStream() << "Loaded Texture: \"" << key.second.c_str() << "\"\n";
280                         GlobalOpenGL_debugAssertNoErrors();
281                 }
282                 else
283                 {
284                         globalErrorStream() << "Texture load failed: \"" << key.second.c_str() << "\"\n";
285                 }
286         }
287 }
288
289 void qtexture_unrealise( qtexture_t& texture ){
290         if ( GlobalOpenGL().contextValid && texture.texture_number != 0 ) {
291                 glDeleteTextures( 1, &texture.texture_number );
292                 GlobalOpenGL_debugAssertNoErrors();
293         }
294 }
295
296 class TextureKeyEqualNoCase
297 {
298 public:
299 bool operator()( const TextureKey& key, const TextureKey& other ) const {
300         return key.first == other.first && string_equal_nocase( key.second.c_str(), other.second.c_str() );
301 }
302 };
303
304 class TextureKeyHashNoCase
305 {
306 public:
307 typedef hash_t hash_type;
308 hash_t operator()( const TextureKey& key ) const {
309         return hash_combine( string_hash_nocase( key.second.c_str() ), pod_hash( key.first ) );
310 }
311 };
312
313 class TexturesMap : public TexturesCache
314 {
315 class TextureConstructor
316 {
317 TexturesMap* m_cache;
318 public:
319 explicit TextureConstructor( TexturesMap* cache )
320         : m_cache( cache ){
321 }
322 qtexture_t* construct( const TextureKey& key ){
323         qtexture_t* texture = new qtexture_t( key.first, key.second.c_str() );
324         if ( m_cache->realised() ) {
325                 qtexture_realise( *texture, key );
326         }
327         return texture;
328 }
329 void destroy( qtexture_t* texture ){
330         if ( m_cache->realised() ) {
331                 qtexture_unrealise( *texture );
332         }
333         delete texture;
334 }
335 };
336
337 typedef HashedCache<TextureKey, qtexture_t, TextureKeyHashNoCase, TextureKeyEqualNoCase, TextureConstructor> qtextures_t;
338 qtextures_t m_qtextures;
339 TexturesCacheObserver* m_observer;
340 std::size_t m_unrealised;
341
342 public:
343 TexturesMap() : m_qtextures( TextureConstructor( this ) ), m_observer( 0 ), m_unrealised( 1 ){
344 }
345 typedef qtextures_t::iterator iterator;
346
347 iterator begin(){
348         return m_qtextures.begin();
349 }
350 iterator end(){
351         return m_qtextures.end();
352 }
353
354 LoadImageCallback defaultLoader() const {
355         return LoadImageCallback( 0, QERApp_LoadImage );
356 }
357 Image* loadImage( const char* name ){
358         return defaultLoader().loadImage( name );
359 }
360 qtexture_t* capture( const char* name ){
361         return capture( defaultLoader(), name );
362 }
363 qtexture_t* capture( const LoadImageCallback& loader, const char* name ){
364         return m_qtextures.capture( TextureKey( loader, name ) ).get();
365 }
366 void release( qtexture_t* texture ){
367         m_qtextures.release( TextureKey( texture->load, texture->name ) );
368 }
369 void attach( TexturesCacheObserver& observer ){
370         ASSERT_MESSAGE( m_observer == 0, "TexturesMap::attach: cannot attach observer" );
371         m_observer = &observer;
372 }
373 void detach( TexturesCacheObserver& observer ){
374         ASSERT_MESSAGE( m_observer == &observer, "TexturesMap::detach: cannot detach observer" );
375         m_observer = 0;
376 }
377 void realise(){
378         if ( --m_unrealised == 0 ) {
379                 g_texture_globals.bTextureCompressionSupported = false;
380
381                 if ( GlobalOpenGL().ARB_texture_compression() ) {
382                         g_texture_globals.bTextureCompressionSupported = true;
383                         g_texture_globals.m_bOpenGLCompressionSupported = true;
384                 }
385
386                 if ( GlobalOpenGL().EXT_texture_compression_s3tc() ) {
387                         g_texture_globals.bTextureCompressionSupported = true;
388                         g_texture_globals.m_bS3CompressionSupported = true;
389                 }
390
391                 switch ( g_texture_globals.texture_components )
392                 {
393                 case GL_RGBA:
394                         break;
395                 case GL_COMPRESSED_RGBA_ARB:
396                         if ( !g_texture_globals.m_bOpenGLCompressionSupported ) {
397                                 globalOutputStream() << "OpenGL extension GL_ARB_texture_compression not supported by current graphics drivers\n";
398                                 g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
399                                 g_texture_globals.texture_components = GL_RGBA;
400                         }
401                         break;
402                 case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
403                 case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
404                 case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
405                         if ( !g_texture_globals.m_bS3CompressionSupported ) {
406                                 globalOutputStream() << "OpenGL extension GL_EXT_texture_compression_s3tc not supported by current graphics drivers\n";
407                                 if ( g_texture_globals.m_bOpenGLCompressionSupported ) {
408                                         g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_RGBA;
409                                         g_texture_globals.texture_components = GL_COMPRESSED_RGBA_ARB;
410                                 }
411                                 else
412                                 {
413                                         g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
414                                         g_texture_globals.texture_components = GL_RGBA;
415                                 }
416                         }
417                         break;
418                 default:
419                         globalOutputStream() << "Unknown texture compression selected, reverting\n";
420                         g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
421                         g_texture_globals.texture_components = GL_RGBA;
422                         break;
423                 }
424
425
426                 glGetIntegerv( GL_MAX_TEXTURE_SIZE, &max_tex_size );
427                 if ( max_tex_size == 0 ) {
428                         max_tex_size = 1024;
429                 }
430
431                 for ( qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i )
432                 {
433                         if ( !( *i ).value.empty() ) {
434                                 qtexture_realise( *( *i ).value, ( *i ).key );
435                         }
436                 }
437                 if ( m_observer != 0 ) {
438                         m_observer->realise();
439                 }
440         }
441 }
442 void unrealise(){
443         if ( ++m_unrealised == 1 ) {
444                 if ( m_observer != 0 ) {
445                         m_observer->unrealise();
446                 }
447                 for ( qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i )
448                 {
449                         if ( !( *i ).value.empty() ) {
450                                 qtexture_unrealise( *( *i ).value );
451                         }
452                 }
453         }
454 }
455 bool realised(){
456         return m_unrealised == 0;
457 }
458 };
459
460 TexturesMap* g_texturesmap;
461
462 TexturesCache& GetTexturesCache(){
463         return *g_texturesmap;
464 }
465
466
467 void Textures_Realise(){
468         g_texturesmap->realise();
469 }
470
471 void Textures_Unrealise(){
472         g_texturesmap->unrealise();
473 }
474
475
476 Callback g_texturesModeChangedNotify;
477
478 void Textures_setModeChangedNotify( const Callback& notify ){
479         g_texturesModeChangedNotify = notify;
480 }
481
482 void Textures_ModeChanged(){
483         if ( g_texturesmap->realised() ) {
484                 SetTexParameters( g_texture_mode );
485
486                 for ( TexturesMap::iterator i = g_texturesmap->begin(); i != g_texturesmap->end(); ++i )
487                 {
488                         glBindTexture( GL_TEXTURE_2D, ( *i ).value->texture_number );
489                         SetTexParameters( g_texture_mode );
490                 }
491
492                 glBindTexture( GL_TEXTURE_2D, 0 );
493         }
494         g_texturesModeChangedNotify();
495 }
496
497 void Textures_SetMode( ETexturesMode mode ){
498         if ( g_texture_mode != mode ) {
499                 g_texture_mode = mode;
500
501                 Textures_ModeChanged();
502         }
503 }
504
505 void Textures_setTextureComponents( GLint texture_components ){
506         if ( g_texture_globals.texture_components != texture_components ) {
507                 Textures_Unrealise();
508                 g_texture_globals.texture_components = texture_components;
509                 Textures_Realise();
510         }
511 }
512
513 void Textures_UpdateTextureCompressionFormat(){
514         GLint texture_components = GL_RGBA;
515
516         switch ( g_texture_globals.m_nTextureCompressionFormat )
517         {
518         case ( TEXTURECOMPRESSION_NONE ):
519         {
520                 texture_components = GL_RGBA;
521                 break;
522         }
523         case ( TEXTURECOMPRESSION_RGBA ):
524         {
525                 texture_components = GL_COMPRESSED_RGBA_ARB;
526                 break;
527         }
528         case ( TEXTURECOMPRESSION_RGBA_S3TC_DXT1 ):
529         {
530                 texture_components = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
531                 break;
532         }
533         case ( TEXTURECOMPRESSION_RGBA_S3TC_DXT3 ):
534         {
535                 texture_components = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
536                 break;
537         }
538         case ( TEXTURECOMPRESSION_RGBA_S3TC_DXT5 ):
539         {
540                 texture_components = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
541                 break;
542         }
543         }
544
545         Textures_setTextureComponents( texture_components );
546 }
547
548 void TextureCompressionImport( TextureCompressionFormat& self, int value ){
549         if ( !g_texture_globals.m_bOpenGLCompressionSupported
550                  && g_texture_globals.m_bS3CompressionSupported
551                  && value >= 1 ) {
552                 ++value;
553         }
554         switch ( value )
555         {
556         case 0:
557                 self = TEXTURECOMPRESSION_NONE;
558                 break;
559         case 1:
560                 self = TEXTURECOMPRESSION_RGBA;
561                 break;
562         case 2:
563                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT1;
564                 break;
565         case 3:
566                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT3;
567                 break;
568         case 4:
569                 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT5;
570                 break;
571         }
572         Textures_UpdateTextureCompressionFormat();
573 }
574 typedef ReferenceCaller1<TextureCompressionFormat, int, TextureCompressionImport> TextureCompressionImportCaller;
575
576 void TextureGammaImport( float& self, float value ){
577         if ( self != value ) {
578                 Textures_Unrealise();
579                 self = value;
580                 Textures_Realise();
581         }
582 }
583 typedef ReferenceCaller1<float, float, TextureGammaImport> TextureGammaImportCaller;
584
585 void TextureModeImport( ETexturesMode& self, int value ){
586         switch ( value )
587         {
588         case 0:
589                 Textures_SetMode( eTextures_NEAREST );
590                 break;
591         case 1:
592                 Textures_SetMode( eTextures_NEAREST_MIPMAP_NEAREST );
593                 break;
594         case 2:
595                 Textures_SetMode( eTextures_LINEAR );
596                 break;
597         case 3:
598                 Textures_SetMode( eTextures_NEAREST_MIPMAP_LINEAR );
599                 break;
600         case 4:
601                 Textures_SetMode( eTextures_LINEAR_MIPMAP_NEAREST );
602                 break;
603         case 5:
604                 Textures_SetMode( eTextures_LINEAR_MIPMAP_LINEAR );
605                 break;
606         case 6:
607                 Textures_SetMode( eTextures_MAX_ANISOTROPY );
608         }
609 }
610 typedef ReferenceCaller1<ETexturesMode, int, TextureModeImport> TextureModeImportCaller;
611
612 void TextureModeExport( ETexturesMode& self, const IntImportCallback& importer ){
613         switch ( self )
614         {
615         case eTextures_NEAREST:
616                 importer( 0 );
617                 break;
618         case eTextures_NEAREST_MIPMAP_NEAREST:
619                 importer( 1 );
620                 break;
621         case eTextures_LINEAR:
622                 importer( 2 );
623                 break;
624         case eTextures_NEAREST_MIPMAP_LINEAR:
625                 importer( 3 );
626                 break;
627         case eTextures_LINEAR_MIPMAP_NEAREST:
628                 importer( 4 );
629                 break;
630         case eTextures_LINEAR_MIPMAP_LINEAR:
631                 importer( 5 );
632                 break;
633         case eTextures_MAX_ANISOTROPY:
634                 importer( 6 );
635                 break;
636         default:
637                 importer( 4 );
638         }
639 }
640 typedef ReferenceCaller1<ETexturesMode, const IntImportCallback&, TextureModeExport> TextureModeExportCaller;
641
642 void Textures_constructPreferences( PreferencesPage& page ){
643         {
644                 const char* percentages[] = { "12.5%", "25%", "50%", "100%", };
645                 page.appendRadio(
646                         "Texture Quality",
647                         STRING_ARRAY_RANGE( percentages ),
648                         LatchedIntImportCaller( g_Textures_textureQuality ),
649                         IntExportCaller( g_Textures_textureQuality.m_latched )
650                         );
651         }
652         page.appendSpinner(
653                 "Texture Gamma",
654                 1.0,
655                 0.0,
656                 1.0,
657                 FloatImportCallback( TextureGammaImportCaller( g_texture_globals.fGamma ) ),
658                 FloatExportCallback( FloatExportCaller( g_texture_globals.fGamma ) )
659                 );
660         {
661                 const char* texture_mode[] = { "Nearest", "Nearest Mipmap", "Linear", "Bilinear", "Bilinear Mipmap", "Trilinear", "Anisotropy" };
662                 page.appendCombo(
663                         "Texture Render Mode",
664                         STRING_ARRAY_RANGE( texture_mode ),
665                         IntImportCallback( TextureModeImportCaller( g_texture_mode ) ),
666                         IntExportCallback( TextureModeExportCaller( g_texture_mode ) )
667                         );
668         }
669         {
670                 const char* compression_none[] = { "None" };
671                 const char* compression_opengl[] = { "None", "OpenGL ARB" };
672                 const char* compression_s3tc[] = { "None", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5" };
673                 const char* compression_opengl_s3tc[] = { "None", "OpenGL ARB", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5" };
674                 StringArrayRange compression(
675                         ( g_texture_globals.m_bOpenGLCompressionSupported )
676                         ? ( g_texture_globals.m_bS3CompressionSupported )
677                         ? STRING_ARRAY_RANGE( compression_opengl_s3tc )
678                         : STRING_ARRAY_RANGE( compression_opengl )
679                         : ( g_texture_globals.m_bS3CompressionSupported )
680                         ? STRING_ARRAY_RANGE( compression_s3tc )
681                         : STRING_ARRAY_RANGE( compression_none )
682                         );
683                 page.appendCombo(
684                         "Hardware Texture Compression",
685                         compression,
686                         TextureCompressionImportCaller( g_texture_globals.m_nTextureCompressionFormat ),
687                         IntExportCaller( reinterpret_cast<int&>( g_texture_globals.m_nTextureCompressionFormat ) )
688                         );
689         }
690 }
691 void Textures_constructPage( PreferenceGroup& group ){
692         PreferencesPage page( group.createPage( "Textures", "Texture Settings" ) );
693         Textures_constructPreferences( page );
694 }
695 void Textures_registerPreferencesPage(){
696         PreferencesDialog_addDisplayPage( FreeCaller1<PreferenceGroup&, Textures_constructPage>() );
697 }
698
699 void TextureCompression_importString( const char* string ){
700         g_texture_globals.m_nTextureCompressionFormat = static_cast<TextureCompressionFormat>( atoi( string ) );
701         Textures_UpdateTextureCompressionFormat();
702 }
703 typedef FreeCaller1<const char*, TextureCompression_importString> TextureCompressionImportStringCaller;
704
705
706 void Textures_Construct(){
707         g_texturesmap = new TexturesMap;
708
709         GlobalPreferenceSystem().registerPreference( "TextureCompressionFormat", TextureCompressionImportStringCaller(), IntExportStringCaller( reinterpret_cast<int&>( g_texture_globals.m_nTextureCompressionFormat ) ) );
710         GlobalPreferenceSystem().registerPreference( "TextureFiltering", IntImportStringCaller( reinterpret_cast<int&>( g_texture_mode ) ), IntExportStringCaller( reinterpret_cast<int&>( g_texture_mode ) ) );
711         GlobalPreferenceSystem().registerPreference( "TextureQuality", IntImportStringCaller( g_Textures_textureQuality.m_latched ), IntExportStringCaller( g_Textures_textureQuality.m_latched ) );
712         GlobalPreferenceSystem().registerPreference( "SI_Gamma", FloatImportStringCaller( g_texture_globals.fGamma ), FloatExportStringCaller( g_texture_globals.fGamma ) );
713
714         g_Textures_textureQuality.useLatched();
715
716         Textures_registerPreferencesPage();
717
718         Textures_ModeChanged();
719 }
720 void Textures_Destroy(){
721         delete g_texturesmap;
722 }
723
724
725 #include "modulesystem/modulesmap.h"
726 #include "modulesystem/singletonmodule.h"
727 #include "modulesystem/moduleregistry.h"
728
729 class TexturesDependencies :
730         public GlobalRadiantModuleRef,
731         public GlobalOpenGLModuleRef,
732         public GlobalPreferenceSystemModuleRef
733 {
734 ImageModulesRef m_image_modules;
735 public:
736 TexturesDependencies() :
737         m_image_modules( GlobalRadiant().getRequiredGameDescriptionKeyValue( "texturetypes" ) ){
738 }
739 ImageModules& getImageModules(){
740         return m_image_modules.get();
741 }
742 };
743
744 class TexturesAPI
745 {
746 TexturesCache* m_textures;
747 public:
748 typedef TexturesCache Type;
749 STRING_CONSTANT( Name, "*" );
750
751 TexturesAPI(){
752         Textures_Construct();
753
754         m_textures = &GetTexturesCache();
755 }
756 ~TexturesAPI(){
757         Textures_Destroy();
758 }
759 TexturesCache* getTable(){
760         return m_textures;
761 }
762 };
763
764 typedef SingletonModule<TexturesAPI, TexturesDependencies> TexturesModule;
765 typedef Static<TexturesModule> StaticTexturesModule;
766 StaticRegisterModule staticRegisterTextures( StaticTexturesModule::instance() );
767
768 ImageModules& Textures_getImageModules(){
769         return StaticTexturesModule::instance().getDependencies().getImageModules();
770 }