Panda3D
Loading...
Searching...
No Matches
texturePool.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file texturePool.cxx
10 * @author drose
11 * @date 2000-04-26
12 * @author fperazzi, PandaSE
13 * @date 2010-04-29
14 */
15
16#include "texturePool.h"
17#include "config_gobj.h"
18#include "config_putil.h"
19#include "config_express.h"
20#include "string_utils.h"
21#include "virtualFileSystem.h"
22#include "bamCache.h"
23#include "bamCacheRecord.h"
24#include "pnmFileTypeRegistry.h"
25#include "texturePoolFilter.h"
26#include "configVariableList.h"
27#include "load_dso.h"
28#include "mutexHolder.h"
29#include "dcast.h"
30
31using std::istream;
32using std::ostream;
33using std::string;
34
35TexturePool *TexturePool::_global_ptr;
36
37/**
38 * Lists the contents of the texture pool to the indicated output stream. For
39 * debugging.
40 */
42write(ostream &out) {
43 get_global_ptr()->ns_list_contents(out);
44}
45
46/**
47 * Records a factory function that makes a Texture object of the appropriate
48 * type for one or more particular filename extensions. The string extensions
49 * may be a string that contains space-separated list of extensions, case-
50 * insensitive.
51 */
53register_texture_type(MakeTextureFunc *func, const string &extensions) {
54 MutexHolder holder(_lock);
55
56 vector_string words;
57 extract_words(downcase(extensions), words);
58
59 vector_string::const_iterator wi;
60 for (wi = words.begin(); wi != words.end(); ++wi) {
61 _type_registry[*wi] = func;
62 }
63}
64
65/**
66 * Records a TexturePoolFilter object that may operate on texture images as
67 * they are loaded from disk.
68 */
71 MutexHolder holder(_lock);
72
73 gobj_cat.info()
74 << "Registering Texture filter " << *filter << "\n";
75 _filter_registry.push_back(filter);
76}
77
78/**
79 * Returns the factory function to construct a new texture of the type
80 * appropriate for the indicated filename extension, if any, or NULL if the
81 * extension is not one of the extensions for a texture file.
82 */
83TexturePool::MakeTextureFunc *TexturePool::
84get_texture_type(const string &extension) const {
85 MutexHolder holder(_lock);
86
87 string c = downcase(extension);
88 TypeRegistry::const_iterator ti;
89 ti = _type_registry.find(c);
90 if (ti != _type_registry.end()) {
91 return (*ti).second;
92 }
93
94 // Check the PNM type registry.
96 PNMFileType *type = pnm_reg->get_type_from_extension(c);
97 if (type != nullptr || c == "txo" || c == "dds" || c == "ktx") {
98 // This is a known image type; create an ordinary Texture.
99 ((TexturePool *)this)->_type_registry[c] = Texture::make_texture;
100 return Texture::make_texture;
101 }
102
103 // This is an unknown texture type.
104 return nullptr;
105}
106
107/**
108 * Outputs a list of the available texture types to the indicated output
109 * stream. This is mostly the list of available image types, with maybe a few
110 * additional ones for video textures.
111 */
113write_texture_types(ostream &out, int indent_level) const {
114 MutexHolder holder(_lock);
115
116 // These are supported out of the box.
117 indent(out, indent_level) << "Texture Object .txo\n";
118 indent(out, indent_level) << "DirectDraw Surface .dds\n";
119 indent(out, indent_level) << "Khronos Texture .ktx\n";
120
122 pnm_reg->write(out, indent_level);
123
124 // Also output any of the additional texture types, that aren't strictly
125 // images (these are typically video textures).
126 TypeRegistry::const_iterator ti;
127 for (ti = _type_registry.begin(); ti != _type_registry.end(); ++ti) {
128 string extension = (*ti).first;
129 MakeTextureFunc *func = (*ti).second;
130
131 if (pnm_reg->get_type_from_extension(extension) == nullptr) {
132 PT(Texture) tex = func();
133 string name = tex->get_type().get_name();
134 indent(out, indent_level) << name;
135 indent(out, std::max(30 - (int)name.length(), 0))
136 << " ." << extension << "\n";
137 }
138 }
139}
140
141/**
142 * Initializes and/or returns the global pointer to the one TexturePool object
143 * in the system.
144 */
147 if (_global_ptr == nullptr) {
148 _global_ptr = new TexturePool;
149
150 // We have to call this here, not in the constructor, so that the
151 // _global_ptr is safely assigned by the time the filters begin to load.
152 _global_ptr->load_filters();
153 }
154 return _global_ptr;
155}
156
157/**
158 * The constructor is not intended to be called directly; there's only
159 * supposed to be one TexturePool in the universe and it constructs itself.
160 */
161TexturePool::
162TexturePool() {
163 ConfigVariableFilename fake_texture_image
164 ("fake-texture-image", "",
165 PRC_DESC("Set this to enable a speedy-load mode in which you don't care "
166 "what the world looks like, you just want it to load in minimal "
167 "time. This causes all texture loads via the TexturePool to use "
168 "the same texture file, which will presumably only be loaded "
169 "once."));
170 _fake_texture_image = fake_texture_image;
171}
172
173/**
174 * The nonstatic implementation of has_texture().
175 */
176bool TexturePool::
177ns_has_texture(const Filename &orig_filename) {
178 MutexHolder holder(_lock);
179
180 LookupKey key;
181 resolve_filename(key._fullpath, orig_filename, false, LoaderOptions());
182
183 Textures::const_iterator ti;
184 ti = _textures.find(key);
185 if (ti != _textures.end()) {
186 // This texture was previously loaded.
187 return true;
188 }
189
190 // It might still have been loaded with non-standard settings.
191 for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
192 if (ti->first._fullpath == key._fullpath) {
193 return true;
194 }
195 }
196
197 return false;
198}
199
200/**
201 * The nonstatic implementation of get_texture().
202 */
203Texture *TexturePool::
204ns_get_texture(const Filename &orig_filename, int primary_file_num_channels,
205 bool read_mipmaps) {
206 LookupKey key;
207 key._primary_file_num_channels = primary_file_num_channels;
208 {
209 MutexHolder holder(_lock);
210 resolve_filename(key._fullpath, orig_filename, read_mipmaps, LoaderOptions());
211
212 Textures::const_iterator ti;
213 ti = _textures.find(key);
214 if (ti != _textures.end()) {
215 // This texture was previously loaded.
216 Texture *tex = (*ti).second;
217 nassertr(!tex->get_fullpath().empty(), tex);
218 return tex;
219 }
220 }
221
222 return nullptr;
223}
224
225/**
226 * The nonstatic implementation of get_texture().
227 */
228Texture *TexturePool::
229ns_get_texture(const Filename &orig_filename,
230 const Filename &orig_alpha_filename,
231 int primary_file_num_channels,
232 int alpha_file_channel,
233 bool read_mipmaps) {
234 LookupKey key;
235 key._primary_file_num_channels = primary_file_num_channels;
236 key._alpha_file_channel = alpha_file_channel;
237 {
238 MutexHolder holder(_lock);
239 LoaderOptions options;
240 resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
241 resolve_filename(key._alpha_fullpath, orig_alpha_filename, read_mipmaps, options);
242
243 Textures::const_iterator ti;
244 ti = _textures.find(key);
245 if (ti != _textures.end()) {
246 // This texture was previously loaded.
247 Texture *tex = (*ti).second;
248 nassertr(!tex->get_fullpath().empty(), tex);
249 return tex;
250 }
251 }
252
253 return nullptr;
254}
255
256/**
257 * The nonstatic implementation of load_texture().
258 */
259Texture *TexturePool::
260ns_load_texture(const Filename &orig_filename, int primary_file_num_channels,
261 bool read_mipmaps, const LoaderOptions &options) {
262 LookupKey key;
263 key._primary_file_num_channels = primary_file_num_channels;
264 {
265 MutexHolder holder(_lock);
266 resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
267
268 Textures::const_iterator ti;
269 ti = _textures.find(key);
270 if (ti != _textures.end()) {
271 // This texture was previously loaded.
272 Texture *tex = (*ti).second;
273 nassertr(!tex->get_fullpath().empty(), tex);
274 return tex;
275 }
276 }
277
278 // The texture was not found in the pool.
279 PT(Texture) tex;
280 PT(BamCacheRecord) record;
281 bool store_record = false;
282
283 // Can one of our texture filters supply the texture?
284 tex = pre_load(orig_filename, Filename(), primary_file_num_channels, 0,
285 read_mipmaps, options);
286
288 bool compressed_cache_record = false;
289 try_load_cache(tex, cache, key._fullpath, record, compressed_cache_record,
290 options);
291
292 if (tex == nullptr) {
293 // The texture was neither in the pool, nor found in the on-disk cache; it
294 // needs to be loaded from its source image(s).
295 gobj_cat.info()
296 << "Loading texture " << key._fullpath << "\n";
297
298 string ext = downcase(key._fullpath.get_extension());
299 if (ext == "txo" || ext == "bam") {
300 // Assume this is a txo file, which might conceivably contain a movie
301 // file or some other subclass of Texture. In that case, use
302 // make_from_txo() to load it instead of read().
304
305 key._fullpath.set_binary();
306 PT(VirtualFile) file = vfs->get_file(key._fullpath);
307 if (file == nullptr) {
308 // No such file.
309 gobj_cat.error()
310 << "Could not find " << key._fullpath << "\n";
311 return nullptr;
312 }
313
314 if (gobj_cat.is_debug()) {
315 gobj_cat.debug()
316 << "Reading texture object " << key._fullpath << "\n";
317 }
318
319 istream *in = file->open_read_file(true);
320 tex = Texture::make_from_txo(*in, key._fullpath);
321 vfs->close_read_file(in);
322
323 if (tex == nullptr) {
324 return nullptr;
325 }
326 tex->set_fullpath(key._fullpath);
327 tex->clear_alpha_fullpath();
328 tex->set_keep_ram_image(false);
329
330 } else {
331 // Read it the conventional way.
332 tex = ns_make_texture(ext);
333 if (!tex->read(key._fullpath, Filename(), primary_file_num_channels, 0,
334 0, 0, false, read_mipmaps, record, options)) {
335 // This texture was not found or could not be read.
336 report_texture_unreadable(key._fullpath);
337 return nullptr;
338 }
339 }
340
341 if (options.get_texture_flags() & LoaderOptions::TF_preload_simple) {
342 tex->generate_simple_ram_image();
343 }
344
345 store_record = (record != nullptr);
346 }
347
348 if (cache->get_cache_compressed_textures() && tex->has_compression()) {
349#ifndef HAVE_SQUISH
350 bool needs_driver_compression = true;
351#else
352 bool needs_driver_compression = driver_compress_textures;
353#endif // HAVE_SQUISH
354 if (needs_driver_compression) {
355 // We don't want to save the uncompressed version; we'll save the
356 // compressed version when it becomes available.
357 store_record = false;
358 if (!compressed_cache_record) {
359 tex->set_post_load_store_cache(true);
360 }
361 }
362
363 } else if (!cache->get_cache_textures()) {
364 // We don't want to save this texture.
365 store_record = false;
366 }
367
368 // Set the original filename, before we searched along the path.
369 nassertr(tex != nullptr, nullptr);
370 tex->set_filename(orig_filename);
371 tex->set_fullpath(key._fullpath);
372 tex->_texture_pool_key = key._fullpath;
373
374 {
375 MutexHolder holder(_lock);
376
377 // Now look again--someone may have just loaded this texture in another
378 // thread.
379 Textures::const_iterator ti;
380 ti = _textures.find(key);
381 if (ti != _textures.end()) {
382 // This texture was previously loaded.
383 Texture *tex = (*ti).second;
384 nassertr(!tex->get_fullpath().empty(), tex);
385 return tex;
386 }
387
388 _textures[std::move(key)] = tex;
389 }
390
391 if (store_record && tex->is_cacheable()) {
392 // Store the on-disk cache record for next time.
393 record->set_data(tex);
394 cache->store(record);
395 }
396
397 if (!(options.get_texture_flags() & LoaderOptions::TF_preload)) {
398 // And now drop the RAM until we need it.
399 tex->clear_ram_image();
400 }
401
402 nassertr(!tex->get_fullpath().empty(), tex);
403
404 // Finally, apply any post-loading texture filters.
405 tex = post_load(tex);
406
407 return tex;
408}
409
410/**
411 * The nonstatic implementation of load_texture().
412 */
413Texture *TexturePool::
414ns_load_texture(const Filename &orig_filename,
415 const Filename &orig_alpha_filename,
416 int primary_file_num_channels,
417 int alpha_file_channel,
418 bool read_mipmaps, const LoaderOptions &options) {
419 if (!_fake_texture_image.empty()) {
420 return ns_load_texture(_fake_texture_image, primary_file_num_channels,
421 read_mipmaps, options);
422 }
423
424 LookupKey key;
425 key._primary_file_num_channels = primary_file_num_channels;
426 key._alpha_file_channel = alpha_file_channel;
427 {
428 MutexHolder holder(_lock);
429 resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
430 resolve_filename(key._alpha_fullpath, orig_alpha_filename, read_mipmaps, options);
431
432 Textures::const_iterator ti;
433 ti = _textures.find(key);
434 if (ti != _textures.end()) {
435 // This texture was previously loaded.
436 Texture *tex = (*ti).second;
437 nassertr(!tex->get_fullpath().empty(), tex);
438 return tex;
439 }
440 }
441
442 PT(Texture) tex;
443 PT(BamCacheRecord) record;
444 bool store_record = false;
445
446 // Can one of our texture filters supply the texture?
447 tex = pre_load(orig_filename, orig_alpha_filename, primary_file_num_channels,
448 alpha_file_channel, read_mipmaps, options);
449
451 bool compressed_cache_record = false;
452 try_load_cache(tex, cache, key._fullpath, record, compressed_cache_record,
453 options);
454
455 if (tex == nullptr) {
456 // The texture was neither in the pool, nor found in the on-disk cache; it
457 // needs to be loaded from its source image(s).
458 gobj_cat.info()
459 << "Loading texture " << key._fullpath << " and alpha component "
460 << key._alpha_fullpath << std::endl;
461 tex = ns_make_texture(key._fullpath.get_extension());
462 if (!tex->read(key._fullpath, key._alpha_fullpath, primary_file_num_channels,
463 alpha_file_channel, 0, 0, false, read_mipmaps, nullptr,
464 options)) {
465 // This texture was not found or could not be read.
466 report_texture_unreadable(key._fullpath);
467 return nullptr;
468 }
469
470 if (options.get_texture_flags() & LoaderOptions::TF_preload_simple) {
471 tex->generate_simple_ram_image();
472 }
473
474 store_record = (record != nullptr);
475 }
476
477 if (cache->get_cache_compressed_textures() && tex->has_compression()) {
478#ifndef HAVE_SQUISH
479 bool needs_driver_compression = true;
480#else
481 bool needs_driver_compression = driver_compress_textures;
482#endif // HAVE_SQUISH
483 if (needs_driver_compression) {
484 // We don't want to save the uncompressed version; we'll save the
485 // compressed version when it becomes available.
486 store_record = false;
487 if (!compressed_cache_record) {
488 tex->set_post_load_store_cache(true);
489 }
490 }
491
492 } else if (!cache->get_cache_textures()) {
493 // We don't want to save this texture.
494 store_record = false;
495 }
496
497 // Set the original filenames, before we searched along the path.
498 nassertr(tex != nullptr, nullptr);
499 tex->set_filename(orig_filename);
500 tex->set_fullpath(key._fullpath);
501 tex->set_alpha_filename(orig_alpha_filename);
502 tex->set_alpha_fullpath(key._alpha_fullpath);
503 tex->_texture_pool_key = key._fullpath;
504
505 {
506 MutexHolder holder(_lock);
507
508 // Now look again.
509 Textures::const_iterator ti;
510 ti = _textures.find(key);
511 if (ti != _textures.end()) {
512 // This texture was previously loaded.
513 Texture *tex = (*ti).second;
514 nassertr(!tex->get_fullpath().empty(), tex);
515 return tex;
516 }
517
518 _textures[std::move(key)] = tex;
519 }
520
521 if (store_record && tex->is_cacheable()) {
522 // Store the on-disk cache record for next time.
523 record->set_data(tex);
524 cache->store(record);
525 }
526
527 if (!(options.get_texture_flags() & LoaderOptions::TF_preload)) {
528 // And now drop the RAM until we need it.
529 tex->clear_ram_image();
530 }
531
532 nassertr(!tex->get_fullpath().empty(), tex);
533
534 // Finally, apply any post-loading texture filters.
535 tex = post_load(tex);
536
537 return tex;
538}
539
540/**
541 * The nonstatic implementation of load_3d_texture().
542 */
543Texture *TexturePool::
544ns_load_3d_texture(const Filename &filename_pattern,
545 bool read_mipmaps, const LoaderOptions &options) {
546 Filename orig_filename(filename_pattern);
547 orig_filename.set_pattern(true);
548
549 LookupKey key;
550 key._texture_type = Texture::TT_3d_texture;
551 {
552 MutexHolder holder(_lock);
553 resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
554
555 Textures::const_iterator ti;
556 ti = _textures.find(key);
557 if (ti != _textures.end()) {
558 // This texture was previously loaded.
559 Texture *tex = (*ti).second;
560 nassertr(!tex->get_fullpath().empty(), tex);
561 return tex;
562 }
563 }
564
565 PT(Texture) tex;
566 PT(BamCacheRecord) record;
567 bool store_record = false;
568
570 bool compressed_cache_record = false;
571 try_load_cache(tex, cache, key._fullpath, record, compressed_cache_record,
572 options);
573
574 if (tex == nullptr ||
575 tex->get_texture_type() != Texture::TT_3d_texture) {
576 // The texture was neither in the pool, nor found in the on-disk cache; it
577 // needs to be loaded from its source image(s).
578 gobj_cat.info()
579 << "Loading 3-d texture " << key._fullpath << "\n";
580 tex = ns_make_texture(key._fullpath.get_extension());
581 tex->setup_3d_texture();
582 if (!tex->read(key._fullpath, 0, 0, true, read_mipmaps, options)) {
583 // This texture was not found or could not be read.
584 report_texture_unreadable(key._fullpath);
585 return nullptr;
586 }
587 store_record = (record != nullptr);
588 }
589
590 if (cache->get_cache_compressed_textures() && tex->has_compression()) {
591#ifndef HAVE_SQUISH
592 bool needs_driver_compression = true;
593#else
594 bool needs_driver_compression = driver_compress_textures;
595#endif // HAVE_SQUISH
596 if (needs_driver_compression) {
597 // We don't want to save the uncompressed version; we'll save the
598 // compressed version when it becomes available.
599 store_record = false;
600 if (!compressed_cache_record) {
601 tex->set_post_load_store_cache(true);
602 }
603 }
604
605 } else if (!cache->get_cache_textures()) {
606 // We don't want to save this texture.
607 store_record = false;
608 }
609
610 // Set the original filename, before we searched along the path.
611 nassertr(tex != nullptr, nullptr);
612 tex->set_filename(filename_pattern);
613 tex->set_fullpath(key._fullpath);
614 tex->_texture_pool_key = key._fullpath;
615
616 {
617 MutexHolder holder(_lock);
618
619 // Now look again.
620 Textures::const_iterator ti;
621 ti = _textures.find(key);
622 if (ti != _textures.end()) {
623 // This texture was previously loaded.
624 Texture *tex = (*ti).second;
625 nassertr(!tex->get_fullpath().empty(), tex);
626 return tex;
627 }
628
629 _textures[std::move(key)] = tex;
630 }
631
632 if (store_record && tex->is_cacheable()) {
633 // Store the on-disk cache record for next time.
634 record->set_data(tex);
635 cache->store(record);
636 }
637
638 nassertr(!tex->get_fullpath().empty(), tex);
639 return tex;
640}
641
642/**
643 * The nonstatic implementation of load_2d_texture_array().
644 */
645Texture *TexturePool::
646ns_load_2d_texture_array(const Filename &filename_pattern,
647 bool read_mipmaps, const LoaderOptions &options) {
648 Filename orig_filename(filename_pattern);
649 orig_filename.set_pattern(true);
650
651 LookupKey key;
652 key._texture_type = Texture::TT_2d_texture_array;
653 {
654 MutexHolder holder(_lock);
655 resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
656
657 Textures::const_iterator ti;
658 ti = _textures.find(key);
659 if (ti != _textures.end()) {
660 // This texture was previously loaded.
661 Texture *tex = (*ti).second;
662 nassertr(!tex->get_fullpath().empty(), tex);
663 return tex;
664 }
665 }
666
667 PT(Texture) tex;
668 PT(BamCacheRecord) record;
669 bool store_record = false;
670
672 bool compressed_cache_record = false;
673 try_load_cache(tex, cache, key._fullpath, record, compressed_cache_record,
674 options);
675
676 if (tex == nullptr ||
677 tex->get_texture_type() != Texture::TT_2d_texture_array) {
678 // The texture was neither in the pool, nor found in the on-disk cache; it
679 // needs to be loaded from its source image(s).
680 gobj_cat.info()
681 << "Loading 2-d texture array " << key._fullpath << "\n";
682 tex = ns_make_texture(key._fullpath.get_extension());
683 tex->setup_2d_texture_array();
684 if (!tex->read(key._fullpath, 0, 0, true, read_mipmaps, options)) {
685 // This texture was not found or could not be read.
686 report_texture_unreadable(key._fullpath);
687 return nullptr;
688 }
689 store_record = (record != nullptr);
690 }
691
692 if (cache->get_cache_compressed_textures() && tex->has_compression()) {
693#ifndef HAVE_SQUISH
694 bool needs_driver_compression = true;
695#else
696 bool needs_driver_compression = driver_compress_textures;
697#endif // HAVE_SQUISH
698 if (needs_driver_compression) {
699 // We don't want to save the uncompressed version; we'll save the
700 // compressed version when it becomes available.
701 store_record = false;
702 if (!compressed_cache_record) {
703 tex->set_post_load_store_cache(true);
704 }
705 }
706
707 } else if (!cache->get_cache_textures()) {
708 // We don't want to save this texture.
709 store_record = false;
710 }
711
712 // Set the original filename, before we searched along the path.
713 nassertr(tex != nullptr, nullptr);
714 tex->set_filename(filename_pattern);
715 tex->set_fullpath(key._fullpath);
716 tex->_texture_pool_key = key._fullpath;
717
718 {
719 MutexHolder holder(_lock);
720
721 // Now look again.
722 Textures::const_iterator ti;
723 ti = _textures.find(key);
724 if (ti != _textures.end()) {
725 // This texture was previously loaded.
726 Texture *tex = (*ti).second;
727 nassertr(!tex->get_fullpath().empty(), tex);
728 return tex;
729 }
730
731 _textures[std::move(key)] = tex;
732 }
733
734 if (store_record && tex->is_cacheable()) {
735 // Store the on-disk cache record for next time.
736 record->set_data(tex);
737 cache->store(record);
738 }
739
740 nassertr(!tex->get_fullpath().empty(), tex);
741 return tex;
742}
743
744/**
745 * The nonstatic implementation of load_cube_map().
746 */
747Texture *TexturePool::
748ns_load_cube_map(const Filename &filename_pattern, bool read_mipmaps,
749 const LoaderOptions &options) {
750 Filename orig_filename(filename_pattern);
751 orig_filename.set_pattern(true);
752
753 LookupKey key;
754 key._texture_type = Texture::TT_cube_map;
755 {
756 MutexHolder holder(_lock);
757 resolve_filename(key._fullpath, orig_filename, read_mipmaps, options);
758
759 Textures::const_iterator ti;
760 ti = _textures.find(key);
761 if (ti != _textures.end()) {
762 // This texture was previously loaded.
763 Texture *tex = (*ti).second;
764 nassertr(!tex->get_fullpath().empty(), tex);
765 return tex;
766 }
767 }
768
769 PT(Texture) tex;
770 PT(BamCacheRecord) record;
771 bool store_record = false;
772
774 bool compressed_cache_record = false;
775 try_load_cache(tex, cache, key._fullpath, record, compressed_cache_record,
776 options);
777
778 if (tex == nullptr ||
779 tex->get_texture_type() != Texture::TT_cube_map) {
780 // The texture was neither in the pool, nor found in the on-disk cache; it
781 // needs to be loaded from its source image(s).
782 gobj_cat.info()
783 << "Loading cube map texture " << key._fullpath << "\n";
784 tex = ns_make_texture(key._fullpath.get_extension());
785 tex->setup_cube_map();
786 if (!tex->read(key._fullpath, 0, 0, true, read_mipmaps, options)) {
787 // This texture was not found or could not be read.
788 report_texture_unreadable(key._fullpath);
789 return nullptr;
790 }
791 store_record = (record != nullptr);
792 }
793
794 if (cache->get_cache_compressed_textures() && tex->has_compression()) {
795#ifndef HAVE_SQUISH
796 bool needs_driver_compression = true;
797#else
798 bool needs_driver_compression = driver_compress_textures;
799#endif // HAVE_SQUISH
800 if (needs_driver_compression) {
801 // We don't want to save the uncompressed version; we'll save the
802 // compressed version when it becomes available.
803 store_record = false;
804 if (!compressed_cache_record) {
805 tex->set_post_load_store_cache(true);
806 }
807 }
808
809 } else if (!cache->get_cache_textures()) {
810 // We don't want to save this texture.
811 store_record = false;
812 }
813
814 // Set the original filename, before we searched along the path.
815 nassertr(tex != nullptr, nullptr);
816 tex->set_filename(filename_pattern);
817 tex->set_fullpath(key._fullpath);
818 tex->_texture_pool_key = key._fullpath;
819
820 {
821 MutexHolder holder(_lock);
822
823 // Now look again.
824 Textures::const_iterator ti;
825 ti = _textures.find(key);
826 if (ti != _textures.end()) {
827 // This texture was previously loaded.
828 Texture *tex = (*ti).second;
829 nassertr(!tex->get_fullpath().empty(), tex);
830 return tex;
831 }
832
833 _textures[std::move(key)] = tex;
834 }
835
836 if (store_record && tex->is_cacheable()) {
837 // Store the on-disk cache record for next time.
838 record->set_data(tex);
839 cache->store(record);
840 }
841
842 nassertr(!tex->get_fullpath().empty(), tex);
843 return tex;
844}
845
846/**
847 * The nonstatic implementation of get_normalization_cube_map().
848 */
849Texture *TexturePool::
850ns_get_normalization_cube_map(int size) {
851 MutexHolder holder(_lock);
852
853 if (_normalization_cube_map == nullptr) {
854 _normalization_cube_map = new Texture("normalization_cube_map");
855 }
856 if (_normalization_cube_map->get_x_size() < size ||
857 _normalization_cube_map->get_texture_type() != Texture::TT_cube_map) {
858 _normalization_cube_map->generate_normalization_cube_map(size);
859 }
860
861 return _normalization_cube_map;
862}
863
864/**
865 * The nonstatic implementation of get_alpha_scale_map().
866 */
867Texture *TexturePool::
868ns_get_alpha_scale_map() {
869 MutexHolder holder(_lock);
870
871 if (_alpha_scale_map == nullptr) {
872 _alpha_scale_map = new Texture("alpha_scale_map");
873 _alpha_scale_map->generate_alpha_scale_map();
874 }
875
876 return _alpha_scale_map;
877}
878
879/**
880 * The nonstatic implementation of add_texture().
881 */
882void TexturePool::
883ns_add_texture(Texture *tex) {
884 PT(Texture) keep = tex;
885 MutexHolder holder(_lock);
886
887 if (!tex->_texture_pool_key.empty()) {
888 ns_release_texture(tex);
889 }
890
891 Texture::CDReader tex_cdata(tex->_cycler);
892 if (tex_cdata->_fullpath.empty()) {
893 gobj_cat.error() << "Attempt to call add_texture() on an unnamed texture.\n";
894 return;
895 }
896
897 LookupKey key;
898 key._fullpath = tex_cdata->_fullpath;
899 key._alpha_fullpath = tex_cdata->_alpha_fullpath;
900 key._alpha_file_channel = tex_cdata->_alpha_file_channel;
901 key._texture_type = tex_cdata->_texture_type;
902
903 // We blow away whatever texture was there previously, if any.
904 tex->_texture_pool_key = key._fullpath;
905 _textures[key] = tex;
906}
907
908/**
909 * The nonstatic implementation of release_texture().
910 */
911void TexturePool::
912ns_release_texture(Texture *tex) {
913 MutexHolder holder(_lock);
914
915 Textures::iterator ti;
916 for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
917 if (tex == (*ti).second) {
918 _textures.erase(ti);
919 tex->_texture_pool_key = string();
920 break;
921 }
922 }
923
924 // Blow away the cache of resolved relative filenames.
925 _relpath_lookup.clear();
926}
927
928/**
929 * The nonstatic implementation of release_all_textures().
930 */
931void TexturePool::
932ns_release_all_textures() {
933 MutexHolder holder(_lock);
934
935 Textures::iterator ti;
936 for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
937 Texture *tex = (*ti).second;
938 tex->_texture_pool_key = string();
939 }
940
941 _textures.clear();
942 _normalization_cube_map = nullptr;
943
944 // Blow away the cache of resolved relative filenames.
945 _relpath_lookup.clear();
946}
947
948/**
949 * The nonstatic implementation of garbage_collect().
950 */
951int TexturePool::
952ns_garbage_collect() {
953 MutexHolder holder(_lock);
954
955 int num_released = 0;
956 Textures new_set;
957
958 Textures::iterator ti;
959 for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
960 Texture *tex = (*ti).second;
961 if (tex->get_ref_count() == 1) {
962 if (gobj_cat.is_debug()) {
963 gobj_cat.debug()
964 << "Releasing " << (*ti).first._fullpath << "\n";
965 }
966 ++num_released;
967 tex->_texture_pool_key = string();
968 } else {
969 new_set.insert(new_set.end(), *ti);
970 }
971 }
972
973 _textures.swap(new_set);
974
975 if (_normalization_cube_map != nullptr &&
976 _normalization_cube_map->get_ref_count() == 1) {
977 if (gobj_cat.is_debug()) {
978 gobj_cat.debug()
979 << "Releasing normalization cube map\n";
980 }
981 ++num_released;
982 _normalization_cube_map = nullptr;
983 }
984
985 return num_released;
986}
987
988/**
989 * The nonstatic implementation of list_contents().
990 */
991void TexturePool::
992ns_list_contents(ostream &out) const {
993 MutexHolder holder(_lock);
994
995 int total_size;
996 int total_ram_size;
997 Textures::const_iterator ti;
998
999 out << "texture pool contents:\n";
1000
1001 total_size = 0;
1002 total_ram_size = 0;
1003 for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
1004 Texture *tex = (*ti).second;
1005 out << (*ti).first._fullpath << "\n";
1006 out << " (count = " << tex->get_ref_count()
1007 << ", ram = " << tex->get_ram_image_size()
1008 << ", size = " << tex->get_ram_page_size()
1009 << ", w = " << tex->get_x_size()
1010 << ", h = " << tex->get_y_size()
1011 << ")\n";
1012 nassertv(tex->_texture_pool_key == (*ti).first._fullpath);
1013 total_ram_size += tex->get_ram_image_size();
1014 total_size += tex->get_ram_page_size();
1015 }
1016
1017 out << "total number of textures: " << _textures.size() << "\n";
1018 out << "texture pool ram : " << total_ram_size << "\n";
1019 out << "texture pool size: " << total_size << "\n";
1020 out << "texture pool size - texture pool ram: " << total_size - total_ram_size << "\n";
1021}
1022
1023/**
1024 * The nonstatic implementation of find_texture().
1025 */
1026Texture *TexturePool::
1027ns_find_texture(const string &name) const {
1028 MutexHolder holder(_lock);
1029 GlobPattern glob(name);
1030
1031 Textures::const_iterator ti;
1032 for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
1033 Texture *tex = (*ti).second;
1034 if (glob.matches(tex->get_name())) {
1035 return tex;
1036 }
1037 }
1038
1039 return nullptr;
1040}
1041
1042/**
1043 * The nonstatic implementation of find_all_textures().
1044 */
1045TextureCollection TexturePool::
1046ns_find_all_textures(const string &name) const {
1047 MutexHolder holder(_lock);
1048 TextureCollection result;
1049 GlobPattern glob(name);
1050
1051 Textures::const_iterator ti;
1052 for (ti = _textures.begin(); ti != _textures.end(); ++ti) {
1053 Texture *tex = (*ti).second;
1054 if (glob.matches(tex->get_name())) {
1055 result.add_texture(tex);
1056 }
1057 }
1058
1059 return result;
1060}
1061
1062/**
1063 * Creates a new Texture object of the appropriate type for the indicated
1064 * filename extension, according to the types that have been registered via
1065 * register_texture_type().
1066 */
1067PT(Texture) TexturePool::
1068ns_make_texture(const string &extension) const {
1069 MakeTextureFunc *func = get_texture_type(extension);
1070 if (func != nullptr) {
1071 return func();
1072 }
1073
1074 // We don't know what kind of file type this is; return an ordinary Texture
1075 // in case it's an image file with no extension.
1076 return new Texture;
1077}
1078
1079/**
1080 * Searches for the indicated filename along the model path. If the filename
1081 * was previously searched for, doesn't search again, as an optimization.
1082 * Assumes _lock is held.
1083 */
1084void TexturePool::
1085resolve_filename(Filename &new_filename, const Filename &orig_filename,
1086 bool read_mipmaps, const LoaderOptions &options) {
1087 if (!_fake_texture_image.empty()) {
1088 new_filename = _fake_texture_image;
1089 return;
1090 }
1091
1092 RelpathLookup::iterator rpi = _relpath_lookup.find(orig_filename);
1093 if (rpi != _relpath_lookup.end()) {
1094 new_filename = (*rpi).second;
1095 return;
1096 }
1097
1098 new_filename = orig_filename;
1099 if (read_mipmaps || (options.get_texture_flags() & LoaderOptions::TF_multiview)) {
1100 new_filename.set_pattern(true);
1101 }
1102
1104 vfs->resolve_filename(new_filename, get_model_path());
1105
1106 _relpath_lookup[orig_filename] = new_filename;
1107}
1108
1109/**
1110 * Attempts to load the texture from the cache record.
1111 */
1112void TexturePool::
1113try_load_cache(PT(Texture) &tex, BamCache *cache, const Filename &filename,
1114 PT(BamCacheRecord) &record, bool &compressed_cache_record,
1115 const LoaderOptions &options) {
1116 if (tex == nullptr) {
1117 // The texture was not supplied by a texture filter. See if it can be
1118 // found in the on-disk cache, if it is active.
1119 if ((cache->get_cache_textures() || cache->get_cache_compressed_textures()) && !textures_header_only) {
1120
1121 // Call ns_make_texture() on the file extension and create a dummy
1122 // texture object we can call ensure_loaded_type() on. We don't need to
1123 // keep this object around after this call, since we'll be creating a
1124 // new one below. I know this is a bit hacky.
1125 string ext = downcase(filename.get_extension());
1126 PT(Texture) dummy = ns_make_texture(ext);
1127 dummy->ensure_loader_type(filename);
1128 dummy.clear();
1129
1130 record = cache->lookup(filename, "txo");
1131 if (record != nullptr) {
1132 if (record->has_data()) {
1133 tex = DCAST(Texture, record->get_data());
1134 compressed_cache_record = (tex->get_ram_image_compression() != Texture::CM_off);
1135 int x_size = tex->get_orig_file_x_size();
1136 int y_size = tex->get_orig_file_y_size();
1137 tex->adjust_this_size(x_size, y_size, filename.get_basename(), true);
1138
1139 if (!cache->get_cache_textures() && !compressed_cache_record) {
1140 // We're not supposed to be caching uncompressed textures.
1141 if (gobj_cat.is_debug()) {
1142 gobj_cat.debug()
1143 << "Not caching uncompressed texture " << *tex << "\n";
1144 }
1145 tex = nullptr;
1146 record = nullptr;
1147
1148 } else if (x_size != tex->get_x_size() ||
1149 y_size != tex->get_y_size()) {
1150 // The cached texture no longer matches our expected size (the
1151 // resizing config variables must have changed). We'll have to
1152 // reload the texture from its original file so we can rebuild the
1153 // cache.
1154 if (gobj_cat.is_debug()) {
1155 gobj_cat.debug()
1156 << "Cached texture " << *tex << " has size "
1157 << tex->get_x_size() << " x " << tex->get_y_size()
1158 << " instead of " << x_size << " x " << y_size
1159 << "; dropping cache.\n";
1160 }
1161 tex = nullptr;
1162
1163 } else if (!tex->has_compression() && tex->get_ram_image_compression() != Texture::CM_off) {
1164 // This texture shouldn't be compressed, but it is. Go reload it.
1165 if (gobj_cat.is_debug()) {
1166 gobj_cat.debug()
1167 << "Cached texture " << *tex
1168 << " is compressed in cache; dropping cache.\n";
1169 }
1170 tex = nullptr;
1171
1172 } else {
1173 gobj_cat.info()
1174 << "Texture " << filename << " found in disk cache.\n";
1175 if ((options.get_texture_flags() & LoaderOptions::TF_preload_simple) &&
1176 !tex->has_simple_ram_image()) {
1178 }
1179 if (!(options.get_texture_flags() & LoaderOptions::TF_preload)) {
1180 // But drop the RAM until we need it.
1181 tex->clear_ram_image();
1182
1183 } else {
1184 bool was_compressed = (tex->get_ram_image_compression() != Texture::CM_off);
1185 if (tex->consider_auto_process_ram_image(tex->uses_mipmaps(), true)) {
1186 bool is_compressed = (tex->get_ram_image_compression() != Texture::CM_off);
1187 if (!was_compressed && is_compressed &&
1189 // We've re-compressed the image after loading it from the
1190 // cache. To keep the cache current, rewrite it to the
1191 // cache now, in its newly compressed form.
1192 record->set_data(tex);
1193 cache->store(record);
1194 compressed_cache_record = true;
1195 }
1196 }
1197 }
1198 tex->set_keep_ram_image(false);
1199 }
1200 } else {
1201 if (!cache->get_cache_textures()) {
1202 // This texture has no actual record, and therefore no compressed
1203 // record (yet). And we're not supposed to be caching
1204 // uncompressed textures.
1205 if (gobj_cat.is_debug()) {
1206 gobj_cat.debug()
1207 << "Not caching uncompressed texture\n";
1208 }
1209 record = nullptr;
1210 }
1211 }
1212 }
1213 }
1214 }
1215}
1216
1217/**
1218 * Prints a suitable error message when a texture could not be loaded.
1219 */
1220void TexturePool::
1221report_texture_unreadable(const Filename &filename) const {
1223 bool has_hash = (filename.get_fullpath().find('#') != string::npos);
1224 if (!has_hash && !vfs->exists(filename)) {
1225 if (filename.is_local()) {
1226 // The file doesn't exist, and it wasn't fully-qualified--therefore, it
1227 // wasn't found along either search path.
1228 gobj_cat.error()
1229 << "Unable to find texture \"" << filename << "\""
1230 << " on model-path " << get_model_path() <<"\n";
1231 } else {
1232 // A fully-specified filename is not searched along the path, so don't
1233 // mislead the user with the error message.
1234 gobj_cat.error()
1235 << "Texture \"" << filename << "\" does not exist.\n";
1236 }
1237
1238 } else {
1239 // The file exists, but it couldn't be read for some reason.
1240 if (!has_hash) {
1241 gobj_cat.error()
1242 << "Texture \"" << filename << "\" exists but cannot be read.\n";
1243 } else {
1244 // If the filename contains a hash, we'll be noncommittal about whether
1245 // it exists or not.
1246 gobj_cat.error()
1247 << "Texture \"" << filename << "\" cannot be read.\n";
1248 }
1249
1250 // Maybe the filename extension is unknown.
1251 MakeTextureFunc *func = get_texture_type(filename.get_extension());
1252 if (func == nullptr) {
1253 gobj_cat.error()
1254 << "Texture extension \"" << filename.get_extension()
1255 << "\" is unknown. Supported texture types:\n";
1256 write_texture_types(gobj_cat.error(false), 2);
1257 }
1258 }
1259}
1260
1261/**
1262 * Invokes pre_load() on all registered filters until one returns non-NULL;
1263 * returns NULL if there are no registered filters or if all registered
1264 * filters returned NULL.
1265 */
1266PT(Texture) TexturePool::
1267pre_load(const Filename &orig_filename, const Filename &orig_alpha_filename,
1268 int primary_file_num_channels, int alpha_file_channel,
1269 bool read_mipmaps, const LoaderOptions &options) {
1270 PT(Texture) tex;
1271
1272 MutexHolder holder(_lock);
1273
1274 FilterRegistry::iterator fi;
1275 for (fi = _filter_registry.begin();
1276 fi != _filter_registry.end();
1277 ++fi) {
1278 tex = (*fi)->pre_load(orig_filename, orig_alpha_filename,
1279 primary_file_num_channels, alpha_file_channel,
1280 read_mipmaps, options);
1281 if (tex != nullptr) {
1282 return tex;
1283 }
1284 }
1285
1286 return tex;
1287}
1288
1289/**
1290 * Invokes post_load() on all registered filters.
1291 */
1292PT(Texture) TexturePool::
1293post_load(Texture *tex) {
1294 PT(Texture) result = tex;
1295
1296 MutexHolder holder(_lock);
1297
1298 FilterRegistry::iterator fi;
1299 for (fi = _filter_registry.begin();
1300 fi != _filter_registry.end();
1301 ++fi) {
1302 result = (*fi)->post_load(result);
1303 }
1304
1305 return result;
1306}
1307
1308
1309/**
1310 * Loads up all of the dll's named by the texture-filter Config.prc variable.
1311 */
1312void TexturePool::
1313load_filters() {
1314 ConfigVariableList texture_filter
1315 ("texture-filter",
1316 PRC_DESC("Names one or more external libraries that should be loaded for the "
1317 "purposes of performing texture filtering. This variable may be repeated several "
1318 "times. As in load-display, the actual library filename is derived by "
1319 "prefixing 'lib' to the specified name."));
1320
1321 int num_aux = texture_filter.get_num_unique_values();
1322 for (int i = 0; i < num_aux; i++) {
1323 string name = texture_filter.get_unique_value(i);
1324
1325 Filename dlname = Filename::dso_filename("lib" + name + ".so");
1326 gobj_cat->info()
1327 << "loading texture filter: " << dlname.to_os_specific() << std::endl;
1328 void *tmp = load_dso(get_plugin_path().get_value(), dlname);
1329 if (tmp == nullptr) {
1330 gobj_cat.info()
1331 << "Unable to load: " << load_dso_error() << std::endl;
1332 }
1333 }
1334}
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
An instance of this class is written to the front of a Bam or Txo file to make the file a cached inst...
get_data
Returns a pointer to the data stored in the record, or NULL if there is no data.
set_data
Stores a new data object on the record.
This class maintains a cache of Bam and/or Txo objects generated from model files and texture images ...
Definition bamCache.h:42
get_cache_textures
Returns whether texture files (e.g.
Definition bamCache.h:90
bool store(BamCacheRecord *record)
Flushes a cache entry to disk.
Definition bamCache.cxx:194
static BamCache * get_global_ptr()
Returns a pointer to the global BamCache object, which is used automatically by the ModelPool and Tex...
Definition bamCache.I:223
get_cache_compressed_textures
Returns whether compressed texture files will be stored in the cache, as compressed txo files.
Definition bamCache.h:92
This is a convenience class to specialize ConfigVariable as a Filename type.
This class is similar to ConfigVariable, but it reports its value as a list of strings.
This template class calls PipelineCycler::read_unlocked(), and then provides a transparent read-only ...
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
std::string get_basename() const
Returns the basename part of the filename.
Definition filename.I:367
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
std::string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition filename.I:338
std::string get_extension() const
Returns the file extension.
Definition filename.I:400
bool is_local() const
Returns true if the filename is local, e.g.
Definition filename.I:549
void set_pattern(bool pattern)
Sets the flag indicating whether this is a filename pattern.
Definition filename.I:503
This class can be used to test for string matches against standard Unix- shell filename globbing conv...
Definition globPattern.h:32
Specifies parameters that may be passed to the loader.
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition mutexHolder.h:25
This class maintains the set of all known PNMFileTypes in the universe.
static PNMFileTypeRegistry * get_global_ptr()
Returns a pointer to the global PNMFileTypeRegistry object.
void write(std::ostream &out, int indent_level=0) const
Writes a list of supported image file types to the indicated output stream, one per line.
PNMFileType * get_type_from_extension(const std::string &filename) const
Tries to determine what the PNMFileType is likely to be for a particular image file based on its exte...
This is the base class of a family of classes that represent particular image file types that PNMImag...
Definition pnmFileType.h:32
get_ref_count
Returns the current reference count.
Manages a list of Texture objects, as returned by TexturePool::find_all_textures().
void add_texture(Texture *texture)
Adds a new Texture to the collection.
This is an abstract base class, a placeholder for any number of different classes that may wish to im...
This is the preferred interface for loading textures from image files.
Definition texturePool.h:37
static TexturePool * get_global_ptr()
Initializes and/or returns the global pointer to the one TexturePool object in the system.
void register_texture_type(MakeTextureFunc *func, const std::string &extensions)
Records a factory function that makes a Texture object of the appropriate type for one or more partic...
void register_filter(TexturePoolFilter *filter)
Records a TexturePoolFilter object that may operate on texture images as they are loaded from disk.
void write_texture_types(std::ostream &out, int indent_level) const
Outputs a list of the available texture types to the indicated output stream.
static static PT(Texture) make_texture(const std void write(std::ostream &out)
Lists the contents of the texture pool to the indicated output stream.
MakeTextureFunc * get_texture_type(const std::string &extension) const
Returns the factory function to construct a new texture of the type appropriate for the indicated fil...
Represents a texture object, which is typically a single 2-d image but may also represent a 1-d or 3-...
Definition texture.h:72
get_ram_image_size
Returns the total number of bytes used by the in-memory image, across all pages and views,...
Definition texture.h:446
get_ram_page_size
Returns the number of bytes used by the in-memory image per page, or 0 if there is no in-memory image...
Definition texture.h:448
get_ram_image_compression
Returns the compression mode in which the ram image is already stored pre- compressed.
Definition texture.h:472
bool has_compression() const
Returns true if the texture indicates it wants to be compressed, either with CM_on or higher,...
Definition texture.I:1103
get_y_size
Returns the height of the texture image in texels.
Definition texture.h:347
void clear_ram_image()
Discards the current system-RAM image.
Definition texture.I:1440
bool uses_mipmaps() const
Returns true if the minfilter settings on this texture indicate the use of mipmapping,...
Definition texture.I:1128
get_fullpath
Returns the fullpath that has been set.
Definition texture.h:333
has_simple_ram_image
Returns true if the Texture has a "simple" image available in main RAM.
Definition texture.h:520
get_orig_file_x_size
Returns the X size of the original disk image that this Texture was loaded from (if it came from a di...
Definition texture.h:575
get_x_size
Returns the width of the texture image in texels.
Definition texture.h:343
bool adjust_this_size(int &x_size, int &y_size, const std::string &name, bool for_padding) const
Works like adjust_size, but also considers the texture class.
Definition texture.I:2155
void generate_simple_ram_image()
Computes the "simple" ram image by loading the main RAM image, if it is not already available,...
Definition texture.cxx:1324
set_keep_ram_image
Sets the flag that indicates whether this Texture is eligible to have its main RAM copy of the textur...
Definition texture.h:473
get_orig_file_y_size
Returns the Y size of the original disk image that this Texture was loaded from (if it came from a di...
Definition texture.h:576
A hierarchy of directories and files that appears to be one continuous file system,...
bool exists(const Filename &filename) const
Convenience function; returns true if the named file exists in the virtual file system hierarchy.
bool resolve_filename(Filename &filename, const DSearchPath &searchpath, const std::string &default_extension=std::string()) const
Searches the given search path for the filename.
static void close_read_file(std::istream *stream)
Closes a file opened by a previous call to open_read_file().
PointerTo< VirtualFile > get_file(const Filename &filename, bool status_only=false) const
Looks up the file by the indicated name in the file system.
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
The abstract base class for a file or directory within the VirtualFileSystem.
Definition virtualFile.h:35
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
std::ostream & indent(std::ostream &out, int indent_level)
A handy function for doing text formatting.
Definition indent.cxx:20
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
int extract_words(const string &str, vector_string &words)
Divides the string into a number of words according to whitespace.
string downcase(const string &s)
Returns the input string with all uppercase letters converted to lowercase.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.