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