2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
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.
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.
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
24 #include "debugging/debugging.h"
27 #include "itextures.h"
29 #include "preferencesystem.h"
32 #include "texturelib.h"
33 #include "container/hashfunc.h"
34 #include "container/cache.h"
35 #include "generic/callback.h"
40 #include "preferences.h"
44 eTextures_NEAREST = 0,
45 eTextures_NEAREST_MIPMAP_NEAREST = 1,
46 eTextures_NEAREST_MIPMAP_LINEAR = 2,
48 eTextures_LINEAR_MIPMAP_NEAREST = 4,
49 eTextures_LINEAR_MIPMAP_LINEAR = 5,
50 eTextures_MAX_ANISOTROPY = 6,
53 enum TextureCompressionFormat {
54 TEXTURECOMPRESSION_NONE = 0,
55 TEXTURECOMPRESSION_RGBA = 1,
56 TEXTURECOMPRESSION_RGBA_S3TC_DXT1 = 2,
57 TEXTURECOMPRESSION_RGBA_S3TC_DXT3 = 3,
58 TEXTURECOMPRESSION_RGBA_S3TC_DXT5 = 4,
61 struct texture_globals_t {
63 // texture compression format
64 TextureCompressionFormat m_nTextureCompressionFormat;
68 bool bTextureCompressionSupported; // is texture compression supported by hardware?
69 GLint texture_components;
71 // temporary values that should be initialised only once at run-time
72 bool m_bOpenGLCompressionSupported;
73 bool m_bS3CompressionSupported;
75 texture_globals_t(GLint components) :
76 m_nTextureCompressionFormat(TEXTURECOMPRESSION_NONE),
78 bTextureCompressionSupported(false),
79 texture_components(components),
80 m_bOpenGLCompressionSupported(false),
81 m_bS3CompressionSupported(false)
86 texture_globals_t g_texture_globals(GL_RGBA);
88 void SetTexParameters(ETexturesMode mode)
90 float maxAniso = QGL_maxTextureAnisotropy();
92 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f);
93 } else if (mode == eTextures_MAX_ANISOTROPY) {
94 mode = eTextures_LINEAR_MIPMAP_LINEAR;
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);
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);
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);
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);
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);
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);
122 case eTextures_MAX_ANISOTROPY:
123 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAniso);
126 globalOutputStream() << "invalid texture mode\n";
130 ETexturesMode g_texture_mode = eTextures_LINEAR_MIPMAP_LINEAR;
133 byte g_gammatable[256];
135 void ResampleGamma(float fGamma)
139 for (i = 0; i < 256; i++) {
143 for (i = 0; i < 256; i++) {
144 inf = (int) (255 * pow(static_cast<double>((i + 0.5) / 255.5 ), static_cast<double>( fGamma )) + 0.5);
151 g_gammatable[i] = inf;
156 inline const int &min_int(const int &left, const int &right)
158 return std::min(left, right);
161 int max_tex_size = 0;
162 const int max_texture_quality = 3;
163 LatchedValue<int> g_Textures_textureQuality(3, "Texture Quality");
165 /// \brief This function does the actual processing of raw RGBA data into a GL texture.
166 /// It will also resample to power-of-two dimensions, generate the mipmaps and adjust gamma.
167 void LoadTextureRGBA(qtexture_t *q, unsigned char *pPixels, int nWidth, int nHeight)
169 static float fGamma = -1;
172 int nCount = nWidth * nHeight;
174 if (fGamma != g_texture_globals.fGamma) {
175 fGamma = g_texture_globals.fGamma;
176 ResampleGamma(fGamma);
182 total[0] = total[1] = total[2] = 0.0f;
184 // resample texture gamma according to user settings
185 for (int i = 0; i < (nCount * 4); i += 4) {
186 for (int j = 0; j < 3; j++) {
187 total[j] += (pPixels + i)[j];
188 byte b = (pPixels + i)[j];
189 (pPixels + i)[j] = g_gammatable[b];
193 q->color[0] = total[0] / (nCount * 255);
194 q->color[1] = total[1] / (nCount * 255);
195 q->color[2] = total[2] / (nCount * 255);
197 glGenTextures(1, &q->texture_number);
199 glBindTexture(GL_TEXTURE_2D, q->texture_number);
201 SetTexParameters(g_texture_mode);
204 while (gl_width < nWidth) {
209 while (gl_height < nHeight) {
213 bool resampled = false;
214 if (!(gl_width == nWidth && gl_height == nHeight)) {
216 outpixels = (byte *) malloc(gl_width * gl_height * 4);
217 R_ResampleTexture(pPixels, nWidth, nHeight, outpixels, gl_width, gl_height, 4);
222 int quality_reduction = max_texture_quality - g_Textures_textureQuality.m_value;
223 int target_width = min_int(gl_width >> quality_reduction, max_tex_size);
224 int target_height = min_int(gl_height >> quality_reduction, max_tex_size);
226 while (gl_width > target_width || gl_height > target_height) {
227 GL_MipReduce(outpixels, outpixels, gl_width, gl_height, target_width, target_height);
229 if (gl_width > target_width) {
232 if (gl_height > target_height) {
238 glTexImage2D(GL_TEXTURE_2D, mip++, g_texture_globals.texture_components, gl_width, gl_height, 0, GL_RGBA,
239 GL_UNSIGNED_BYTE, outpixels);
240 while (gl_width > 1 || gl_height > 1) {
241 GL_MipReduce(outpixels, outpixels, gl_width, gl_height, 1, 1);
250 glTexImage2D(GL_TEXTURE_2D, mip++, g_texture_globals.texture_components, gl_width, gl_height, 0, GL_RGBA,
251 GL_UNSIGNED_BYTE, outpixels);
254 glBindTexture(GL_TEXTURE_2D, 0);
266 void Texture_InitPalette( byte *pal ){
270 byte gammatable[256];
273 gamma = g_texture_globals.fGamma;
275 if ( gamma == 1.0 ) {
276 for ( i = 0 ; i < 256 ; i++ )
281 for ( i = 0 ; i < 256 ; i++ )
283 inf = (int)( 255 * pow( ( i + 0.5 ) / 255.5, gamma ) + 0.5 );
294 for ( i = 0 ; i < 256 ; i++ )
296 r = gammatable[pal[0]];
297 g = gammatable[pal[1]];
298 b = gammatable[pal[2]];
301 //v = (r<<24) + (g<<16) + (b<<8) + 255;
304 //tex_palette[i] = v;
305 tex_palette[i * 3 + 0] = r;
306 tex_palette[i * 3 + 1] = g;
307 tex_palette[i * 3 + 2] = b;
317 HashTable<CopiedString, CopiedString, HashStringNoCase, StringEqualNoCase> strings;
318 strings["Monkey"] = "bleh";
319 strings["MonkeY"] = "blah";
323 const TestHashtable g_testhashtable;
327 typedef std::pair<LoadImageCallback, CopiedString> TextureKey;
329 void qtexture_realise(qtexture_t &texture, const TextureKey &key)
331 texture.texture_number = 0;
332 if (!string_empty(key.second.c_str())) {
333 Image *image = key.first.loadImage(key.second.c_str());
335 LoadTextureRGBA(&texture, image->getRGBAPixels(), image->getWidth(), image->getHeight());
336 texture.surfaceFlags = image->getSurfaceFlags();
337 texture.contentFlags = image->getContentFlags();
338 texture.value = image->getValue();
340 globalOutputStream() << "Loaded Texture: \"" << key.second.c_str() << "\"\n";
341 GlobalOpenGL_debugAssertNoErrors();
343 globalErrorStream() << "Texture load failed: \"" << key.second.c_str() << "\"\n";
348 void qtexture_unrealise(qtexture_t &texture)
350 if (GlobalOpenGL().contextValid && texture.texture_number != 0) {
351 glDeleteTextures(1, &texture.texture_number);
352 GlobalOpenGL_debugAssertNoErrors();
356 class TextureKeyEqualNoCase {
358 bool operator()(const TextureKey &key, const TextureKey &other) const
360 return key.first == other.first && string_equal_nocase(key.second.c_str(), other.second.c_str());
364 class TextureKeyHashNoCase {
366 typedef hash_t hash_type;
368 hash_t operator()(const TextureKey &key) const
370 return hash_combine(string_hash_nocase(key.second.c_str()), pod_hash(key.first));
374 #define DEBUG_TEXTURES 0
376 class TexturesMap : public TexturesCache {
377 class TextureConstructor {
378 TexturesMap *m_cache;
380 explicit TextureConstructor(TexturesMap *cache)
385 qtexture_t *construct(const TextureKey &key)
387 qtexture_t *texture = new qtexture_t(key.first, key.second.c_str());
388 if (m_cache->realised()) {
389 qtexture_realise(*texture, key);
394 void destroy(qtexture_t *texture)
396 if (m_cache->realised()) {
397 qtexture_unrealise(*texture);
403 typedef HashedCache<TextureKey, qtexture_t, TextureKeyHashNoCase, TextureKeyEqualNoCase, TextureConstructor> qtextures_t;
404 qtextures_t m_qtextures;
405 TexturesCacheObserver *m_observer;
406 std::size_t m_unrealised;
409 virtual ~TexturesMap() = default;
411 TexturesMap() : m_qtextures(TextureConstructor(this)), m_observer(0), m_unrealised(1)
415 typedef qtextures_t::iterator iterator;
419 return m_qtextures.begin();
424 return m_qtextures.end();
427 LoadImageCallback defaultLoader() const
429 return LoadImageCallback(0, QERApp_LoadImage);
432 Image *loadImage(const char *name)
434 return defaultLoader().loadImage(name);
437 qtexture_t *capture(const char *name)
439 return capture(defaultLoader(), name);
442 qtexture_t *capture(const LoadImageCallback &loader, const char *name)
445 globalOutputStream() << "textures capture: " << makeQuoted( name ) << '\n';
447 return m_qtextures.capture(TextureKey(loader, name)).get();
450 void release(qtexture_t *texture)
453 globalOutputStream() << "textures release: " << makeQuoted( texture->name ) << '\n';
455 m_qtextures.release(TextureKey(texture->load, texture->name));
458 void attach(TexturesCacheObserver &observer)
460 ASSERT_MESSAGE(m_observer == 0, "TexturesMap::attach: cannot attach observer");
461 m_observer = &observer;
464 void detach(TexturesCacheObserver &observer)
466 ASSERT_MESSAGE(m_observer == &observer, "TexturesMap::detach: cannot detach observer");
472 if (--m_unrealised == 0) {
473 g_texture_globals.bTextureCompressionSupported = false;
475 if (GlobalOpenGL().ARB_texture_compression()) {
476 g_texture_globals.bTextureCompressionSupported = true;
477 g_texture_globals.m_bOpenGLCompressionSupported = true;
480 if (GlobalOpenGL().EXT_texture_compression_s3tc()) {
481 g_texture_globals.bTextureCompressionSupported = true;
482 g_texture_globals.m_bS3CompressionSupported = true;
485 switch (g_texture_globals.texture_components) {
488 case GL_COMPRESSED_RGBA_ARB:
489 if (!g_texture_globals.m_bOpenGLCompressionSupported) {
491 << "OpenGL extension GL_ARB_texture_compression not supported by current graphics drivers\n";
492 g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
493 g_texture_globals.texture_components = GL_RGBA;
496 case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
497 case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
498 case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
499 if (!g_texture_globals.m_bS3CompressionSupported) {
501 << "OpenGL extension GL_EXT_texture_compression_s3tc not supported by current graphics drivers\n";
502 if (g_texture_globals.m_bOpenGLCompressionSupported) {
503 g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_RGBA;
504 g_texture_globals.texture_components = GL_COMPRESSED_RGBA_ARB;
506 g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
507 g_texture_globals.texture_components = GL_RGBA;
512 globalOutputStream() << "Unknown texture compression selected, reverting\n";
513 g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE;
514 g_texture_globals.texture_components = GL_RGBA;
519 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size);
520 if (max_tex_size == 0) {
524 for (qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i) {
525 if (!(*i).value.empty()) {
526 qtexture_realise(*(*i).value, (*i).key);
529 if (m_observer != 0) {
530 m_observer->realise();
537 if (++m_unrealised == 1) {
538 if (m_observer != 0) {
539 m_observer->unrealise();
541 for (qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i) {
542 if (!(*i).value.empty()) {
543 qtexture_unrealise(*(*i).value);
551 return m_unrealised == 0;
555 TexturesMap *g_texturesmap;
557 TexturesCache &GetTexturesCache()
559 return *g_texturesmap;
563 void Textures_Realise()
565 g_texturesmap->realise();
568 void Textures_Unrealise()
570 g_texturesmap->unrealise();
574 Callback<void()> g_texturesModeChangedNotify;
576 void Textures_setModeChangedNotify(const Callback<void()> ¬ify)
578 g_texturesModeChangedNotify = notify;
581 void Textures_ModeChanged()
583 if (g_texturesmap->realised()) {
584 SetTexParameters(g_texture_mode);
586 for (TexturesMap::iterator i = g_texturesmap->begin(); i != g_texturesmap->end(); ++i) {
587 glBindTexture(GL_TEXTURE_2D, (*i).value->texture_number);
588 SetTexParameters(g_texture_mode);
591 glBindTexture(GL_TEXTURE_2D, 0);
593 g_texturesModeChangedNotify();
596 void Textures_SetMode(ETexturesMode mode)
598 if (g_texture_mode != mode) {
599 g_texture_mode = mode;
601 Textures_ModeChanged();
605 void Textures_setTextureComponents(GLint texture_components)
607 if (g_texture_globals.texture_components != texture_components) {
608 Textures_Unrealise();
609 g_texture_globals.texture_components = texture_components;
614 void Textures_UpdateTextureCompressionFormat()
616 GLint texture_components = GL_RGBA;
618 switch (g_texture_globals.m_nTextureCompressionFormat) {
619 case (TEXTURECOMPRESSION_NONE): {
620 texture_components = GL_RGBA;
623 case (TEXTURECOMPRESSION_RGBA): {
624 texture_components = GL_COMPRESSED_RGBA_ARB;
627 case (TEXTURECOMPRESSION_RGBA_S3TC_DXT1): {
628 texture_components = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
631 case (TEXTURECOMPRESSION_RGBA_S3TC_DXT3): {
632 texture_components = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
635 case (TEXTURECOMPRESSION_RGBA_S3TC_DXT5): {
636 texture_components = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
641 Textures_setTextureComponents(texture_components);
644 struct TextureCompression {
645 static void Export(const TextureCompressionFormat &self, const Callback<void(int)> &returnz)
650 static void Import(TextureCompressionFormat &self, int value)
652 if (!g_texture_globals.m_bOpenGLCompressionSupported
653 && g_texture_globals.m_bS3CompressionSupported
659 self = TEXTURECOMPRESSION_NONE;
662 self = TEXTURECOMPRESSION_RGBA;
665 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT1;
668 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT3;
671 self = TEXTURECOMPRESSION_RGBA_S3TC_DXT5;
674 Textures_UpdateTextureCompressionFormat();
678 struct TextureGamma {
679 static void Export(const float &self, const Callback<void(float)> &returnz)
684 static void Import(float &self, float value)
687 Textures_Unrealise();
695 static void Export(const ETexturesMode &self, const Callback<void(int)> &returnz)
698 case eTextures_NEAREST:
701 case eTextures_NEAREST_MIPMAP_NEAREST:
704 case eTextures_LINEAR:
707 case eTextures_NEAREST_MIPMAP_LINEAR:
710 case eTextures_LINEAR_MIPMAP_NEAREST:
713 case eTextures_LINEAR_MIPMAP_LINEAR:
716 case eTextures_MAX_ANISOTROPY:
724 static void Import(ETexturesMode &self, int value)
728 Textures_SetMode(eTextures_NEAREST);
731 Textures_SetMode(eTextures_NEAREST_MIPMAP_NEAREST);
734 Textures_SetMode(eTextures_LINEAR);
737 Textures_SetMode(eTextures_NEAREST_MIPMAP_LINEAR);
740 Textures_SetMode(eTextures_LINEAR_MIPMAP_NEAREST);
743 Textures_SetMode(eTextures_LINEAR_MIPMAP_LINEAR);
746 Textures_SetMode(eTextures_MAX_ANISOTROPY);
751 void Textures_constructPreferences(PreferencesPage &page)
754 const char *percentages[] = {"12.5%", "25%", "50%", "100%",};
757 STRING_ARRAY_RANGE(percentages),
758 make_property(g_Textures_textureQuality)
766 make_property<TextureGamma>(g_texture_globals.fGamma)
769 const char *texture_mode[] = {"Nearest", "Nearest Mipmap", "Linear", "Bilinear", "Bilinear Mipmap", "Trilinear",
772 "Texture Render Mode",
773 STRING_ARRAY_RANGE(texture_mode),
774 make_property<TextureMode>(g_texture_mode)
778 const char *compression_none[] = {"None"};
779 const char *compression_opengl[] = {"None", "OpenGL ARB"};
780 const char *compression_s3tc[] = {"None", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5"};
781 const char *compression_opengl_s3tc[] = {"None", "OpenGL ARB", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5"};
782 StringArrayRange compression(
783 (g_texture_globals.m_bOpenGLCompressionSupported)
784 ? (g_texture_globals.m_bS3CompressionSupported)
785 ? STRING_ARRAY_RANGE(compression_opengl_s3tc)
786 : STRING_ARRAY_RANGE(compression_opengl)
787 : (g_texture_globals.m_bS3CompressionSupported)
788 ? STRING_ARRAY_RANGE(compression_s3tc)
789 : STRING_ARRAY_RANGE(compression_none)
792 "Hardware Texture Compression",
794 make_property<TextureCompression>(g_texture_globals.m_nTextureCompressionFormat)
799 void Textures_constructPage(PreferenceGroup &group)
801 PreferencesPage page(group.createPage("Textures", "Texture Settings"));
802 Textures_constructPreferences(page);
805 void Textures_registerPreferencesPage()
807 PreferencesDialog_addDisplayPage(makeCallbackF(Textures_constructPage));
810 struct TextureCompressionPreference {
811 static void Export(const Callback<void(int)> &returnz)
813 returnz(g_texture_globals.m_nTextureCompressionFormat);
816 static void Import(int value)
818 g_texture_globals.m_nTextureCompressionFormat = static_cast<TextureCompressionFormat>( value );
819 Textures_UpdateTextureCompressionFormat();
823 void Textures_Construct()
825 g_texturesmap = new TexturesMap;
827 GlobalPreferenceSystem().registerPreference("TextureCompressionFormat",
828 make_property_string<TextureCompressionPreference>());
829 GlobalPreferenceSystem().registerPreference("TextureFiltering",
830 make_property_string(reinterpret_cast<int &>( g_texture_mode )));
831 GlobalPreferenceSystem().registerPreference("TextureQuality",
832 make_property_string(g_Textures_textureQuality.m_latched));
833 GlobalPreferenceSystem().registerPreference("SI_Gamma", make_property_string(g_texture_globals.fGamma));
835 g_Textures_textureQuality.useLatched();
837 Textures_registerPreferencesPage();
839 Textures_ModeChanged();
842 void Textures_Destroy()
844 delete g_texturesmap;
848 #include "modulesystem/modulesmap.h"
849 #include "modulesystem/singletonmodule.h"
850 #include "modulesystem/moduleregistry.h"
852 class TexturesDependencies :
853 public GlobalRadiantModuleRef,
854 public GlobalOpenGLModuleRef,
855 public GlobalPreferenceSystemModuleRef {
856 ImageModulesRef m_image_modules;
858 TexturesDependencies() :
859 m_image_modules(GlobalRadiant().getRequiredGameDescriptionKeyValue("texturetypes"))
863 ImageModules &getImageModules()
865 return m_image_modules.get();
870 TexturesCache *m_textures;
872 typedef TexturesCache Type;
874 STRING_CONSTANT(Name, "*");
878 Textures_Construct();
880 m_textures = &GetTexturesCache();
888 TexturesCache *getTable()
894 typedef SingletonModule<TexturesAPI, TexturesDependencies> TexturesModule;
895 typedef Static<TexturesModule> StaticTexturesModule;
896 StaticRegisterModule staticRegisterTextures(StaticTexturesModule::instance());
898 ImageModules &Textures_getImageModules()
900 return StaticTexturesModule::instance().getDependencies().getImageModules();