panda3d/panda/src/gobj/texturePool.cxx

1109 lines
37 KiB
C++

// Filename: texturePool.cxx
// Created by: drose (26Apr00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////
#include "texturePool.h"
#include "config_gobj.h"
#include "config_util.h"
#include "config_express.h"
#include "string_utils.h"
#include "virtualFileSystem.h"
#include "bamCache.h"
#include "bamCacheRecord.h"
#include "pnmFileTypeRegistry.h"
#include "texturePoolFilter.h"
#include "configVariableList.h"
#include "load_dso.h"
#include "mutexHolder.h"
TexturePool *TexturePool::_global_ptr;
////////////////////////////////////////////////////////////////////
// Function: TexturePool::write
// Access: Published, Static
// Description: Lists the contents of the texture pool to the
// indicated output stream.
// For debugging.
////////////////////////////////////////////////////////////////////
void TexturePool::
write(ostream &out) {
get_global_ptr()->ns_list_contents(out);
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::register_texture_type
// Access: Public
// Description: Records a factory function that makes a Texture
// object of the appropriate type for one or more
// particular filename extensions. The string
// extensions may be a string that contains
// space-separated list of extensions, case-insensitive.
////////////////////////////////////////////////////////////////////
void TexturePool::
register_texture_type(MakeTextureFunc *func, const string &extensions) {
MutexHolder holder(_lock);
vector_string words;
extract_words(downcase(extensions), words);
vector_string::const_iterator wi;
for (wi = words.begin(); wi != words.end(); ++wi) {
_type_registry[*wi] = func;
}
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::register_filter
// Access: Public
// Description: Records a TexturePoolFilter object that may operate
// on texture images as they are loaded from disk.
////////////////////////////////////////////////////////////////////
void TexturePool::
register_filter(TexturePoolFilter *filter) {
MutexHolder holder(_lock);
gobj_cat.info()
<< "Registering Texture filter " << *filter << "\n";
_filter_registry.push_back(filter);
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::get_texture_type
// Access: Public
// Description: Returns the factory function to construct a new
// texture of the type appropriate for the indicated
// filename extension, if any, or NULL if the extension
// is not one of the extensions for a texture file.
////////////////////////////////////////////////////////////////////
TexturePool::MakeTextureFunc *TexturePool::
get_texture_type(const string &extension) const {
MutexHolder holder(_lock);
string c = downcase(extension);
TypeRegistry::const_iterator ti;
ti = _type_registry.find(extension);
if (ti != _type_registry.end()) {
return (*ti).second;
}
// Check the PNM type registry.
PNMFileTypeRegistry *pnm_reg = PNMFileTypeRegistry::get_global_ptr();
PNMFileType *type = pnm_reg->get_type_from_extension(extension);
if (type != (PNMFileType *)NULL) {
// This is a known image type; create an ordinary Texture.
((TexturePool *)this)->_type_registry[extension] = Texture::make_texture;
return Texture::make_texture;
}
// This is an unknown texture type.
return NULL;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::make_texture
// Access: Public
// Description: Creates a new Texture object of the appropriate type
// for the indicated filename extension, according to
// the types that have been registered via
// register_texture_type().
////////////////////////////////////////////////////////////////////
PT(Texture) TexturePool::
make_texture(const string &extension) const {
MakeTextureFunc *func = get_texture_type(extension);
if (func != NULL) {
return func();
}
// We don't know what kind of file type this is; return an ordinary
// Texture in case it's an image file with no extension.
return new Texture;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::write_texture_types
// Access: Public
// Description: Outputs a list of the available texture types to the
// indicated output stream. This is mostly the list of
// available image types, with maybe a few additional
// ones for video textures.
////////////////////////////////////////////////////////////////////
void TexturePool::
write_texture_types(ostream &out, int indent_level) const {
MutexHolder holder(_lock);
PNMFileTypeRegistry *pnm_reg = PNMFileTypeRegistry::get_global_ptr();
pnm_reg->write(out, indent_level);
// Also output any of the additional texture types, that aren't
// strictly images (these are typically video textures).
TypeRegistry::const_iterator ti;
for (ti = _type_registry.begin(); ti != _type_registry.end(); ++ti) {
string extension = (*ti).first;
MakeTextureFunc *func = (*ti).second;
if (pnm_reg->get_type_from_extension(extension) == NULL) {
PT(Texture) tex = func();
string name = tex->get_type().get_name();
indent(out, indent_level) << name;
indent(out, max(30 - (int)name.length(), 0))
<< " ." << extension << "\n";
}
}
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::get_global_ptr
// Access: Public, Static
// Description: Initializes and/or returns the global pointer to the
// one TexturePool object in the system.
////////////////////////////////////////////////////////////////////
TexturePool *TexturePool::
get_global_ptr() {
if (_global_ptr == (TexturePool *)NULL) {
_global_ptr = new TexturePool;
// We have to call this here, not in the constructor, so that the
// _global_ptr is safely assigned by the time the filters begin to
// load.
_global_ptr->load_filters();
}
return _global_ptr;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::Constructor
// Access: Private
// Description: The constructor is not intended to be called
// directly; there's only supposed to be one TexturePool
// in the universe and it constructs itself.
////////////////////////////////////////////////////////////////////
TexturePool::
TexturePool() {
ConfigVariableFilename fake_texture_image
("fake-texture-image", "",
PRC_DESC("Set this to enable a speedy-load mode in which you don't care "
"what the world looks like, you just want it to load in minimal "
"time. This causes all texture loads via the TexturePool to use "
"the same texture file, which will presumably only be loaded "
"once."));
_fake_texture_image = fake_texture_image;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_has_texture
// Access: Private
// Description: The nonstatic implementation of has_texture().
////////////////////////////////////////////////////////////////////
bool TexturePool::
ns_has_texture(const Filename &orig_filename) {
MutexHolder holder(_lock);
Filename filename;
resolve_filename(filename, orig_filename);
Textures::const_iterator ti;
ti = _textures.find(filename);
if (ti != _textures.end()) {
// This texture was previously loaded.
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_load_texture
// Access: Private
// Description: The nonstatic implementation of load_texture().
////////////////////////////////////////////////////////////////////
Texture *TexturePool::
ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
bool read_mipmaps, const LoaderOptions &options) {
Filename filename;
{
MutexHolder holder(_lock);
resolve_filename(filename, orig_filename);
Textures::const_iterator ti;
ti = _textures.find(filename);
if (ti != _textures.end()) {
// This texture was previously loaded.
Texture *tex = (*ti).second;
nassertr(!tex->get_fullpath().empty(), tex);
return tex;
}
}
// The texture was not found in the pool.
PT(Texture) tex;
PT(BamCacheRecord) record;
bool store_record = false;
// Can one of our texture filters supply the texture?
tex = pre_load(orig_filename, Filename(), primary_file_num_channels, 0,
read_mipmaps, options);
BamCache *cache = BamCache::get_global_ptr();
bool compressed_cache_record = false;
try_load_cache(tex, cache, filename, record, compressed_cache_record,
options);
if (tex == (Texture *)NULL) {
// The texture was neither in the pool, nor found in the on-disk
// cache; it needs to be loaded from its source image(s).
gobj_cat.info()
<< "Loading texture " << filename << "\n";
tex = make_texture(filename.get_extension());
if (!tex->read(filename, Filename(), primary_file_num_channels, 0,
0, 0, false, read_mipmaps, record, options)) {
// This texture was not found or could not be read.
report_texture_unreadable(filename);
return NULL;
}
if (options.get_texture_flags() & LoaderOptions::TF_preload_simple) {
tex->generate_simple_ram_image();
}
store_record = (record != (BamCacheRecord *)NULL);
}
if (cache->get_cache_compressed_textures() && tex->has_compression()) {
#ifndef HAVE_SQUISH
bool needs_driver_compression = true;
#else
bool needs_driver_compression = driver_compress_textures;
#endif // HAVE_SQUISH
if (needs_driver_compression) {
// We don't want to save the uncompressed version; we'll save the
// compressed version when it becomes available.
store_record = false;
if (!compressed_cache_record) {
tex->set_post_load_store_cache(true);
}
}
} else if (!cache->get_cache_textures()) {
// We don't want to save this texture.
store_record = false;
}
// Set the original filename, before we searched along the path.
nassertr(tex != (Texture *)NULL, NULL);
tex->set_filename(orig_filename);
tex->set_fullpath(filename);
tex->_texture_pool_key = filename;
{
MutexHolder holder(_lock);
// Now look again--someone may have just loaded this texture in
// another thread.
Textures::const_iterator ti;
ti = _textures.find(filename);
if (ti != _textures.end()) {
// This texture was previously loaded.
Texture *tex = (*ti).second;
nassertr(!tex->get_fullpath().empty(), tex);
return tex;
}
_textures[filename] = tex;
}
if (store_record && tex->has_ram_image()) {
// Store the on-disk cache record for next time.
record->set_data(tex, false);
cache->store(record);
}
if (!(options.get_texture_flags() & LoaderOptions::TF_preload)) {
// And now drop the RAM until we need it.
tex->clear_ram_image();
}
nassertr(!tex->get_fullpath().empty(), tex);
// Finally, apply any post-loading texture filters.
tex = post_load(tex);
return tex;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_load_texture
// Access: Private
// Description: The nonstatic implementation of load_texture().
////////////////////////////////////////////////////////////////////
Texture *TexturePool::
ns_load_texture(const Filename &orig_filename,
const Filename &orig_alpha_filename,
int primary_file_num_channels,
int alpha_file_channel,
bool read_mipmaps, const LoaderOptions &options) {
if (!_fake_texture_image.empty()) {
return ns_load_texture(_fake_texture_image, primary_file_num_channels,
read_mipmaps, options);
}
Filename filename;
Filename alpha_filename;
{
MutexHolder holder(_lock);
resolve_filename(filename, orig_filename);
resolve_filename(alpha_filename, orig_alpha_filename);
Textures::const_iterator ti;
ti = _textures.find(filename);
if (ti != _textures.end()) {
// This texture was previously loaded.
Texture *tex = (*ti).second;
nassertr(!tex->get_fullpath().empty(), tex);
return tex;
}
}
PT(Texture) tex;
PT(BamCacheRecord) record;
bool store_record = false;
// Can one of our texture filters supply the texture?
tex = pre_load(orig_filename, alpha_filename, primary_file_num_channels,
alpha_file_channel, read_mipmaps, options);
BamCache *cache = BamCache::get_global_ptr();
bool compressed_cache_record = false;
try_load_cache(tex, cache, filename, record, compressed_cache_record,
options);
if (tex == (Texture *)NULL) {
// The texture was neither in the pool, nor found in the on-disk
// cache; it needs to be loaded from its source image(s).
gobj_cat.info()
<< "Loading texture " << filename << " and alpha component "
<< alpha_filename << endl;
tex = make_texture(filename.get_extension());
if (!tex->read(filename, alpha_filename, primary_file_num_channels,
alpha_file_channel, 0, 0, false, read_mipmaps, NULL,
options)) {
// This texture was not found or could not be read.
report_texture_unreadable(filename);
return NULL;
}
if (options.get_texture_flags() & LoaderOptions::TF_preload_simple) {
tex->generate_simple_ram_image();
}
store_record = (record != (BamCacheRecord *)NULL);
}
if (cache->get_cache_compressed_textures() && tex->has_compression()) {
#ifndef HAVE_SQUISH
bool needs_driver_compression = true;
#else
bool needs_driver_compression = driver_compress_textures;
#endif // HAVE_SQUISH
if (needs_driver_compression) {
// We don't want to save the uncompressed version; we'll save the
// compressed version when it becomes available.
store_record = false;
if (!compressed_cache_record) {
tex->set_post_load_store_cache(true);
}
}
} else if (!cache->get_cache_textures()) {
// We don't want to save this texture.
store_record = false;
}
// Set the original filenames, before we searched along the path.
nassertr(tex != (Texture *)NULL, NULL);
tex->set_filename(orig_filename);
tex->set_fullpath(filename);
tex->set_alpha_filename(orig_alpha_filename);
tex->set_alpha_fullpath(alpha_filename);
tex->_texture_pool_key = filename;
{
MutexHolder holder(_lock);
// Now look again.
Textures::const_iterator ti;
ti = _textures.find(filename);
if (ti != _textures.end()) {
// This texture was previously loaded.
Texture *tex = (*ti).second;
nassertr(!tex->get_fullpath().empty(), tex);
return tex;
}
_textures[filename] = tex;
}
if (store_record && tex->has_ram_image()) {
// Store the on-disk cache record for next time.
record->set_data(tex, false);
cache->store(record);
}
if (!(options.get_texture_flags() & LoaderOptions::TF_preload)) {
// And now drop the RAM until we need it.
tex->clear_ram_image();
}
nassertr(!tex->get_fullpath().empty(), tex);
// Finally, apply any post-loading texture filters.
tex = post_load(tex);
return tex;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_load_3d_texture
// Access: Private
// Description: The nonstatic implementation of load_3d_texture().
////////////////////////////////////////////////////////////////////
Texture *TexturePool::
ns_load_3d_texture(const Filename &filename_pattern,
bool read_mipmaps, const LoaderOptions &options) {
Filename orig_filename(filename_pattern);
orig_filename.set_pattern(true);
Filename filename;
{
MutexHolder holder(_lock);
resolve_filename(filename, orig_filename);
Textures::const_iterator ti;
ti = _textures.find(filename);
if (ti != _textures.end()) {
// This texture was previously loaded.
return (*ti).second;
}
}
PT(Texture) tex;
PT(BamCacheRecord) record;
bool store_record = false;
BamCache *cache = BamCache::get_global_ptr();
bool compressed_cache_record = false;
try_load_cache(tex, cache, filename, record, compressed_cache_record,
options);
if (tex == (Texture *)NULL ||
tex->get_texture_type() != Texture::TT_3d_texture) {
// The texture was neither in the pool, nor found in the on-disk
// cache; it needs to be loaded from its source image(s).
gobj_cat.info()
<< "Loading 3-d texture " << filename << "\n";
tex = make_texture(filename.get_extension());
tex->setup_3d_texture();
if (!tex->read(filename, 0, 0, true, read_mipmaps, options)) {
// This texture was not found or could not be read.
report_texture_unreadable(filename);
return NULL;
}
store_record = (record != (BamCacheRecord *)NULL);
}
if (cache->get_cache_compressed_textures() && tex->has_compression()) {
#ifndef HAVE_SQUISH
bool needs_driver_compression = true;
#else
bool needs_driver_compression = driver_compress_textures;
#endif // HAVE_SQUISH
if (needs_driver_compression) {
// We don't want to save the uncompressed version; we'll save the
// compressed version when it becomes available.
store_record = false;
if (!compressed_cache_record) {
tex->set_post_load_store_cache(true);
}
}
} else if (!cache->get_cache_textures()) {
// We don't want to save this texture.
store_record = false;
}
// Set the original filename, before we searched along the path.
nassertr(tex != (Texture *)NULL, false);
tex->set_filename(filename_pattern);
tex->set_fullpath(filename);
tex->_texture_pool_key = filename;
{
MutexHolder holder(_lock);
// Now look again.
Textures::const_iterator ti;
ti = _textures.find(filename);
if (ti != _textures.end()) {
// This texture was previously loaded.
return (*ti).second;
}
_textures[filename] = tex;
}
if (store_record && tex->has_ram_image()) {
// Store the on-disk cache record for next time.
record->set_data(tex, false);
cache->store(record);
}
nassertr(!tex->get_fullpath().empty(), tex);
return tex;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_load_cube_map
// Access: Private
// Description: The nonstatic implementation of load_cube_map().
////////////////////////////////////////////////////////////////////
Texture *TexturePool::
ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps,
const LoaderOptions &options) {
Filename orig_filename(filename_pattern);
orig_filename.set_pattern(true);
Filename filename;
{
MutexHolder holder(_lock);
resolve_filename(filename, orig_filename);
Textures::const_iterator ti;
ti = _textures.find(filename);
if (ti != _textures.end()) {
// This texture was previously loaded.
return (*ti).second;
}
}
PT(Texture) tex;
PT(BamCacheRecord) record;
bool store_record = false;
BamCache *cache = BamCache::get_global_ptr();
bool compressed_cache_record = false;
try_load_cache(tex, cache, filename, record, compressed_cache_record,
options);
if (tex == (Texture *)NULL ||
tex->get_texture_type() != Texture::TT_cube_map) {
// The texture was neither in the pool, nor found in the on-disk
// cache; it needs to be loaded from its source image(s).
gobj_cat.info()
<< "Loading cube map texture " << filename << "\n";
tex = make_texture(filename.get_extension());
tex->setup_cube_map();
if (!tex->read(filename, 0, 0, true, read_mipmaps, options)) {
// This texture was not found or could not be read.
report_texture_unreadable(filename);
return NULL;
}
store_record = (record != (BamCacheRecord *)NULL);
}
if (cache->get_cache_compressed_textures() && tex->has_compression()) {
#ifndef HAVE_SQUISH
bool needs_driver_compression = true;
#else
bool needs_driver_compression = driver_compress_textures;
#endif // HAVE_SQUISH
if (needs_driver_compression) {
// We don't want to save the uncompressed version; we'll save the
// compressed version when it becomes available.
store_record = false;
if (!compressed_cache_record) {
tex->set_post_load_store_cache(true);
}
}
} else if (!cache->get_cache_textures()) {
// We don't want to save this texture.
store_record = false;
}
// Set the original filename, before we searched along the path.
nassertr(tex != (Texture *)NULL, false);
tex->set_filename(filename_pattern);
tex->set_fullpath(filename);
tex->_texture_pool_key = filename;
{
MutexHolder holder(_lock);
// Now look again.
Textures::const_iterator ti;
ti = _textures.find(filename);
if (ti != _textures.end()) {
// This texture was previously loaded.
return (*ti).second;
}
_textures[filename] = tex;
}
if (store_record && tex->has_ram_image()) {
// Store the on-disk cache record for next time.
record->set_data(tex, false);
cache->store(record);
}
nassertr(!tex->get_fullpath().empty(), tex);
return tex;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_get_normalization_cube_map
// Access: Private
// Description: The nonstatic implementation of get_normalization_cube_map().
////////////////////////////////////////////////////////////////////
Texture *TexturePool::
ns_get_normalization_cube_map(int size) {
MutexHolder holder(_lock);
if (_normalization_cube_map == (Texture *)NULL) {
_normalization_cube_map = new Texture("normalization_cube_map");
}
if (_normalization_cube_map->get_x_size() < size ||
_normalization_cube_map->get_texture_type() != Texture::TT_cube_map) {
_normalization_cube_map->generate_normalization_cube_map(size);
}
return _normalization_cube_map;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_get_alpha_scale_map
// Access: Private
// Description: The nonstatic implementation of get_alpha_scale_map().
////////////////////////////////////////////////////////////////////
Texture *TexturePool::
ns_get_alpha_scale_map() {
MutexHolder holder(_lock);
if (_alpha_scale_map == (Texture *)NULL) {
_alpha_scale_map = new Texture("alpha_scale_map");
_alpha_scale_map->generate_alpha_scale_map();
}
return _alpha_scale_map;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_add_texture
// Access: Private
// Description: The nonstatic implementation of add_texture().
////////////////////////////////////////////////////////////////////
void TexturePool::
ns_add_texture(Texture *tex) {
PT(Texture) keep = tex;
MutexHolder holder(_lock);
if (!tex->_texture_pool_key.empty()) {
ns_release_texture(tex);
}
string filename = tex->get_fullpath();
if (filename.empty()) {
gobj_cat.error() << "Attempt to call add_texture() on an unnamed texture.\n";
}
// We blow away whatever texture was there previously, if any.
tex->_texture_pool_key = filename;
_textures[filename] = tex;
nassertv(!tex->get_fullpath().empty());
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_release_texture
// Access: Private
// Description: The nonstatic implementation of release_texture().
////////////////////////////////////////////////////////////////////
void TexturePool::
ns_release_texture(Texture *tex) {
MutexHolder holder(_lock);
if (!tex->_texture_pool_key.empty()) {
Textures::iterator ti;
ti = _textures.find(tex->_texture_pool_key);
if (ti != _textures.end() && (*ti).second == tex) {
_textures.erase(ti);
}
tex->_texture_pool_key = string();
}
// Blow away the cache of resolved relative filenames.
_relpath_lookup.clear();
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_release_all_textures
// Access: Private
// Description: The nonstatic implementation of release_all_textures().
////////////////////////////////////////////////////////////////////
void TexturePool::
ns_release_all_textures() {
MutexHolder holder(_lock);
Textures::iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
Texture *tex = (*ti).second;
tex->_texture_pool_key = string();
}
_textures.clear();
_normalization_cube_map = NULL;
// Blow away the cache of resolved relative filenames.
_relpath_lookup.clear();
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_garbage_collect
// Access: Private
// Description: The nonstatic implementation of garbage_collect().
////////////////////////////////////////////////////////////////////
int TexturePool::
ns_garbage_collect() {
MutexHolder holder(_lock);
int num_released = 0;
Textures new_set;
Textures::iterator ti;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
Texture *tex = (*ti).second;
if (tex->get_ref_count() == 1) {
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Releasing " << (*ti).first << "\n";
}
++num_released;
tex->_texture_pool_key = string();
} else {
new_set.insert(new_set.end(), *ti);
}
}
_textures.swap(new_set);
if (_normalization_cube_map != (Texture *)NULL &&
_normalization_cube_map->get_ref_count() == 1) {
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Releasing normalization cube map\n";
}
++num_released;
_normalization_cube_map = NULL;
}
return num_released;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::ns_list_contents
// Access: Private
// Description: The nonstatic implementation of list_contents().
////////////////////////////////////////////////////////////////////
void TexturePool::
ns_list_contents(ostream &out) const {
MutexHolder holder(_lock);
int total_size;
int total_ram_size;
Textures::const_iterator ti;
out << "texture pool contents:\n";
total_size = 0;
total_ram_size = 0;
for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
Texture *tex = (*ti).second;
out << (*ti).first << "\n";
out << " (count = " << tex->get_ref_count()
<< ", ram = " << tex->get_ram_image_size()
<< ", size = " << tex->get_ram_page_size()
<< ", w = " << tex->get_x_size()
<< ", h = " << tex->get_y_size()
<< ")\n";
nassertv(tex->_texture_pool_key == (*ti).first);
total_ram_size += tex->get_ram_image_size();
total_size += tex->get_ram_page_size();
}
out << "total number of textures: " << _textures.size() << "\n";
out << "texture pool ram : " << total_ram_size << "\n";
out << "texture pool size: " << total_size << "\n";
out << "texture pool size - texture pool ram: " << total_size - total_ram_size << "\n";
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::resolve_filename
// Access: Private
// Description: Searches for the indicated filename along the
// model path. If the filename was previously
// searched for, doesn't search again, as an
// optimization. Assumes _lock is held.
////////////////////////////////////////////////////////////////////
void TexturePool::
resolve_filename(Filename &new_filename, const Filename &orig_filename) {
if (!_fake_texture_image.empty()) {
new_filename = _fake_texture_image;
return;
}
RelpathLookup::iterator rpi = _relpath_lookup.find(orig_filename);
if (rpi != _relpath_lookup.end()) {
new_filename = (*rpi).second;
return;
}
new_filename = orig_filename;
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
vfs->resolve_filename(new_filename, get_model_path());
_relpath_lookup[orig_filename] = new_filename;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::try_load_cache
// Access: Private
// Description: Attempts to load the texture from the cache record.
////////////////////////////////////////////////////////////////////
void TexturePool::
try_load_cache(PT(Texture) &tex, BamCache *cache, const Filename &filename,
PT(BamCacheRecord) &record, bool &compressed_cache_record,
const LoaderOptions &options) {
if (tex == (Texture *)NULL) {
// The texture was not supplied by a texture filter. See if it
// can be found in the on-disk cache, if it is active.
if ((cache->get_cache_textures() || cache->get_cache_compressed_textures()) && !textures_header_only) {
record = cache->lookup(filename, "txo");
if (record != (BamCacheRecord *)NULL) {
if (record->has_data()) {
tex = DCAST(Texture, record->extract_data());
compressed_cache_record = (tex->get_ram_image_compression() != Texture::CM_off);
int x_size = tex->get_orig_file_x_size();
int y_size = tex->get_orig_file_y_size();
Texture::adjust_size(x_size, y_size, filename.get_basename());
if (!cache->get_cache_textures() && !compressed_cache_record) {
// We're not supposed to be caching uncompressed textures.
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Not caching uncompressed texture " << *tex << "\n";
}
tex = NULL;
record = NULL;
} else if (x_size != tex->get_x_size() ||
y_size != tex->get_y_size()) {
// The cached texture no longer matches our expected size
// (the resizing config variables must have changed).
// We'll have to reload the texture from its original file
// so we can rebuild the cache.
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Cached texture " << *tex << " has size "
<< tex->get_x_size() << " x " << tex->get_y_size()
<< " instead of " << x_size << " x " << y_size
<< "; dropping cache.\n";
}
tex = NULL;
} else if (!tex->has_compression() && tex->get_ram_image_compression() != Texture::CM_off) {
// This texture shouldn't be compressed, but it is. Go
// reload it.
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Cached texture " << *tex
<< " is compressed in cache; dropping cache.\n";
}
tex = NULL;
} else {
gobj_cat.info()
<< "Texture " << filename << " found in disk cache.\n";
if ((options.get_texture_flags() & LoaderOptions::TF_preload_simple) &&
!tex->has_simple_ram_image()) {
tex->generate_simple_ram_image();
}
if (!(options.get_texture_flags() & LoaderOptions::TF_preload)) {
// But drop the RAM until we need it.
tex->clear_ram_image();
} else {
bool was_compressed = (tex->get_ram_image_compression() != Texture::CM_off);
if (tex->consider_auto_process_ram_image(tex->uses_mipmaps(), true)) {
bool is_compressed = (tex->get_ram_image_compression() != Texture::CM_off);
if (!was_compressed && is_compressed &&
cache->get_cache_compressed_textures()) {
// We've re-compressed the image after loading it
// from the cache. To keep the cache current,
// rewrite it to the cache now, in its newly
// compressed form.
record->set_data(tex, false);
cache->store(record);
compressed_cache_record = true;
}
}
}
tex->set_keep_ram_image(false);
}
} else {
if (!cache->get_cache_textures()) {
// This texture has no actual record, and therefore no
// compressed record (yet). And we're not supposed to be
// caching uncompressed textures.
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Not caching uncompressed texture\n";
}
record = NULL;
}
}
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::report_texture_unreadable
// Access: Private
// Description: Prints a suitable error message when a texture could
// not be loaded.
////////////////////////////////////////////////////////////////////
void TexturePool::
report_texture_unreadable(const Filename &filename) const {
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
if (!vfs->exists(filename)) {
if (filename.is_local()) {
// The file doesn't exist, and it wasn't
// fully-qualified--therefore, it wasn't found along either
// search path.
gobj_cat.error()
<< "Unable to find texture \"" << filename << "\""
<< " on model-path " << get_model_path() <<"\n";
} else {
// A fully-specified filename is not searched along the path, so
// don't mislead the user with the error message.
gobj_cat.error()
<< "Texture \"" << filename << "\" does not exist.\n";
}
} else {
// The file exists, but it couldn't be read for some reason.
gobj_cat.error()
<< "Texture \"" << filename << "\" exists but cannot be read.\n";
// Maybe the filename extension is unknown.
MakeTextureFunc *func = get_texture_type(filename.get_extension());
if (func == (MakeTextureFunc *)NULL) {
gobj_cat.error()
<< "Texture extension \"" << filename.get_extension()
<< "\" is unknown. Supported texture types:\n";
write_texture_types(gobj_cat.error(false), 2);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::pre_load
// Access: Private
// Description: Invokes pre_load() on all registered filters until
// one returns non-NULL; returns NULL if there are no
// registered filters or if all registered filters
// returned NULL.
////////////////////////////////////////////////////////////////////
PT(Texture) TexturePool::
pre_load(const Filename &orig_filename, const Filename &orig_alpha_filename,
int primary_file_num_channels, int alpha_file_channel,
bool read_mipmaps, const LoaderOptions &options) {
PT(Texture) tex;
MutexHolder holder(_lock);
FilterRegistry::iterator fi;
for (fi = _filter_registry.begin();
fi != _filter_registry.end();
++fi) {
tex = (*fi)->pre_load(orig_filename, orig_alpha_filename,
primary_file_num_channels, alpha_file_channel,
read_mipmaps, options);
if (tex != (Texture *)NULL) {
return tex;
}
}
return tex;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::post_load
// Access: Public, Virtual
// Description: Invokes post_load() on all registered filters.
////////////////////////////////////////////////////////////////////
PT(Texture) TexturePool::
post_load(Texture *tex) {
PT(Texture) result = tex;
MutexHolder holder(_lock);
FilterRegistry::iterator fi;
for (fi = _filter_registry.begin();
fi != _filter_registry.end();
++fi) {
result = (*fi)->post_load(result);
}
return result;
}
////////////////////////////////////////////////////////////////////
// Function: TexturePool::load_filters
// Access: Private
// Description: Loads up all of the dll's named by the texture-filter
// Config.prc variable.
////////////////////////////////////////////////////////////////////
void TexturePool::
load_filters() {
ConfigVariableList texture_filter
("texture-filter",
PRC_DESC("Names one or more external libraries that should be loaded for the "
"purposes of performing texture filtering. This variable may be repeated several "
"times. As in load-display, the actual library filename is derived by "
"prefixing 'lib' to the specified name."));
int num_aux = texture_filter.get_num_unique_values();
for (int i = 0; i < num_aux; i++) {
string name = texture_filter.get_unique_value(i);
Filename dlname = Filename::dso_filename("lib" + name + ".so");
gobj_cat->info()
<< "loading texture filter: " << dlname.to_os_specific() << endl;
void *tmp = load_dso(get_plugin_path().get_value(), dlname);
if (tmp == (void *)NULL) {
gobj_cat.info()
<< "Unable to load: " << load_dso_error() << endl;
}
}
}