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