Panda3D
 All Classes Functions Variables Enumerations
texturePool.cxx
00001 // Filename: texturePool.cxx
00002 // Created by:  drose (26Apr00)
00003 // Updated by: fperazzi, PandaSE(29Apr10) (added ns_load_2d_texture_array)
00004 //
00005 ////////////////////////////////////////////////////////////////////
00006 //
00007 // PANDA 3D SOFTWARE
00008 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00009 //
00010 // All use of this software is subject to the terms of the revised BSD
00011 // license.  You should have received a copy of this license along
00012 // with this source code in a file named "LICENSE."
00013 //
00014 ////////////////////////////////////////////////////////////////////
00015 
00016 #include "texturePool.h"
00017 #include "config_gobj.h"
00018 #include "config_util.h"
00019 #include "config_express.h"
00020 #include "string_utils.h"
00021 #include "virtualFileSystem.h"
00022 #include "bamCache.h"
00023 #include "bamCacheRecord.h"
00024 #include "pnmFileTypeRegistry.h"
00025 #include "texturePoolFilter.h"
00026 #include "configVariableList.h"
00027 #include "load_dso.h"
00028 #include "mutexHolder.h"
00029 
00030 TexturePool *TexturePool::_global_ptr;
00031 
00032 ////////////////////////////////////////////////////////////////////
00033 //     Function: TexturePool::write
00034 //       Access: Published, Static
00035 //  Description: Lists the contents of the texture pool to the
00036 //               indicated output stream.
00037 //               For debugging.
00038 ////////////////////////////////////////////////////////////////////
00039 void TexturePool::
00040 write(ostream &out) {
00041   get_global_ptr()->ns_list_contents(out);
00042 }
00043 
00044 ////////////////////////////////////////////////////////////////////
00045 //     Function: TexturePool::register_texture_type
00046 //       Access: Public
00047 //  Description: Records a factory function that makes a Texture
00048 //               object of the appropriate type for one or more
00049 //               particular filename extensions.  The string
00050 //               extensions may be a string that contains
00051 //               space-separated list of extensions, case-insensitive.
00052 ////////////////////////////////////////////////////////////////////
00053 void TexturePool::
00054 register_texture_type(MakeTextureFunc *func, const string &extensions) {
00055   MutexHolder holder(_lock);
00056 
00057   vector_string words;
00058   extract_words(downcase(extensions), words);
00059 
00060   vector_string::const_iterator wi;
00061   for (wi = words.begin(); wi != words.end(); ++wi) {
00062     _type_registry[*wi] = func;
00063   }
00064 }
00065 
00066 ////////////////////////////////////////////////////////////////////
00067 //     Function: TexturePool::register_filter
00068 //       Access: Public
00069 //  Description: Records a TexturePoolFilter object that may operate
00070 //               on texture images as they are loaded from disk.
00071 ////////////////////////////////////////////////////////////////////
00072 void TexturePool::
00073 register_filter(TexturePoolFilter *filter) {
00074   MutexHolder holder(_lock);
00075 
00076   gobj_cat.info()
00077     << "Registering Texture filter " << *filter << "\n";
00078   _filter_registry.push_back(filter);
00079 }
00080 
00081 ////////////////////////////////////////////////////////////////////
00082 //     Function: TexturePool::get_texture_type
00083 //       Access: Public
00084 //  Description: Returns the factory function to construct a new
00085 //               texture of the type appropriate for the indicated
00086 //               filename extension, if any, or NULL if the extension
00087 //               is not one of the extensions for a texture file.
00088 ////////////////////////////////////////////////////////////////////
00089 TexturePool::MakeTextureFunc *TexturePool::
00090 get_texture_type(const string &extension) const {
00091   MutexHolder holder(_lock);
00092 
00093   string c = downcase(extension);
00094   TypeRegistry::const_iterator ti;
00095   ti = _type_registry.find(c);
00096   if (ti != _type_registry.end()) {
00097     return (*ti).second;
00098   }
00099 
00100   // Check the PNM type registry.
00101   PNMFileTypeRegistry *pnm_reg = PNMFileTypeRegistry::get_global_ptr();
00102   PNMFileType *type = pnm_reg->get_type_from_extension(c);
00103   if (type != (PNMFileType *)NULL) {
00104     // This is a known image type; create an ordinary Texture.
00105     ((TexturePool *)this)->_type_registry[c] = Texture::make_texture;
00106     return Texture::make_texture;
00107   }
00108 
00109   // This is an unknown texture type.
00110   return NULL;
00111 }
00112 
00113 ////////////////////////////////////////////////////////////////////
00114 //     Function: TexturePool::write_texture_types
00115 //       Access: Public
00116 //  Description: Outputs a list of the available texture types to the
00117 //               indicated output stream.  This is mostly the list of
00118 //               available image types, with maybe a few additional
00119 //               ones for video textures.
00120 ////////////////////////////////////////////////////////////////////
00121 void TexturePool::
00122 write_texture_types(ostream &out, int indent_level) const {
00123   MutexHolder holder(_lock);
00124 
00125   PNMFileTypeRegistry *pnm_reg = PNMFileTypeRegistry::get_global_ptr();
00126   pnm_reg->write(out, indent_level);
00127 
00128   // Also output any of the additional texture types, that aren't
00129   // strictly images (these are typically video textures).
00130   TypeRegistry::const_iterator ti;
00131   for (ti = _type_registry.begin(); ti != _type_registry.end(); ++ti) {
00132     string extension = (*ti).first;
00133     MakeTextureFunc *func = (*ti).second;
00134 
00135     if (pnm_reg->get_type_from_extension(extension) == NULL) {
00136       PT(Texture) tex = func();
00137       string name = tex->get_type().get_name();
00138       indent(out, indent_level) << name;
00139       indent(out, max(30 - (int)name.length(), 0))
00140         << "  ." << extension << "\n";
00141     }
00142   }
00143 }
00144 
00145 ////////////////////////////////////////////////////////////////////
00146 //     Function: TexturePool::get_global_ptr
00147 //       Access: Public, Static
00148 //  Description: Initializes and/or returns the global pointer to the
00149 //               one TexturePool object in the system.
00150 ////////////////////////////////////////////////////////////////////
00151 TexturePool *TexturePool::
00152 get_global_ptr() {
00153   if (_global_ptr == (TexturePool *)NULL) {
00154     _global_ptr = new TexturePool;
00155 
00156     // We have to call this here, not in the constructor, so that the
00157     // _global_ptr is safely assigned by the time the filters begin to
00158     // load.
00159     _global_ptr->load_filters();
00160   }
00161   return _global_ptr;
00162 }
00163 
00164 ////////////////////////////////////////////////////////////////////
00165 //     Function: TexturePool::Constructor
00166 //       Access: Private
00167 //  Description: The constructor is not intended to be called
00168 //               directly; there's only supposed to be one TexturePool
00169 //               in the universe and it constructs itself.
00170 ////////////////////////////////////////////////////////////////////
00171 TexturePool::
00172 TexturePool() {
00173   ConfigVariableFilename fake_texture_image
00174     ("fake-texture-image", "",
00175      PRC_DESC("Set this to enable a speedy-load mode in which you don't care "
00176               "what the world looks like, you just want it to load in minimal "
00177               "time.  This causes all texture loads via the TexturePool to use "
00178               "the same texture file, which will presumably only be loaded "
00179               "once."));
00180   _fake_texture_image = fake_texture_image;
00181 }
00182 
00183 ////////////////////////////////////////////////////////////////////
00184 //     Function: TexturePool::ns_has_texture
00185 //       Access: Private
00186 //  Description: The nonstatic implementation of has_texture().
00187 ////////////////////////////////////////////////////////////////////
00188 bool TexturePool::
00189 ns_has_texture(const Filename &orig_filename) {
00190   MutexHolder holder(_lock);
00191 
00192   Filename filename;
00193   resolve_filename(filename, orig_filename, false, LoaderOptions());
00194 
00195   Textures::const_iterator ti;
00196   ti = _textures.find(filename);
00197   if (ti != _textures.end()) {
00198     // This texture was previously loaded.
00199     return true;
00200   }
00201 
00202   return false;
00203 }
00204 
00205 ////////////////////////////////////////////////////////////////////
00206 //     Function: TexturePool::ns_load_texture
00207 //       Access: Private
00208 //  Description: The nonstatic implementation of load_texture().
00209 ////////////////////////////////////////////////////////////////////
00210 Texture *TexturePool::
00211 ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
00212                 bool read_mipmaps, const LoaderOptions &options) {
00213   Filename filename;
00214 
00215   {
00216     MutexHolder holder(_lock);
00217     resolve_filename(filename, orig_filename, read_mipmaps, options);
00218     Textures::const_iterator ti;
00219     ti = _textures.find(filename);
00220     if (ti != _textures.end()) {
00221       // This texture was previously loaded.
00222       Texture *tex = (*ti).second;
00223       nassertr(!tex->get_fullpath().empty(), tex);
00224       return tex;
00225     }
00226   }
00227 
00228   // The texture was not found in the pool.
00229   PT(Texture) tex;
00230   PT(BamCacheRecord) record;
00231   bool store_record = false;
00232 
00233   // Can one of our texture filters supply the texture?
00234   tex = pre_load(orig_filename, Filename(), primary_file_num_channels, 0,
00235                  read_mipmaps, options);
00236 
00237   BamCache *cache = BamCache::get_global_ptr();
00238   bool compressed_cache_record = false;
00239   try_load_cache(tex, cache, filename, record, compressed_cache_record,
00240                  options);
00241 
00242   if (tex == (Texture *)NULL) {
00243     // The texture was neither in the pool, nor found in the on-disk
00244     // cache; it needs to be loaded from its source image(s).
00245     gobj_cat.info()
00246       << "Loading texture " << filename << "\n";
00247 
00248     string ext = downcase(filename.get_extension());
00249     if (ext == "txo" || ext == "bam") {
00250       // Assume this is a txo file, which might conceivably contain a
00251       // movie file or some other subclass of Texture.  In that case,
00252       // use make_from_txo() to load it instead of read().
00253       VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
00254 
00255       filename.set_binary();
00256       PT(VirtualFile) file = vfs->get_file(filename);
00257       if (file == (VirtualFile *)NULL) {
00258         // No such file.
00259         gobj_cat.error()
00260           << "Could not find " << filename << "\n";
00261         return false;
00262       }
00263 
00264       if (gobj_cat.is_debug()) {
00265         gobj_cat.debug()
00266           << "Reading texture object " << filename << "\n";
00267       }
00268 
00269       istream *in = file->open_read_file(true);
00270       tex = Texture::make_from_txo(*in, filename);
00271       vfs->close_read_file(in);
00272 
00273       if (tex == (Texture *)NULL) {
00274         return false;
00275       }
00276       tex->set_fullpath(filename);
00277       tex->clear_alpha_fullpath();
00278       tex->set_keep_ram_image(false);
00279         
00280     } else {
00281       // Read it the conventional way.
00282       tex = ns_make_texture(ext);
00283       if (!tex->read(filename, Filename(), primary_file_num_channels, 0,
00284                      0, 0, false, read_mipmaps, record, options)) {
00285         // This texture was not found or could not be read.
00286         report_texture_unreadable(filename);
00287         return NULL;
00288       }
00289     }
00290 
00291     if (options.get_texture_flags() & LoaderOptions::TF_preload_simple) {
00292       tex->generate_simple_ram_image();
00293     }
00294 
00295     store_record = (record != (BamCacheRecord *)NULL);
00296   }
00297 
00298   if (cache->get_cache_compressed_textures() && tex->has_compression()) {
00299 #ifndef HAVE_SQUISH
00300     bool needs_driver_compression = true;
00301 #else
00302     bool needs_driver_compression = driver_compress_textures;
00303 #endif // HAVE_SQUISH
00304     if (needs_driver_compression) {
00305       // We don't want to save the uncompressed version; we'll save the
00306       // compressed version when it becomes available.
00307       store_record = false;
00308       if (!compressed_cache_record) {
00309         tex->set_post_load_store_cache(true);
00310       }
00311     }
00312 
00313   } else if (!cache->get_cache_textures()) {
00314     // We don't want to save this texture.
00315     store_record = false;
00316   }
00317 
00318   // Set the original filename, before we searched along the path.
00319   nassertr(tex != (Texture *)NULL, NULL);
00320   tex->set_filename(orig_filename);
00321   tex->set_fullpath(filename);
00322   tex->_texture_pool_key = filename;
00323 
00324   {
00325     MutexHolder holder(_lock);
00326 
00327     // Now look again--someone may have just loaded this texture in
00328     // another thread.
00329     Textures::const_iterator ti;
00330     ti = _textures.find(filename);
00331     if (ti != _textures.end()) {
00332       // This texture was previously loaded.
00333       Texture *tex = (*ti).second;
00334       nassertr(!tex->get_fullpath().empty(), tex);
00335       return tex;
00336     }
00337 
00338     _textures[filename] = tex;
00339   }
00340 
00341   if (store_record && tex->is_cacheable()) {
00342     // Store the on-disk cache record for next time.
00343     record->set_data(tex, tex);
00344     cache->store(record);
00345   }
00346 
00347   if (!(options.get_texture_flags() & LoaderOptions::TF_preload)) {
00348     // And now drop the RAM until we need it.
00349     tex->clear_ram_image();
00350   }
00351 
00352   nassertr(!tex->get_fullpath().empty(), tex);
00353 
00354   // Finally, apply any post-loading texture filters.
00355   tex = post_load(tex);
00356 
00357   return tex;
00358 }
00359 
00360 ////////////////////////////////////////////////////////////////////
00361 //     Function: TexturePool::ns_load_texture
00362 //       Access: Private
00363 //  Description: The nonstatic implementation of load_texture().
00364 ////////////////////////////////////////////////////////////////////
00365 Texture *TexturePool::
00366 ns_load_texture(const Filename &orig_filename, 
00367                 const Filename &orig_alpha_filename,
00368                 int primary_file_num_channels,
00369                 int alpha_file_channel,
00370                 bool read_mipmaps, const LoaderOptions &options) {
00371   if (!_fake_texture_image.empty()) {
00372     return ns_load_texture(_fake_texture_image, primary_file_num_channels,
00373                            read_mipmaps, options);
00374   }
00375 
00376   Filename filename;
00377   Filename alpha_filename;
00378 
00379   {
00380     MutexHolder holder(_lock);
00381     resolve_filename(filename, orig_filename, read_mipmaps, options);
00382     resolve_filename(alpha_filename, orig_alpha_filename, read_mipmaps, options);
00383 
00384     Textures::const_iterator ti;
00385     ti = _textures.find(filename);
00386     if (ti != _textures.end()) {
00387       // This texture was previously loaded.
00388       Texture *tex = (*ti).second;
00389       nassertr(!tex->get_fullpath().empty(), tex);
00390       return tex;
00391     }
00392   }
00393 
00394   PT(Texture) tex;
00395   PT(BamCacheRecord) record;
00396   bool store_record = false;
00397 
00398   // Can one of our texture filters supply the texture?
00399   tex = pre_load(orig_filename, alpha_filename, primary_file_num_channels, 
00400                  alpha_file_channel, read_mipmaps, options);
00401 
00402   BamCache *cache = BamCache::get_global_ptr();
00403   bool compressed_cache_record = false;
00404   try_load_cache(tex, cache, filename, record, compressed_cache_record,
00405                  options);
00406 
00407   if (tex == (Texture *)NULL) {
00408     // The texture was neither in the pool, nor found in the on-disk
00409     // cache; it needs to be loaded from its source image(s).
00410     gobj_cat.info()
00411       << "Loading texture " << filename << " and alpha component "
00412       << alpha_filename << endl;
00413     tex = ns_make_texture(filename.get_extension());
00414     if (!tex->read(filename, alpha_filename, primary_file_num_channels,
00415                    alpha_file_channel, 0, 0, false, read_mipmaps, NULL,
00416                    options)) {
00417       // This texture was not found or could not be read.
00418       report_texture_unreadable(filename);
00419       return NULL;
00420     }
00421 
00422     if (options.get_texture_flags() & LoaderOptions::TF_preload_simple) {
00423       tex->generate_simple_ram_image();
00424     }
00425 
00426     store_record = (record != (BamCacheRecord *)NULL);
00427   }
00428 
00429   if (cache->get_cache_compressed_textures() && tex->has_compression()) {
00430 #ifndef HAVE_SQUISH
00431     bool needs_driver_compression = true;
00432 #else
00433     bool needs_driver_compression = driver_compress_textures;
00434 #endif // HAVE_SQUISH
00435     if (needs_driver_compression) {
00436       // We don't want to save the uncompressed version; we'll save the
00437       // compressed version when it becomes available.
00438       store_record = false;
00439       if (!compressed_cache_record) {
00440         tex->set_post_load_store_cache(true);
00441       }
00442     }
00443 
00444   } else if (!cache->get_cache_textures()) {
00445     // We don't want to save this texture.
00446     store_record = false;
00447   }
00448 
00449   // Set the original filenames, before we searched along the path.
00450   nassertr(tex != (Texture *)NULL, NULL);
00451   tex->set_filename(orig_filename);
00452   tex->set_fullpath(filename);
00453   tex->set_alpha_filename(orig_alpha_filename);
00454   tex->set_alpha_fullpath(alpha_filename);
00455   tex->_texture_pool_key = filename;
00456 
00457   {
00458     MutexHolder holder(_lock);
00459 
00460     // Now look again.
00461     Textures::const_iterator ti;
00462     ti = _textures.find(filename);
00463     if (ti != _textures.end()) {
00464       // This texture was previously loaded.
00465       Texture *tex = (*ti).second;
00466       nassertr(!tex->get_fullpath().empty(), tex);
00467       return tex;
00468     }
00469     
00470     _textures[filename] = tex;
00471   }
00472 
00473   if (store_record && tex->is_cacheable()) {
00474     // Store the on-disk cache record for next time.
00475     record->set_data(tex, tex);
00476     cache->store(record);
00477   }
00478 
00479   if (!(options.get_texture_flags() & LoaderOptions::TF_preload)) {
00480     // And now drop the RAM until we need it.
00481     tex->clear_ram_image();
00482   }
00483 
00484   nassertr(!tex->get_fullpath().empty(), tex);
00485 
00486   // Finally, apply any post-loading texture filters.
00487   tex = post_load(tex);
00488 
00489   return tex;
00490 }
00491 
00492 ////////////////////////////////////////////////////////////////////
00493 //     Function: TexturePool::ns_load_3d_texture
00494 //       Access: Private
00495 //  Description: The nonstatic implementation of load_3d_texture().
00496 ////////////////////////////////////////////////////////////////////
00497 Texture *TexturePool::
00498 ns_load_3d_texture(const Filename &filename_pattern,
00499                    bool read_mipmaps, const LoaderOptions &options) {
00500   Filename orig_filename(filename_pattern);
00501   orig_filename.set_pattern(true);
00502 
00503   Filename filename;
00504   {
00505     MutexHolder holder(_lock);
00506     resolve_filename(filename, orig_filename, read_mipmaps, options);
00507 
00508     Textures::const_iterator ti;
00509     ti = _textures.find(filename);
00510     if (ti != _textures.end()) {
00511       if ((*ti).second->get_texture_type() == Texture::TT_3d_texture) {
00512         // This texture was previously loaded, as a 3d texture
00513         return (*ti).second;
00514       }
00515     }
00516   }
00517 
00518   PT(Texture) tex;
00519   PT(BamCacheRecord) record;
00520   bool store_record = false;
00521 
00522   BamCache *cache = BamCache::get_global_ptr();
00523   bool compressed_cache_record = false;
00524   try_load_cache(tex, cache, filename, record, compressed_cache_record,
00525                  options);
00526 
00527   if (tex == (Texture *)NULL || 
00528       tex->get_texture_type() != Texture::TT_3d_texture) {
00529     // The texture was neither in the pool, nor found in the on-disk
00530     // cache; it needs to be loaded from its source image(s).
00531     gobj_cat.info()
00532       << "Loading 3-d texture " << filename << "\n";
00533     tex = ns_make_texture(filename.get_extension());
00534     tex->setup_3d_texture();
00535     if (!tex->read(filename, 0, 0, true, read_mipmaps, options)) {
00536       // This texture was not found or could not be read.
00537       report_texture_unreadable(filename);
00538       return NULL;
00539     }
00540     store_record = (record != (BamCacheRecord *)NULL);
00541   }
00542 
00543   if (cache->get_cache_compressed_textures() && tex->has_compression()) {
00544 #ifndef HAVE_SQUISH
00545     bool needs_driver_compression = true;
00546 #else
00547     bool needs_driver_compression = driver_compress_textures;
00548 #endif // HAVE_SQUISH
00549     if (needs_driver_compression) {
00550       // We don't want to save the uncompressed version; we'll save the
00551       // compressed version when it becomes available.
00552       store_record = false;
00553       if (!compressed_cache_record) {
00554         tex->set_post_load_store_cache(true);
00555       }
00556     }
00557 
00558   } else if (!cache->get_cache_textures()) {
00559     // We don't want to save this texture.
00560     store_record = false;
00561   }
00562 
00563   // Set the original filename, before we searched along the path.
00564   nassertr(tex != (Texture *)NULL, false);
00565   tex->set_filename(filename_pattern);
00566   tex->set_fullpath(filename);
00567   tex->_texture_pool_key = filename;
00568 
00569   {
00570     MutexHolder holder(_lock);
00571 
00572     // Now look again.
00573     Textures::const_iterator ti;
00574     ti = _textures.find(filename);
00575     if (ti != _textures.end()) {
00576       if ((*ti).second->get_texture_type() == Texture::TT_3d_texture) {
00577         // This texture was previously loaded, as a 3d texture
00578         return (*ti).second;
00579       }
00580     }
00581 
00582     _textures[filename] = tex;
00583   }
00584 
00585   if (store_record && tex->is_cacheable()) {
00586     // Store the on-disk cache record for next time.
00587     record->set_data(tex, tex);
00588     cache->store(record);
00589   }
00590 
00591   nassertr(!tex->get_fullpath().empty(), tex);
00592   return tex;
00593 }
00594 
00595 ////////////////////////////////////////////////////////////////////
00596 //     Function: TexturePool::ns_load_2d_texture_array
00597 //       Access: Private
00598 //  Description: The nonstatic implementation of load_2d_texture_array().
00599 ////////////////////////////////////////////////////////////////////
00600 Texture *TexturePool::
00601 ns_load_2d_texture_array(const Filename &filename_pattern,
00602                          bool read_mipmaps, const LoaderOptions &options) {
00603   Filename orig_filename(filename_pattern);
00604   orig_filename.set_pattern(true);
00605 
00606   Filename filename;
00607   Filename unique_filename; //differentiate 3d-textures from 2d-texture arrays
00608   {
00609     MutexHolder holder(_lock);
00610     resolve_filename(filename, orig_filename, read_mipmaps, options);
00611     // Differentiate from preloaded 3d textures
00612     unique_filename = filename + ".2DARRAY";
00613 
00614     Textures::const_iterator ti;
00615     ti = _textures.find(unique_filename);
00616     if (ti != _textures.end()) {
00617       if ((*ti).second->get_texture_type() == Texture::TT_2d_texture_array) {
00618         // This texture was previously loaded, as a 2d texture array
00619         return (*ti).second;
00620       }
00621     }
00622   }
00623 
00624   PT(Texture) tex;
00625   PT(BamCacheRecord) record;
00626   bool store_record = false;
00627 
00628   BamCache *cache = BamCache::get_global_ptr();
00629   bool compressed_cache_record = false;
00630   try_load_cache(tex, cache, filename, record, compressed_cache_record,
00631                  options);
00632 
00633   if (tex == (Texture *)NULL || 
00634       tex->get_texture_type() != Texture::TT_2d_texture_array) {
00635     // The texture was neither in the pool, nor found in the on-disk
00636     // cache; it needs to be loaded from its source image(s).
00637     gobj_cat.info()
00638       << "Loading 2-d texture array " << filename << "\n";
00639     tex = ns_make_texture(filename.get_extension());
00640     tex->setup_2d_texture_array();
00641     if (!tex->read(filename, 0, 0, true, read_mipmaps, options)) {
00642       // This texture was not found or could not be read.
00643       report_texture_unreadable(filename);
00644       return NULL;
00645     }
00646     store_record = (record != (BamCacheRecord *)NULL);
00647   }
00648 
00649   if (cache->get_cache_compressed_textures() && tex->has_compression()) {
00650 #ifndef HAVE_SQUISH
00651     bool needs_driver_compression = true;
00652 #else
00653     bool needs_driver_compression = driver_compress_textures;
00654 #endif // HAVE_SQUISH
00655     if (needs_driver_compression) {
00656       // We don't want to save the uncompressed version; we'll save the
00657       // compressed version when it becomes available.
00658       store_record = false;
00659       if (!compressed_cache_record) {
00660         tex->set_post_load_store_cache(true);
00661       }
00662     }
00663 
00664   } else if (!cache->get_cache_textures()) {
00665     // We don't want to save this texture.
00666     store_record = false;
00667   }
00668 
00669   // Set the original filename, before we searched along the path.
00670   nassertr(tex != (Texture *)NULL, false);
00671   tex->set_filename(filename_pattern);
00672   tex->set_fullpath(filename);
00673   tex->_texture_pool_key = filename;
00674 
00675   {
00676     MutexHolder holder(_lock);
00677 
00678     // Now look again.
00679     Textures::const_iterator ti;
00680     ti = _textures.find(unique_filename);
00681     if (ti != _textures.end()) {
00682       if ((*ti).second->get_texture_type() == Texture::TT_2d_texture_array) {
00683         // This texture was previously loaded, as a 2d texture array
00684         return (*ti).second;
00685       }
00686     }
00687 
00688     _textures[unique_filename] = tex;
00689   }
00690 
00691   if (store_record && tex->is_cacheable()) {
00692     // Store the on-disk cache record for next time.
00693     record->set_data(tex, tex);
00694     cache->store(record);
00695   }
00696 
00697   nassertr(!tex->get_fullpath().empty(), tex);
00698   return tex;
00699 }
00700 
00701 ////////////////////////////////////////////////////////////////////
00702 //     Function: TexturePool::ns_load_cube_map
00703 //       Access: Private
00704 //  Description: The nonstatic implementation of load_cube_map().
00705 ////////////////////////////////////////////////////////////////////
00706 Texture *TexturePool::
00707 ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps, 
00708                  const LoaderOptions &options) {
00709   Filename orig_filename(filename_pattern);
00710   orig_filename.set_pattern(true);
00711 
00712   Filename filename;
00713   {
00714     MutexHolder holder(_lock);
00715     resolve_filename(filename, orig_filename, read_mipmaps, options);
00716 
00717     Textures::const_iterator ti;
00718     ti = _textures.find(filename);
00719     if (ti != _textures.end()) {
00720       // This texture was previously loaded.
00721       return (*ti).second;
00722     }
00723   }
00724 
00725   PT(Texture) tex;
00726   PT(BamCacheRecord) record;
00727   bool store_record = false;
00728 
00729   BamCache *cache = BamCache::get_global_ptr();
00730   bool compressed_cache_record = false;
00731   try_load_cache(tex, cache, filename, record, compressed_cache_record,
00732                  options);
00733 
00734   if (tex == (Texture *)NULL || 
00735       tex->get_texture_type() != Texture::TT_cube_map) {
00736     // The texture was neither in the pool, nor found in the on-disk
00737     // cache; it needs to be loaded from its source image(s).
00738     gobj_cat.info()
00739       << "Loading cube map texture " << filename << "\n";
00740     tex = ns_make_texture(filename.get_extension());
00741     tex->setup_cube_map();
00742     if (!tex->read(filename, 0, 0, true, read_mipmaps, options)) {
00743       // This texture was not found or could not be read.
00744       report_texture_unreadable(filename);
00745       return NULL;
00746     }
00747     store_record = (record != (BamCacheRecord *)NULL);
00748   }
00749 
00750   if (cache->get_cache_compressed_textures() && tex->has_compression()) {
00751 #ifndef HAVE_SQUISH
00752     bool needs_driver_compression = true;
00753 #else
00754     bool needs_driver_compression = driver_compress_textures;
00755 #endif // HAVE_SQUISH
00756     if (needs_driver_compression) {
00757       // We don't want to save the uncompressed version; we'll save the
00758       // compressed version when it becomes available.
00759       store_record = false;
00760       if (!compressed_cache_record) {
00761         tex->set_post_load_store_cache(true);
00762       }
00763     }
00764 
00765   } else if (!cache->get_cache_textures()) {
00766     // We don't want to save this texture.
00767     store_record = false;
00768   }
00769     
00770   // Set the original filename, before we searched along the path.
00771   nassertr(tex != (Texture *)NULL, false);
00772   tex->set_filename(filename_pattern);
00773   tex->set_fullpath(filename);
00774   tex->_texture_pool_key = filename;
00775 
00776   {
00777     MutexHolder holder(_lock);
00778 
00779     // Now look again.
00780     Textures::const_iterator ti;
00781     ti = _textures.find(filename);
00782     if (ti != _textures.end()) {
00783       // This texture was previously loaded.
00784       return (*ti).second;
00785     }
00786 
00787     _textures[filename] = tex;
00788   }
00789 
00790   if (store_record && tex->is_cacheable()) {
00791     // Store the on-disk cache record for next time.
00792     record->set_data(tex, tex);
00793     cache->store(record);
00794   }
00795 
00796   nassertr(!tex->get_fullpath().empty(), tex);
00797   return tex;
00798 }
00799 
00800 ////////////////////////////////////////////////////////////////////
00801 //     Function: TexturePool::ns_get_normalization_cube_map
00802 //       Access: Private
00803 //  Description: The nonstatic implementation of get_normalization_cube_map().
00804 ////////////////////////////////////////////////////////////////////
00805 Texture *TexturePool::
00806 ns_get_normalization_cube_map(int size) {
00807   MutexHolder holder(_lock);
00808 
00809   if (_normalization_cube_map == (Texture *)NULL) {
00810     _normalization_cube_map = new Texture("normalization_cube_map");
00811   }
00812   if (_normalization_cube_map->get_x_size() < size ||
00813       _normalization_cube_map->get_texture_type() != Texture::TT_cube_map) {
00814     _normalization_cube_map->generate_normalization_cube_map(size);
00815   }
00816 
00817   return _normalization_cube_map;
00818 }
00819 
00820 ////////////////////////////////////////////////////////////////////
00821 //     Function: TexturePool::ns_get_alpha_scale_map
00822 //       Access: Private
00823 //  Description: The nonstatic implementation of get_alpha_scale_map().
00824 ////////////////////////////////////////////////////////////////////
00825 Texture *TexturePool::
00826 ns_get_alpha_scale_map() {
00827   MutexHolder holder(_lock);
00828 
00829   if (_alpha_scale_map == (Texture *)NULL) {
00830     _alpha_scale_map = new Texture("alpha_scale_map");
00831     _alpha_scale_map->generate_alpha_scale_map();
00832   }
00833 
00834   return _alpha_scale_map;
00835 }
00836 
00837 ////////////////////////////////////////////////////////////////////
00838 //     Function: TexturePool::ns_add_texture
00839 //       Access: Private
00840 //  Description: The nonstatic implementation of add_texture().
00841 ////////////////////////////////////////////////////////////////////
00842 void TexturePool::
00843 ns_add_texture(Texture *tex) {
00844   PT(Texture) keep = tex;
00845   MutexHolder holder(_lock);
00846 
00847   if (!tex->_texture_pool_key.empty()) {
00848     ns_release_texture(tex);
00849   }
00850   string filename = tex->get_fullpath();
00851   if (filename.empty()) {
00852     gobj_cat.error() << "Attempt to call add_texture() on an unnamed texture.\n";
00853   }
00854 
00855   // We blow away whatever texture was there previously, if any.
00856   tex->_texture_pool_key = filename;
00857   _textures[filename] = tex;
00858   nassertv(!tex->get_fullpath().empty());
00859 }
00860 
00861 ////////////////////////////////////////////////////////////////////
00862 //     Function: TexturePool::ns_release_texture
00863 //       Access: Private
00864 //  Description: The nonstatic implementation of release_texture().
00865 ////////////////////////////////////////////////////////////////////
00866 void TexturePool::
00867 ns_release_texture(Texture *tex) {
00868   MutexHolder holder(_lock);
00869 
00870   if (!tex->_texture_pool_key.empty()) {
00871     Textures::iterator ti;
00872     ti = _textures.find(tex->_texture_pool_key);
00873     if (ti != _textures.end() && (*ti).second == tex) {
00874       _textures.erase(ti);
00875     }
00876     tex->_texture_pool_key = string();
00877   }
00878 
00879   // Blow away the cache of resolved relative filenames.
00880   _relpath_lookup.clear();
00881 }
00882 
00883 ////////////////////////////////////////////////////////////////////
00884 //     Function: TexturePool::ns_release_all_textures
00885 //       Access: Private
00886 //  Description: The nonstatic implementation of release_all_textures().
00887 ////////////////////////////////////////////////////////////////////
00888 void TexturePool::
00889 ns_release_all_textures() {
00890   MutexHolder holder(_lock);
00891 
00892   Textures::iterator ti;
00893   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00894     Texture *tex = (*ti).second;
00895     tex->_texture_pool_key = string();
00896   }
00897 
00898   _textures.clear();
00899   _normalization_cube_map = NULL;
00900 
00901   // Blow away the cache of resolved relative filenames.
00902   _relpath_lookup.clear();
00903 }
00904 
00905 ////////////////////////////////////////////////////////////////////
00906 //     Function: TexturePool::ns_garbage_collect
00907 //       Access: Private
00908 //  Description: The nonstatic implementation of garbage_collect().
00909 ////////////////////////////////////////////////////////////////////
00910 int TexturePool::
00911 ns_garbage_collect() {
00912   MutexHolder holder(_lock);
00913 
00914   int num_released = 0;
00915   Textures new_set;
00916 
00917   Textures::iterator ti;
00918   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00919     Texture *tex = (*ti).second;
00920     if (tex->get_ref_count() == 1) {
00921       if (gobj_cat.is_debug()) {
00922         gobj_cat.debug()
00923           << "Releasing " << (*ti).first << "\n";
00924       }
00925       ++num_released;
00926       tex->_texture_pool_key = string();
00927     } else {
00928       new_set.insert(new_set.end(), *ti);
00929     }
00930   }
00931 
00932   _textures.swap(new_set);
00933 
00934   if (_normalization_cube_map != (Texture *)NULL &&
00935       _normalization_cube_map->get_ref_count() == 1) {
00936     if (gobj_cat.is_debug()) {
00937       gobj_cat.debug()
00938         << "Releasing normalization cube map\n";
00939     }
00940     ++num_released;
00941     _normalization_cube_map = NULL;
00942   }
00943 
00944   return num_released;
00945 }
00946 
00947 ////////////////////////////////////////////////////////////////////
00948 //     Function: TexturePool::ns_list_contents
00949 //       Access: Private
00950 //  Description: The nonstatic implementation of list_contents().
00951 ////////////////////////////////////////////////////////////////////
00952 void TexturePool::
00953 ns_list_contents(ostream &out) const {
00954   MutexHolder holder(_lock);
00955 
00956   int total_size;
00957   int total_ram_size;
00958   Textures::const_iterator ti;
00959 
00960   out << "texture pool contents:\n";
00961   
00962   total_size = 0;
00963   total_ram_size = 0;
00964   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00965     Texture *tex = (*ti).second;
00966     out << (*ti).first << "\n";
00967     out << "  (count = " << tex->get_ref_count() 
00968         << ", ram  = " << tex->get_ram_image_size() 
00969         << ", size = " << tex->get_ram_page_size()
00970         << ", w = " << tex->get_x_size() 
00971         << ", h = " << tex->get_y_size() 
00972         << ")\n";
00973     nassertv(tex->_texture_pool_key == (*ti).first);
00974     total_ram_size += tex->get_ram_image_size();
00975     total_size += tex->get_ram_page_size();
00976   }
00977   
00978   out << "total number of textures: " << _textures.size() << "\n";
00979   out << "texture pool ram : " << total_ram_size << "\n";
00980   out << "texture pool size: " << total_size << "\n";
00981   out << "texture pool size - texture pool ram: " << total_size - total_ram_size << "\n";
00982 }
00983 
00984 ////////////////////////////////////////////////////////////////////
00985 //     Function: TexturePool::ns_find_texture
00986 //       Access: Private
00987 //  Description: The nonstatic implementation of find_texture().
00988 ////////////////////////////////////////////////////////////////////
00989 Texture *TexturePool::
00990 ns_find_texture(const string &name) const {
00991   MutexHolder holder(_lock);
00992   GlobPattern glob(name);
00993 
00994   Textures::const_iterator ti;
00995   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
00996     Texture *tex = (*ti).second;
00997     if (glob.matches(tex->get_name())) {
00998       return tex;
00999     }
01000   }
01001 
01002   return NULL;
01003 }
01004 
01005 ////////////////////////////////////////////////////////////////////
01006 //     Function: TexturePool::ns_find_all_textures
01007 //       Access: Private
01008 //  Description: The nonstatic implementation of find_all_textures().
01009 ////////////////////////////////////////////////////////////////////
01010 TextureCollection TexturePool::
01011 ns_find_all_textures(const string &name) const {
01012   MutexHolder holder(_lock);
01013   TextureCollection result;
01014   GlobPattern glob(name);
01015 
01016   Textures::const_iterator ti;
01017   for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
01018     Texture *tex = (*ti).second;
01019     if (glob.matches(tex->get_name())) {
01020       result.add_texture(tex);
01021     }
01022   }
01023 
01024   return result;
01025 }
01026 
01027 ////////////////////////////////////////////////////////////////////
01028 //     Function: TexturePool::ns_make_texture
01029 //       Access: Public
01030 //  Description: Creates a new Texture object of the appropriate type
01031 //               for the indicated filename extension, according to
01032 //               the types that have been registered via
01033 //               register_texture_type().
01034 ////////////////////////////////////////////////////////////////////
01035 PT(Texture) TexturePool::
01036 ns_make_texture(const string &extension) const {
01037   MakeTextureFunc *func = get_texture_type(extension);
01038   if (func != NULL) {
01039     return func();
01040   }
01041 
01042   // We don't know what kind of file type this is; return an ordinary
01043   // Texture in case it's an image file with no extension.
01044   return new Texture;
01045 }
01046 
01047 ////////////////////////////////////////////////////////////////////
01048 //     Function: TexturePool::resolve_filename
01049 //       Access: Private
01050 //  Description: Searches for the indicated filename along the
01051 //               model path.  If the filename was previously
01052 //               searched for, doesn't search again, as an
01053 //               optimization.  Assumes _lock is held.
01054 ////////////////////////////////////////////////////////////////////
01055 void TexturePool::
01056 resolve_filename(Filename &new_filename, const Filename &orig_filename,
01057                  bool read_mipmaps, const LoaderOptions &options) {
01058   if (!_fake_texture_image.empty()) {
01059     new_filename = _fake_texture_image;
01060     return;
01061   }
01062 
01063   RelpathLookup::iterator rpi = _relpath_lookup.find(orig_filename);
01064   if (rpi != _relpath_lookup.end()) {
01065     new_filename = (*rpi).second;
01066     return;
01067   }
01068 
01069   new_filename = orig_filename;
01070   if (read_mipmaps || (options.get_texture_flags() & LoaderOptions::TF_multiview)) {
01071     new_filename.set_pattern(true);
01072   }
01073 
01074   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
01075   vfs->resolve_filename(new_filename, get_model_path());
01076 
01077   _relpath_lookup[orig_filename] = new_filename;
01078 }
01079 
01080 ////////////////////////////////////////////////////////////////////
01081 //     Function: TexturePool::try_load_cache
01082 //       Access: Private
01083 //  Description: Attempts to load the texture from the cache record.
01084 ////////////////////////////////////////////////////////////////////
01085 void TexturePool::
01086 try_load_cache(PT(Texture) &tex, BamCache *cache, const Filename &filename,
01087                PT(BamCacheRecord) &record, bool &compressed_cache_record,
01088                const LoaderOptions &options) {
01089   if (tex == (Texture *)NULL) {
01090     // The texture was not supplied by a texture filter.  See if it
01091     // can be found in the on-disk cache, if it is active.
01092     if ((cache->get_cache_textures() || cache->get_cache_compressed_textures()) && !textures_header_only) {
01093       record = cache->lookup(filename, "txo");
01094       if (record != (BamCacheRecord *)NULL) {
01095         if (record->has_data()) {
01096           tex = DCAST(Texture, record->get_data());
01097           compressed_cache_record = (tex->get_ram_image_compression() != Texture::CM_off);
01098           int x_size = tex->get_orig_file_x_size();
01099           int y_size = tex->get_orig_file_y_size();
01100           tex->adjust_this_size(x_size, y_size, filename.get_basename(), true);
01101 
01102           if (!cache->get_cache_textures() && !compressed_cache_record) {
01103             // We're not supposed to be caching uncompressed textures.
01104             if (gobj_cat.is_debug()) {
01105               gobj_cat.debug()
01106                 << "Not caching uncompressed texture " << *tex << "\n";
01107             }
01108             tex = NULL;
01109             record = NULL;
01110 
01111           } else if (x_size != tex->get_x_size() ||
01112                      y_size != tex->get_y_size()) {
01113             // The cached texture no longer matches our expected size
01114             // (the resizing config variables must have changed).
01115             // We'll have to reload the texture from its original file
01116             // so we can rebuild the cache.
01117             if (gobj_cat.is_debug()) {
01118               gobj_cat.debug()
01119                 << "Cached texture " << *tex << " has size "
01120                 << tex->get_x_size() << " x " << tex->get_y_size()
01121                 << " instead of " << x_size << " x " << y_size
01122                 << "; dropping cache.\n";
01123             }
01124             tex = NULL;
01125 
01126           } else if (!tex->has_compression() && tex->get_ram_image_compression() != Texture::CM_off) {
01127             // This texture shouldn't be compressed, but it is.  Go
01128             // reload it.
01129             if (gobj_cat.is_debug()) {
01130               gobj_cat.debug()
01131                 << "Cached texture " << *tex
01132                 << " is compressed in cache; dropping cache.\n";
01133             }
01134             tex = NULL;
01135 
01136           } else {
01137             gobj_cat.info()
01138               << "Texture " << filename << " found in disk cache.\n";
01139             if ((options.get_texture_flags() & LoaderOptions::TF_preload_simple) &&
01140                 !tex->has_simple_ram_image()) {
01141               tex->generate_simple_ram_image();
01142             }
01143             if (!(options.get_texture_flags() & LoaderOptions::TF_preload)) {
01144               // But drop the RAM until we need it.
01145               tex->clear_ram_image();
01146 
01147             } else {
01148               bool was_compressed = (tex->get_ram_image_compression() != Texture::CM_off);
01149               if (tex->consider_auto_process_ram_image(tex->uses_mipmaps(), true)) {
01150                 bool is_compressed = (tex->get_ram_image_compression() != Texture::CM_off);
01151                 if (!was_compressed && is_compressed &&
01152                     cache->get_cache_compressed_textures()) {
01153                   // We've re-compressed the image after loading it
01154                   // from the cache.  To keep the cache current,
01155                   // rewrite it to the cache now, in its newly
01156                   // compressed form.
01157                   record->set_data(tex, tex);
01158                   cache->store(record);
01159                   compressed_cache_record = true;
01160                 }
01161               }
01162             }
01163             tex->set_keep_ram_image(false);
01164           }
01165         } else {
01166           if (!cache->get_cache_textures()) {
01167             // This texture has no actual record, and therefore no
01168             // compressed record (yet).  And we're not supposed to be
01169             // caching uncompressed textures.
01170             if (gobj_cat.is_debug()) {
01171               gobj_cat.debug()
01172                 << "Not caching uncompressed texture\n";
01173             }
01174             record = NULL;
01175           }
01176         }
01177       }
01178     }
01179   }
01180 }
01181 
01182 ////////////////////////////////////////////////////////////////////
01183 //     Function: TexturePool::report_texture_unreadable
01184 //       Access: Private
01185 //  Description: Prints a suitable error message when a texture could
01186 //               not be loaded.
01187 ////////////////////////////////////////////////////////////////////
01188 void TexturePool::
01189 report_texture_unreadable(const Filename &filename) const {
01190   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
01191   bool has_hash = (filename.get_fullpath().find('#') != string::npos);
01192   if (!has_hash && !vfs->exists(filename)) {
01193     if (filename.is_local()) {
01194       // The file doesn't exist, and it wasn't
01195       // fully-qualified--therefore, it wasn't found along either
01196       // search path.
01197       gobj_cat.error()
01198         << "Unable to find texture \"" << filename << "\""
01199         << " on model-path " << get_model_path() <<"\n";
01200     } else {
01201       // A fully-specified filename is not searched along the path, so
01202       // don't mislead the user with the error message.
01203       gobj_cat.error()
01204         << "Texture \"" << filename << "\" does not exist.\n";
01205     }
01206 
01207   } else {
01208     // The file exists, but it couldn't be read for some reason.
01209     if (!has_hash) {
01210       gobj_cat.error()
01211         << "Texture \"" << filename << "\" exists but cannot be read.\n";
01212     } else {
01213       // If the filename contains a hash, we'll be noncommittal about
01214       // whether it exists or not.
01215       gobj_cat.error()
01216         << "Texture \"" << filename << "\" cannot be read.\n";
01217     }
01218 
01219     // Maybe the filename extension is unknown.
01220     MakeTextureFunc *func = get_texture_type(filename.get_extension());
01221     if (func == (MakeTextureFunc *)NULL) {
01222       gobj_cat.error()
01223         << "Texture extension \"" << filename.get_extension() 
01224         << "\" is unknown.  Supported texture types:\n";
01225       write_texture_types(gobj_cat.error(false), 2);
01226     }
01227   }
01228 }
01229 
01230 ////////////////////////////////////////////////////////////////////
01231 //     Function: TexturePool::pre_load
01232 //       Access: Private
01233 //  Description: Invokes pre_load() on all registered filters until
01234 //               one returns non-NULL; returns NULL if there are no
01235 //               registered filters or if all registered filters
01236 //               returned NULL.
01237 ////////////////////////////////////////////////////////////////////
01238 PT(Texture) TexturePool::
01239 pre_load(const Filename &orig_filename, const Filename &orig_alpha_filename,
01240          int primary_file_num_channels, int alpha_file_channel,
01241          bool read_mipmaps, const LoaderOptions &options) {
01242   PT(Texture) tex;
01243 
01244   MutexHolder holder(_lock);
01245 
01246   FilterRegistry::iterator fi;
01247   for (fi = _filter_registry.begin();
01248        fi != _filter_registry.end();
01249        ++fi) {
01250     tex = (*fi)->pre_load(orig_filename, orig_alpha_filename,
01251                           primary_file_num_channels, alpha_file_channel,
01252                           read_mipmaps, options);
01253     if (tex != (Texture *)NULL) {
01254       return tex;
01255     }
01256   }
01257 
01258   return tex;
01259 }
01260 
01261 ////////////////////////////////////////////////////////////////////
01262 //     Function: TexturePool::post_load
01263 //       Access: Public, Virtual
01264 //  Description: Invokes post_load() on all registered filters.
01265 ////////////////////////////////////////////////////////////////////
01266 PT(Texture) TexturePool::
01267 post_load(Texture *tex) {
01268   PT(Texture) result = tex;
01269 
01270   MutexHolder holder(_lock);
01271 
01272   FilterRegistry::iterator fi;
01273   for (fi = _filter_registry.begin();
01274        fi != _filter_registry.end();
01275        ++fi) {
01276     result = (*fi)->post_load(result);
01277   }
01278 
01279   return result;
01280 }
01281 
01282 
01283 ////////////////////////////////////////////////////////////////////
01284 //     Function: TexturePool::load_filters
01285 //       Access: Private
01286 //  Description: Loads up all of the dll's named by the texture-filter
01287 //               Config.prc variable.
01288 ////////////////////////////////////////////////////////////////////
01289 void TexturePool::
01290 load_filters() {
01291   ConfigVariableList texture_filter
01292     ("texture-filter",
01293      PRC_DESC("Names one or more external libraries that should be loaded for the "
01294               "purposes of performing texture filtering.  This variable may be repeated several "
01295               "times.  As in load-display, the actual library filename is derived by "
01296               "prefixing 'lib' to the specified name."));
01297   
01298   int num_aux = texture_filter.get_num_unique_values();
01299   for (int i = 0; i < num_aux; i++) {
01300     string name = texture_filter.get_unique_value(i);
01301     
01302     Filename dlname = Filename::dso_filename("lib" + name + ".so");
01303     gobj_cat->info()
01304       << "loading texture filter: " << dlname.to_os_specific() << endl;
01305     void *tmp = load_dso(get_plugin_path().get_value(), dlname);
01306     if (tmp == (void *)NULL) {
01307       gobj_cat.info()
01308         << "Unable to load: " << load_dso_error() << endl;
01309     }
01310   }
01311 }
 All Classes Functions Variables Enumerations