/* Copyright (C) 1999-2006 Id Software, Inc. and contributors. For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "textures.h" #include "debugging/debugging.h" #include "warnings.h" #include "itextures.h" #include "igl.h" #include "preferencesystem.h" #include "qgl.h" #include "texturelib.h" #include "container/hashfunc.h" #include "container/cache.h" #include "generic/callback.h" #include "stringio.h" #include "image.h" #include "texmanip.h" #include "preferences.h" enum ETexturesMode { eTextures_NEAREST = 0, eTextures_NEAREST_MIPMAP_NEAREST = 1, eTextures_NEAREST_MIPMAP_LINEAR = 2, eTextures_LINEAR = 3, eTextures_LINEAR_MIPMAP_NEAREST = 4, eTextures_LINEAR_MIPMAP_LINEAR = 5, eTextures_MAX_ANISOTROPY = 6, }; enum TextureCompressionFormat { TEXTURECOMPRESSION_NONE = 0, TEXTURECOMPRESSION_RGBA = 1, TEXTURECOMPRESSION_RGBA_S3TC_DXT1 = 2, TEXTURECOMPRESSION_RGBA_S3TC_DXT3 = 3, TEXTURECOMPRESSION_RGBA_S3TC_DXT5 = 4, }; struct texture_globals_t { // RIANT // texture compression format TextureCompressionFormat m_nTextureCompressionFormat; float fGamma; bool bTextureCompressionSupported; // is texture compression supported by hardware? GLint texture_components; // temporary values that should be initialised only once at run-time bool m_bOpenGLCompressionSupported; bool m_bS3CompressionSupported; texture_globals_t( GLint components ) : m_nTextureCompressionFormat( TEXTURECOMPRESSION_NONE ), fGamma( 1.0f ), bTextureCompressionSupported( false ), texture_components( components ), m_bOpenGLCompressionSupported( false ), m_bS3CompressionSupported( false ){ } }; texture_globals_t g_texture_globals( GL_RGBA ); void SetTexParameters( ETexturesMode mode ){ float maxAniso = QGL_maxTextureAnisotropy(); if ( maxAniso > 1 ) { glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f ); } else if ( mode == eTextures_MAX_ANISOTROPY ) { mode = eTextures_LINEAR_MIPMAP_LINEAR; } switch ( mode ) { case eTextures_NEAREST: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); break; case eTextures_NEAREST_MIPMAP_NEAREST: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); break; case eTextures_NEAREST_MIPMAP_LINEAR: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); break; case eTextures_LINEAR: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); break; case eTextures_LINEAR_MIPMAP_NEAREST: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); break; case eTextures_LINEAR_MIPMAP_LINEAR: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); break; case eTextures_MAX_ANISOTROPY: glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAniso ); break; default: globalOutputStream() << "invalid texture mode\n"; } } ETexturesMode g_texture_mode = eTextures_LINEAR_MIPMAP_LINEAR; byte g_gammatable[256]; void ResampleGamma( float fGamma ){ int i,inf; if ( fGamma == 1.0 ) { for ( i = 0; i < 256; i++ ) g_gammatable[i] = i; } else { for ( i = 0; i < 256; i++ ) { inf = (int)( 255 * pow( static_cast( ( i + 0.5 ) / 255.5 ), static_cast( fGamma ) ) + 0.5 ); if ( inf < 0 ) { inf = 0; } if ( inf > 255 ) { inf = 255; } g_gammatable[i] = inf; } } } inline const int& min_int( const int& left, const int& right ){ return std::min( left, right ); } int max_tex_size = 0; const int max_texture_quality = 3; LatchedInt g_Textures_textureQuality( 3, "Texture Quality" ); /// \brief This function does the actual processing of raw RGBA data into a GL texture. /// It will also resample to power-of-two dimensions, generate the mipmaps and adjust gamma. void LoadTextureRGBA( qtexture_t* q, unsigned char* pPixels, int nWidth, int nHeight ){ static float fGamma = -1; float total[3]; byte *outpixels = 0; int nCount = nWidth * nHeight; if ( fGamma != g_texture_globals.fGamma ) { fGamma = g_texture_globals.fGamma; ResampleGamma( fGamma ); } q->width = nWidth; q->height = nHeight; total[0] = total[1] = total[2] = 0.0f; // resample texture gamma according to user settings for ( int i = 0; i < ( nCount * 4 ); i += 4 ) { for ( int j = 0; j < 3; j++ ) { total[j] += ( pPixels + i )[j]; byte b = ( pPixels + i )[j]; ( pPixels + i )[j] = g_gammatable[b]; } } q->color[0] = total[0] / ( nCount * 255 ); q->color[1] = total[1] / ( nCount * 255 ); q->color[2] = total[2] / ( nCount * 255 ); glGenTextures( 1, &q->texture_number ); glBindTexture( GL_TEXTURE_2D, q->texture_number ); SetTexParameters( g_texture_mode ); int gl_width = 1; while ( gl_width < nWidth ) gl_width <<= 1; int gl_height = 1; while ( gl_height < nHeight ) gl_height <<= 1; bool resampled = false; if ( !( gl_width == nWidth && gl_height == nHeight ) ) { resampled = true; outpixels = (byte *)malloc( gl_width * gl_height * 4 ); R_ResampleTexture( pPixels, nWidth, nHeight, outpixels, gl_width, gl_height, 4 ); } else { outpixels = pPixels; } int quality_reduction = max_texture_quality - g_Textures_textureQuality.m_value; int target_width = min_int( gl_width >> quality_reduction, max_tex_size ); int target_height = min_int( gl_height >> quality_reduction, max_tex_size ); while ( gl_width > target_width || gl_height > target_height ) { GL_MipReduce( outpixels, outpixels, gl_width, gl_height, target_width, target_height ); if ( gl_width > target_width ) { gl_width >>= 1; } if ( gl_height > target_height ) { gl_height >>= 1; } } int mip = 0; glTexImage2D( GL_TEXTURE_2D, mip++, g_texture_globals.texture_components, gl_width, gl_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, outpixels ); while ( gl_width > 1 || gl_height > 1 ) { GL_MipReduce( outpixels, outpixels, gl_width, gl_height, 1, 1 ); if ( gl_width > 1 ) { gl_width >>= 1; } if ( gl_height > 1 ) { gl_height >>= 1; } glTexImage2D( GL_TEXTURE_2D, mip++, g_texture_globals.texture_components, gl_width, gl_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, outpixels ); } glBindTexture( GL_TEXTURE_2D, 0 ); if ( resampled ) { free( outpixels ); } } #if 0 /* ============== Texture_InitPalette ============== */ void Texture_InitPalette( byte *pal ){ int r,g,b; int i; int inf; byte gammatable[256]; float gamma; gamma = g_texture_globals.fGamma; if ( gamma == 1.0 ) { for ( i = 0 ; i < 256 ; i++ ) gammatable[i] = i; } else { for ( i = 0 ; i < 256 ; i++ ) { inf = (int)( 255 * pow( ( i + 0.5 ) / 255.5, gamma ) + 0.5 ); if ( inf < 0 ) { inf = 0; } if ( inf > 255 ) { inf = 255; } gammatable[i] = inf; } } for ( i = 0 ; i < 256 ; i++ ) { r = gammatable[pal[0]]; g = gammatable[pal[1]]; b = gammatable[pal[2]]; pal += 3; //v = (r<<24) + (g<<16) + (b<<8) + 255; //v = BigLong (v); //tex_palette[i] = v; tex_palette[i * 3 + 0] = r; tex_palette[i * 3 + 1] = g; tex_palette[i * 3 + 2] = b; } } #endif #if 0 class TestHashtable { public: TestHashtable(){ HashTable strings; strings["Monkey"] = "bleh"; strings["MonkeY"] = "blah"; } }; const TestHashtable g_testhashtable; #endif typedef std::pair TextureKey; void qtexture_realise( qtexture_t& texture, const TextureKey& key ){ texture.texture_number = 0; if ( !string_empty( key.second.c_str() ) ) { Image* image = key.first.loadImage( key.second.c_str() ); if ( image != 0 ) { LoadTextureRGBA( &texture, image->getRGBAPixels(), image->getWidth(), image->getHeight() ); texture.surfaceFlags = image->getSurfaceFlags(); texture.contentFlags = image->getContentFlags(); texture.value = image->getValue(); image->release(); globalOutputStream() << "Loaded Texture: \"" << key.second.c_str() << "\"\n"; GlobalOpenGL_debugAssertNoErrors(); } else { globalErrorStream() << "Texture load failed: \"" << key.second.c_str() << "\"\n"; } } } void qtexture_unrealise( qtexture_t& texture ){ if ( GlobalOpenGL().contextValid && texture.texture_number != 0 ) { glDeleteTextures( 1, &texture.texture_number ); GlobalOpenGL_debugAssertNoErrors(); } } class TextureKeyEqualNoCase { public: bool operator()( const TextureKey& key, const TextureKey& other ) const { return key.first == other.first && string_equal_nocase( key.second.c_str(), other.second.c_str() ); } }; class TextureKeyHashNoCase { public: typedef hash_t hash_type; hash_t operator()( const TextureKey& key ) const { return hash_combine( string_hash_nocase( key.second.c_str() ), pod_hash( key.first ) ); } }; #define DEBUG_TEXTURES 0 class TexturesMap : public TexturesCache { class TextureConstructor { TexturesMap* m_cache; public: explicit TextureConstructor( TexturesMap* cache ) : m_cache( cache ){ } qtexture_t* construct( const TextureKey& key ){ qtexture_t* texture = new qtexture_t( key.first, key.second.c_str() ); if ( m_cache->realised() ) { qtexture_realise( *texture, key ); } return texture; } void destroy( qtexture_t* texture ){ if ( m_cache->realised() ) { qtexture_unrealise( *texture ); } delete texture; } }; typedef HashedCache qtextures_t; qtextures_t m_qtextures; TexturesCacheObserver* m_observer; std::size_t m_unrealised; public: TexturesMap() : m_qtextures( TextureConstructor( this ) ), m_observer( 0 ), m_unrealised( 1 ){ } typedef qtextures_t::iterator iterator; iterator begin(){ return m_qtextures.begin(); } iterator end(){ return m_qtextures.end(); } LoadImageCallback defaultLoader() const { return LoadImageCallback( 0, QERApp_LoadImage ); } Image* loadImage( const char* name ){ return defaultLoader().loadImage( name ); } qtexture_t* capture( const char* name ){ return capture( defaultLoader(), name ); } qtexture_t* capture( const LoadImageCallback& loader, const char* name ){ #if DEBUG_TEXTURES globalOutputStream() << "textures capture: " << makeQuoted( name ) << '\n'; #endif return m_qtextures.capture( TextureKey( loader, name ) ).get(); } void release( qtexture_t* texture ){ #if DEBUG_TEXTURES globalOutputStream() << "textures release: " << makeQuoted( texture->name ) << '\n'; #endif m_qtextures.release( TextureKey( texture->load, texture->name ) ); } void attach( TexturesCacheObserver& observer ){ ASSERT_MESSAGE( m_observer == 0, "TexturesMap::attach: cannot attach observer" ); m_observer = &observer; } void detach( TexturesCacheObserver& observer ){ ASSERT_MESSAGE( m_observer == &observer, "TexturesMap::detach: cannot detach observer" ); m_observer = 0; } void realise(){ if ( --m_unrealised == 0 ) { g_texture_globals.bTextureCompressionSupported = false; if ( GlobalOpenGL().ARB_texture_compression() ) { g_texture_globals.bTextureCompressionSupported = true; g_texture_globals.m_bOpenGLCompressionSupported = true; } if ( GlobalOpenGL().EXT_texture_compression_s3tc() ) { g_texture_globals.bTextureCompressionSupported = true; g_texture_globals.m_bS3CompressionSupported = true; } switch ( g_texture_globals.texture_components ) { case GL_RGBA: break; case GL_COMPRESSED_RGBA_ARB: if ( !g_texture_globals.m_bOpenGLCompressionSupported ) { globalOutputStream() << "OpenGL extension GL_ARB_texture_compression not supported by current graphics drivers\n"; g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE; g_texture_globals.texture_components = GL_RGBA; } break; case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: if ( !g_texture_globals.m_bS3CompressionSupported ) { globalOutputStream() << "OpenGL extension GL_EXT_texture_compression_s3tc not supported by current graphics drivers\n"; if ( g_texture_globals.m_bOpenGLCompressionSupported ) { g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_RGBA; g_texture_globals.texture_components = GL_COMPRESSED_RGBA_ARB; } else { g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE; g_texture_globals.texture_components = GL_RGBA; } } break; default: globalOutputStream() << "Unknown texture compression selected, reverting\n"; g_texture_globals.m_nTextureCompressionFormat = TEXTURECOMPRESSION_NONE; g_texture_globals.texture_components = GL_RGBA; break; } glGetIntegerv( GL_MAX_TEXTURE_SIZE, &max_tex_size ); if ( max_tex_size == 0 ) { max_tex_size = 1024; } for ( qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i ) { if ( !( *i ).value.empty() ) { qtexture_realise( *( *i ).value, ( *i ).key ); } } if ( m_observer != 0 ) { m_observer->realise(); } } } void unrealise(){ if ( ++m_unrealised == 1 ) { if ( m_observer != 0 ) { m_observer->unrealise(); } for ( qtextures_t::iterator i = m_qtextures.begin(); i != m_qtextures.end(); ++i ) { if ( !( *i ).value.empty() ) { qtexture_unrealise( *( *i ).value ); } } } } bool realised(){ return m_unrealised == 0; } }; TexturesMap* g_texturesmap; TexturesCache& GetTexturesCache(){ return *g_texturesmap; } void Textures_Realise(){ g_texturesmap->realise(); } void Textures_Unrealise(){ g_texturesmap->unrealise(); } Callback g_texturesModeChangedNotify; void Textures_setModeChangedNotify( const Callback& notify ){ g_texturesModeChangedNotify = notify; } void Textures_ModeChanged(){ if ( g_texturesmap->realised() ) { SetTexParameters( g_texture_mode ); for ( TexturesMap::iterator i = g_texturesmap->begin(); i != g_texturesmap->end(); ++i ) { glBindTexture( GL_TEXTURE_2D, ( *i ).value->texture_number ); SetTexParameters( g_texture_mode ); } glBindTexture( GL_TEXTURE_2D, 0 ); } g_texturesModeChangedNotify(); } void Textures_SetMode( ETexturesMode mode ){ if ( g_texture_mode != mode ) { g_texture_mode = mode; Textures_ModeChanged(); } } void Textures_setTextureComponents( GLint texture_components ){ if ( g_texture_globals.texture_components != texture_components ) { Textures_Unrealise(); g_texture_globals.texture_components = texture_components; Textures_Realise(); } } void Textures_UpdateTextureCompressionFormat(){ GLint texture_components = GL_RGBA; switch ( g_texture_globals.m_nTextureCompressionFormat ) { case ( TEXTURECOMPRESSION_NONE ): { texture_components = GL_RGBA; break; } case ( TEXTURECOMPRESSION_RGBA ): { texture_components = GL_COMPRESSED_RGBA_ARB; break; } case ( TEXTURECOMPRESSION_RGBA_S3TC_DXT1 ): { texture_components = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; } case ( TEXTURECOMPRESSION_RGBA_S3TC_DXT3 ): { texture_components = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; } case ( TEXTURECOMPRESSION_RGBA_S3TC_DXT5 ): { texture_components = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; } } Textures_setTextureComponents( texture_components ); } void TextureCompressionImport( TextureCompressionFormat& self, int value ){ if ( !g_texture_globals.m_bOpenGLCompressionSupported && g_texture_globals.m_bS3CompressionSupported && value >= 1 ) { ++value; } switch ( value ) { case 0: self = TEXTURECOMPRESSION_NONE; break; case 1: self = TEXTURECOMPRESSION_RGBA; break; case 2: self = TEXTURECOMPRESSION_RGBA_S3TC_DXT1; break; case 3: self = TEXTURECOMPRESSION_RGBA_S3TC_DXT3; break; case 4: self = TEXTURECOMPRESSION_RGBA_S3TC_DXT5; break; } Textures_UpdateTextureCompressionFormat(); } typedef ReferenceCaller1 TextureCompressionImportCaller; void TextureGammaImport( float& self, float value ){ if ( self != value ) { Textures_Unrealise(); self = value; Textures_Realise(); } } typedef ReferenceCaller1 TextureGammaImportCaller; void TextureModeImport( ETexturesMode& self, int value ){ switch ( value ) { case 0: Textures_SetMode( eTextures_NEAREST ); break; case 1: Textures_SetMode( eTextures_NEAREST_MIPMAP_NEAREST ); break; case 2: Textures_SetMode( eTextures_LINEAR ); break; case 3: Textures_SetMode( eTextures_NEAREST_MIPMAP_LINEAR ); break; case 4: Textures_SetMode( eTextures_LINEAR_MIPMAP_NEAREST ); break; case 5: Textures_SetMode( eTextures_LINEAR_MIPMAP_LINEAR ); break; case 6: Textures_SetMode( eTextures_MAX_ANISOTROPY ); } } typedef ReferenceCaller1 TextureModeImportCaller; void TextureModeExport( ETexturesMode& self, const IntImportCallback& importer ){ switch ( self ) { case eTextures_NEAREST: importer( 0 ); break; case eTextures_NEAREST_MIPMAP_NEAREST: importer( 1 ); break; case eTextures_LINEAR: importer( 2 ); break; case eTextures_NEAREST_MIPMAP_LINEAR: importer( 3 ); break; case eTextures_LINEAR_MIPMAP_NEAREST: importer( 4 ); break; case eTextures_LINEAR_MIPMAP_LINEAR: importer( 5 ); break; case eTextures_MAX_ANISOTROPY: importer( 6 ); break; default: importer( 4 ); } } typedef ReferenceCaller1 TextureModeExportCaller; void Textures_constructPreferences( PreferencesPage& page ){ { const char* percentages[] = { "12.5%", "25%", "50%", "100%", }; page.appendRadio( "Texture Quality", STRING_ARRAY_RANGE( percentages ), LatchedIntImportCaller( g_Textures_textureQuality ), IntExportCaller( g_Textures_textureQuality.m_latched ) ); } page.appendSpinner( "Texture Gamma", 1.0, 0.0, 1.0, FloatImportCallback( TextureGammaImportCaller( g_texture_globals.fGamma ) ), FloatExportCallback( FloatExportCaller( g_texture_globals.fGamma ) ) ); { const char* texture_mode[] = { "Nearest", "Nearest Mipmap", "Linear", "Bilinear", "Bilinear Mipmap", "Trilinear", "Anisotropy" }; page.appendCombo( "Texture Render Mode", STRING_ARRAY_RANGE( texture_mode ), IntImportCallback( TextureModeImportCaller( g_texture_mode ) ), IntExportCallback( TextureModeExportCaller( g_texture_mode ) ) ); } { const char* compression_none[] = { "None" }; const char* compression_opengl[] = { "None", "OpenGL ARB" }; const char* compression_s3tc[] = { "None", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5" }; const char* compression_opengl_s3tc[] = { "None", "OpenGL ARB", "S3TC DXT1", "S3TC DXT3", "S3TC DXT5" }; StringArrayRange compression( ( g_texture_globals.m_bOpenGLCompressionSupported ) ? ( g_texture_globals.m_bS3CompressionSupported ) ? STRING_ARRAY_RANGE( compression_opengl_s3tc ) : STRING_ARRAY_RANGE( compression_opengl ) : ( g_texture_globals.m_bS3CompressionSupported ) ? STRING_ARRAY_RANGE( compression_s3tc ) : STRING_ARRAY_RANGE( compression_none ) ); page.appendCombo( "Hardware Texture Compression", compression, TextureCompressionImportCaller( g_texture_globals.m_nTextureCompressionFormat ), IntExportCaller( reinterpret_cast( g_texture_globals.m_nTextureCompressionFormat ) ) ); } } void Textures_constructPage( PreferenceGroup& group ){ PreferencesPage page( group.createPage( "Textures", "Texture Settings" ) ); Textures_constructPreferences( page ); } void Textures_registerPreferencesPage(){ PreferencesDialog_addDisplayPage( FreeCaller1() ); } void TextureCompression_importString( const char* string ){ g_texture_globals.m_nTextureCompressionFormat = static_cast( atoi( string ) ); Textures_UpdateTextureCompressionFormat(); } typedef FreeCaller1 TextureCompressionImportStringCaller; void Textures_Construct(){ g_texturesmap = new TexturesMap; GlobalPreferenceSystem().registerPreference( "TextureCompressionFormat", TextureCompressionImportStringCaller(), IntExportStringCaller( reinterpret_cast( g_texture_globals.m_nTextureCompressionFormat ) ) ); GlobalPreferenceSystem().registerPreference( "TextureFiltering", IntImportStringCaller( reinterpret_cast( g_texture_mode ) ), IntExportStringCaller( reinterpret_cast( g_texture_mode ) ) ); GlobalPreferenceSystem().registerPreference( "TextureQuality", IntImportStringCaller( g_Textures_textureQuality.m_latched ), IntExportStringCaller( g_Textures_textureQuality.m_latched ) ); GlobalPreferenceSystem().registerPreference( "SI_Gamma", FloatImportStringCaller( g_texture_globals.fGamma ), FloatExportStringCaller( g_texture_globals.fGamma ) ); g_Textures_textureQuality.useLatched(); Textures_registerPreferencesPage(); Textures_ModeChanged(); } void Textures_Destroy(){ delete g_texturesmap; } #include "modulesystem/modulesmap.h" #include "modulesystem/singletonmodule.h" #include "modulesystem/moduleregistry.h" class TexturesDependencies : public GlobalRadiantModuleRef, public GlobalOpenGLModuleRef, public GlobalPreferenceSystemModuleRef { ImageModulesRef m_image_modules; public: TexturesDependencies() : m_image_modules( GlobalRadiant().getRequiredGameDescriptionKeyValue( "texturetypes" ) ){ } ImageModules& getImageModules(){ return m_image_modules.get(); } }; class TexturesAPI { TexturesCache* m_textures; public: typedef TexturesCache Type; STRING_CONSTANT( Name, "*" ); TexturesAPI(){ Textures_Construct(); m_textures = &GetTexturesCache(); } ~TexturesAPI(){ Textures_Destroy(); } TexturesCache* getTable(){ return m_textures; } }; typedef SingletonModule TexturesModule; typedef Static StaticTexturesModule; StaticRegisterModule staticRegisterTextures( StaticTexturesModule::instance() ); ImageModules& Textures_getImageModules(){ return StaticTexturesModule::instance().getDependencies().getImageModules(); }