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