Bläddra i källkod

fix fmod threading issues

David Rose 14 år sedan
förälder
incheckning
5b2c80e5f3

+ 8 - 0
panda/src/audiotraits/config_fmodAudio.cxx

@@ -29,6 +29,14 @@ ConfigureFn(config_fmodAudio) {
   init_libFmodAudio();
 }
 
+ConfigVariableInt fmod_audio_preload_threshold
+("fmod-audio-preload-threshold", 1048576,
+ PRC_DESC("Files that are smaller "
+          "than this number of bytes will be preloaded and kept "
+          "resident in memory, while files that are this size or larger "
+          "will be streamed from disk.  Set this to -1 to preload "
+          "every file."));
+
 ////////////////////////////////////////////////////////////////////
 //     Function: init_libFmodAudio
 //  Description: Initializes the library.  This must be called at

+ 2 - 3
panda/src/audiotraits/config_milesAudio.cxx

@@ -46,9 +46,8 @@ ConfigVariableInt miles_audio_expand_mp3_threshold
 
 ConfigVariableInt miles_audio_preload_threshold
 ("miles-audio-preload-threshold", -1,
- PRC_DESC("This is the last Miles fallback size, and should be no smaller "
-          "than both miles-audio-expand-mp3-threshold and "
-          "miles-audio-calc-mp3-threshold.  Files that are smaller "
+ PRC_DESC("This should be no smaller "
+          "than miles-audio-expand-mp3-threshold.  Files that are smaller "
           "than this number of bytes will be preloaded and kept "
           "resident in memory, while files that are this size or larger "
           "will be streamed from disk.  Set this to -1 to preload "

+ 0 - 100
panda/src/audiotraits/fmodAudioManager.cxx

@@ -142,13 +142,6 @@ FmodAudioManager() {
     if (_system_is_valid) {
       result = _system->set3DSettings( _doppler_factor, _distance_factor, _drop_off_factor);
       fmod_audio_errcheck("_system->set3DSettings()", result);
-
-#if (FMOD_VERSION >= 0x00043100) // FMod 4.31.00 changed this API
-      result = _system->setFileSystem(open_callback, close_callback, read_callback, seek_callback, 0, 0, -1);
-#else
-      result = _system->setFileSystem(open_callback, close_callback, read_callback, seek_callback, -1);
-#endif
-      fmod_audio_errcheck("_system->setFileSystem()", result);
     }
   }
 
@@ -833,97 +826,4 @@ get_cache_limit() const {
   return 0;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: FmodAudioManager::open_callback
-//       Access: Private, Static
-//  Description: A hook into Panda's virtual file system.
-////////////////////////////////////////////////////////////////////
-FMOD_RESULT F_CALLBACK FmodAudioManager::
-open_callback(const char *name, int, unsigned int *file_size,
-              void **handle, void **user_data) {
-  if (name == (const char *)NULL || name[0] == '\0') {
-    // An invalid attempt to open an unnamed file.
-    return FMOD_ERR_FILE_NOTFOUND;
-  }
-    
-  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-
-  PT(VirtualFile) file = vfs->get_file(Filename(name));
-  if (file == (VirtualFile *)NULL) {
-    return FMOD_ERR_FILE_NOTFOUND;
-  }
-  istream *str = file->open_read_file(true);
-
-  (*file_size) = file->get_file_size(str);
-  (*handle) = (void *)str;
-  (*user_data) = NULL;
-
-  return FMOD_OK;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: FmodAudioManager::close_callback
-//       Access: Private, Static
-//  Description: A hook into Panda's virtual file system.
-////////////////////////////////////////////////////////////////////
-FMOD_RESULT F_CALLBACK FmodAudioManager::
-close_callback(void *handle, void *user_data) {
-  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
-
-  istream *str = (istream *)handle;
-  vfs->close_read_file(str);
-
-  return FMOD_OK;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: FmodAudioManager::read_callback
-//       Access: Private, Static
-//  Description: A hook into Panda's virtual file system.
-////////////////////////////////////////////////////////////////////
-FMOD_RESULT F_CALLBACK FmodAudioManager::
-read_callback(void *handle, void *buffer, unsigned int size_bytes,
-              unsigned int *bytes_read, void *user_data) {
-  istream *str = (istream *)handle;
-  str->read((char *)buffer, size_bytes);
-  (*bytes_read) = str->gcount();
-
-  // We can't yield here, since this callback is made within a
-  // sub-thread--an OS-level sub-thread spawned by FMod, not a Panda
-  // thread.
-  //thread_consider_yield();
-
-  if (str->eof()) {
-    if ((*bytes_read) == 0) {
-      return FMOD_ERR_FILE_EOF;
-    } else {
-      // Report the EOF next time.
-      return FMOD_OK;
-    }
-  } if (str->fail()) {
-    return FMOD_ERR_FILE_BAD;
-  } else {
-    return FMOD_OK;
-  }
-}
-  
-////////////////////////////////////////////////////////////////////
-//     Function: FmodAudioManager::seek_callback
-//       Access: Private, Static
-//  Description: A hook into Panda's virtual file system.
-////////////////////////////////////////////////////////////////////
-FMOD_RESULT F_CALLBACK FmodAudioManager::
-seek_callback(void *handle, unsigned int pos, void *user_data) {
-  istream *str = (istream *)handle;
-  str->clear();
-  str->seekg(pos);
-
-  if (str->fail() && !str->eof()) {
-    return FMOD_ERR_FILE_COULDNOTSEEK;
-  } else {
-    return FMOD_OK;
-  }
-}
-
-
 #endif //]

+ 0 - 14
panda/src/audiotraits/fmodAudioManager.h

@@ -164,20 +164,6 @@ class EXPCL_FMOD_AUDIO FmodAudioManager : public AudioManager {
   ////////////////////////////////////////////////////////////////////
 
 private:
-  static FMOD_RESULT F_CALLBACK 
-  open_callback(const char *name, int unicode, unsigned int *file_size,
-                void **handle, void **user_data);
-
-  static FMOD_RESULT F_CALLBACK 
-  close_callback(void *handle, void *user_data);
-
-  static FMOD_RESULT F_CALLBACK 
-  read_callback(void *handle, void *buffer, unsigned int size_bytes,
-                unsigned int *bytes_read, void *user_data);
-  
-  static FMOD_RESULT F_CALLBACK 
-  seek_callback(void *handle, unsigned int pos, void *user_data);
-  
   FMOD::DSP *make_dsp(const FilterProperties::FilterConfig &conf);
   void update_dsp_chain(FMOD::DSP *head, FilterProperties *config);
   virtual bool configure_filters(FilterProperties *config);

+ 244 - 48
panda/src/audiotraits/fmodAudioSound.cxx

@@ -25,6 +25,7 @@
 #include "config_audio.h"
 #include "fmodAudioSound.h"
 #include "string_utils.h"
+#include "fileSystemInfo.h"
 
 TypeHandle FmodAudioSound::_type_handle;
 
@@ -71,46 +72,119 @@ FmodAudioSound(AudioManager *manager, Filename file_name, bool positional) {
 
   _channel = 0;
   _file_name = file_name;
-
-  FMOD_CREATESOUNDEXINFO *sound_info = NULL;
+  _file_name.set_binary();
 
   //Get the Speaker Mode [Important for later on.]
   result = _manager->_system->getSpeakerMode( &_speakermode );
   fmod_audio_errcheck("_system->getSpeakerMode()", result);
 
-  // Calculate the approximate uncompressed size of the sound.
-  int size =  file_name.get_file_size();
-  string ext = downcase(file_name.get_extension());
-  if (ext != "wav") size *= 10;
-  
-  int flag = positional ? FMOD_3D : FMOD_2D;
-  int streamflag = (size > 250000) ? FMOD_CREATESTREAM : FMOD_CREATESAMPLE;
-  if (ext == "mid") {
-    streamflag = FMOD_CREATESTREAM;
-    sound_info = &_manager->_midi_info;
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+  PT(VirtualFile) file = vfs->get_file(_file_name);
+  if (file == (VirtualFile *)NULL) {
+    // File not found.  We will display the appropriate error message
+    // below.
+    result = FMOD_ERR_FILE_NOTFOUND;
+
+  } else {
+    bool preload = (fmod_audio_preload_threshold < 0) || (file->get_file_size() < fmod_audio_preload_threshold);
+    int flags = FMOD_SOFTWARE;
+    flags |= positional ? FMOD_3D : FMOD_2D;
+    
+    FMOD_CREATESOUNDEXINFO sound_info;
+    memset(&sound_info, 0, sizeof(sound_info));
+    sound_info.cbsize = sizeof(sound_info);
     
-    if (sound_info->dlsname != NULL) {
-      audio_debug("Using DLS file " << sound_info->dlsname);
+    string ext = downcase(_file_name.get_extension());
+    if (ext == "mid") {
+      // Get the MIDI parameters.
+      memcpy(&sound_info, &_manager->_midi_info, sizeof(sound_info));
+      if (sound_info.dlsname != NULL) {
+	audio_debug("Using DLS file " << sound_info.dlsname);
+      }
     }
-  }
+    
+    const char *name_or_data = _file_name.c_str();
+    
+    pvector<unsigned char> mem_buffer;
+    FileSystemInfo info;
+    if (preload) {
+      // Pre-read the file right now, and pass it in as a memory
+      // buffer.  This avoids threading issues completely, because all
+      // of the reading happens right here.
+      file->read_file(mem_buffer, true);
+      sound_info.length = mem_buffer.size();
+      if (mem_buffer.size() != 0) {
+	name_or_data = (const char *)&mem_buffer[0];
+      }
+      flags |= FMOD_OPENMEMORY;
+      if (fmodAudio_cat.is_debug()) {
+	fmodAudio_cat.debug()
+	  << "Reading " << _file_name << " into memory (" << sound_info.length
+	  << " bytes)\n";
+      }
 
-  result = _manager->_system->createSound( file_name.c_str(), FMOD_SOFTWARE | streamflag | flag , 
-                                           sound_info, &_sound);
-  if (result != FMOD_OK) {
-    audio_error("createSound(" << file_name << "): " << FMOD_ErrorString(result));
+    } else if (file->get_system_info(info)) {
+      // The file exists on disk (or it's part of a multifile that
+      // exists on disk), so we can have FMod read the file directly.
+      // This is also safe, because FMod uses its own I/O operations
+      // that don't involve Panda, so this can safely happen in an
+      // FMod thread.
+      name_or_data = info.get_os_file_name().c_str();
+      sound_info.fileoffset = (unsigned int)info.get_file_start();
+      sound_info.length = (unsigned int)info.get_file_size();
+      flags |= FMOD_CREATESTREAM;
+      if (fmodAudio_cat.is_debug()) {
+	fmodAudio_cat.debug()
+	  << "Streaming " << _file_name << " from disk (" << name_or_data 
+	  << ", " << sound_info.fileoffset << ", " << sound_info.length << ")\n";
+      }
+
+    } else {
+#if defined(HAVE_THREADS) && !defined(SIMPLE_THREADS)
+      // Otherwise, if the Panda threading system is compiled in, we
+      // can assign callbacks to read the file through the VFS.
+      name_or_data = (const char *)file.p();
+      sound_info.length = (unsigned int)info.get_file_size();
+      sound_info.useropen = open_callback;
+      sound_info.userclose = close_callback;
+      sound_info.userread = read_callback;
+      sound_info.userseek = seek_callback;
+      flags |= FMOD_CREATESTREAM;
+      if (fmodAudio_cat.is_debug()) {
+	fmodAudio_cat.debug()
+	  << "Streaming " << _file_name << " from disk using callbacks\n";
+      }
+
+#else  // HAVE_THREADS && !SIMPLE_THREADS
+      // Without threads, we can't safely read this file.
+      name_or_data = "";
 
+      fmodAudio_cat.warning()
+	<< "Cannot stream " << _file_name << "; file is not literally on disk.\n";
+#endif
+    }
+    
+    result = 
+      _manager->_system->createSound(name_or_data, flags, &sound_info, &_sound);
+  }
+    
+  if (result != FMOD_OK) {
+    audio_error("createSound(" << _file_name << "): " << FMOD_ErrorString(result));
+    
     // We couldn't load the sound file.  Create a blank sound record
     // instead.
+    FMOD_CREATESOUNDEXINFO sound_info;
+    memset(&sound_info, 0, sizeof(sound_info));
     char blank_data[100];
-    FMOD_CREATESOUNDEXINFO exinfo;
-    memset(&exinfo, 0, sizeof(exinfo));
     memset(blank_data, 0, sizeof(blank_data));
-    exinfo.cbsize = sizeof(exinfo);
-    exinfo.length = sizeof(blank_data);
-    exinfo.numchannels = 1;
-    exinfo.defaultfrequency = 8000;
-    exinfo.format = FMOD_SOUND_FORMAT_PCM16;
-    result = _manager->_system->createSound( blank_data, FMOD_SOFTWARE | flag | FMOD_OPENMEMORY | FMOD_OPENRAW, &exinfo, &_sound);
+    sound_info.cbsize = sizeof(sound_info);
+    sound_info.length = sizeof(blank_data);
+    sound_info.numchannels = 1;
+    sound_info.defaultfrequency = 8000;
+    sound_info.format = FMOD_SOUND_FORMAT_PCM16;
+    int flags = FMOD_SOFTWARE | FMOD_OPENMEMORY | FMOD_OPENRAW;
+
+    result = _manager->_system->createSound( blank_data, flags, &sound_info, &_sound);
     fmod_audio_errcheck("createSound (blank)", result);
   }
 
@@ -157,27 +231,6 @@ play() {
   start_playing();
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: sound_end_callback
-//       Access: Static
-//  Description: When fmod finishes playing a sound, decrements the
-//               reference count of the associated FmodAudioSound.
-////////////////////////////////////////////////////////////////////
-FMOD_RESULT F_CALLBACK sound_end_callback(FMOD_CHANNEL *  channel, 
-                      FMOD_CHANNEL_CALLBACKTYPE  type, 
-                      void *commanddata1, 
-                      void *commanddata2) {
-  if (type == FMOD_CHANNEL_CALLBACKTYPE_END) {
-    FMOD::Channel *fc = (FMOD::Channel *)channel;
-    void *userdata = NULL;
-    FMOD_RESULT result = fc->getUserData(&userdata);
-    fmod_audio_errcheck("channel->getUserData()", result);
-    FmodAudioSound *fsound = (FmodAudioSound*)userdata;
-    fsound->_self_ref = fsound;
-  }
-  return FMOD_OK;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: FmodAudioSound::stop
 //       Access: public
@@ -884,4 +937,147 @@ get_finished_event() const {
   return _finished_event;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: FmodAudioSound::sound_end_callback
+//       Access: Private, Static
+//  Description: When fmod finishes playing a sound, decrements the
+//               reference count of the associated FmodAudioSound.
+////////////////////////////////////////////////////////////////////
+FMOD_RESULT F_CALLBACK FmodAudioSound::
+sound_end_callback(FMOD_CHANNEL *  channel, 
+		   FMOD_CHANNEL_CALLBACKTYPE  type, 
+		   void *commanddata1, 
+		   void *commanddata2) {
+  // Fortunately, this callback is made synchronously rather than
+  // asynchronously (it is triggered during System::update()), so we
+  // don't have to worry about thread-related issues here.
+  if (type == FMOD_CHANNEL_CALLBACKTYPE_END) {
+    FMOD::Channel *fc = (FMOD::Channel *)channel;
+    void *userdata = NULL;
+    FMOD_RESULT result = fc->getUserData(&userdata);
+    fmod_audio_errcheck("channel->getUserData()", result);
+    FmodAudioSound *fsound = (FmodAudioSound*)userdata;
+    fsound->_self_ref = fsound;
+  }
+  return FMOD_OK;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FmodAudioSound::open_callback
+//       Access: Private, Static
+//  Description: A hook into Panda's virtual file system.
+////////////////////////////////////////////////////////////////////
+FMOD_RESULT F_CALLBACK FmodAudioSound::
+open_callback(const char *name, int, unsigned int *file_size,
+              void **handle, void **user_data) {
+  // We actually pass in the VirtualFile pointer as the "name".
+  VirtualFile *file = (VirtualFile *)name;
+  if (file == (VirtualFile *)NULL) {
+    return FMOD_ERR_FILE_NOTFOUND;
+  }
+  if (fmodAudio_cat.is_spam()) {
+    fmodAudio_cat.spam()
+      << "open_callback(" << *file << ")\n";
+  }
+
+  istream *str = file->open_read_file(true);
+
+  (*file_size) = file->get_file_size(str);
+  (*handle) = (void *)str;
+  (*user_data) = (void *)file;
+
+  // Explicitly ref the VirtualFile since we're storing it in a void
+  // pointer instead of a PT(VirtualFile).
+  file->ref();
+
+  return FMOD_OK;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FmodAudioSound::close_callback
+//       Access: Private, Static
+//  Description: A hook into Panda's virtual file system.
+////////////////////////////////////////////////////////////////////
+FMOD_RESULT F_CALLBACK FmodAudioSound::
+close_callback(void *handle, void *user_data) {
+  VirtualFile *file = (VirtualFile *)user_data;
+  if (fmodAudio_cat.is_spam()) {
+    fmodAudio_cat.spam()
+      << "close_callback(" << *file << ")\n";
+  }
+
+  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
+
+  istream *str = (istream *)handle;
+  vfs->close_read_file(str);
+
+  // Explicitly unref the VirtualFile pointer.
+  unref_delete(file);
+
+  return FMOD_OK;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FmodAudioSound::read_callback
+//       Access: Private, Static
+//  Description: A hook into Panda's virtual file system.
+////////////////////////////////////////////////////////////////////
+FMOD_RESULT F_CALLBACK FmodAudioSound::
+read_callback(void *handle, void *buffer, unsigned int size_bytes,
+              unsigned int *bytes_read, void *user_data) {
+  VirtualFile *file = (VirtualFile *)user_data;
+  if (fmodAudio_cat.is_spam()) {
+    fmodAudio_cat.spam()
+      << "read_callback(" << *file << ", " << size_bytes << ")\n";
+  }
+
+  istream *str = (istream *)handle;
+  str->read((char *)buffer, size_bytes);
+  (*bytes_read) = str->gcount();
+
+  // We can't yield here, since this callback is made within a
+  // sub-thread--an OS-level sub-thread spawned by FMod, not a Panda
+  // thread.  But we will only execute this code in the true-threads
+  // case anyway.
+  //thread_consider_yield();
+
+  if (str->eof()) {
+    if ((*bytes_read) == 0) {
+      return FMOD_ERR_FILE_EOF;
+    } else {
+      // Report the EOF next time.
+      return FMOD_OK;
+    }
+  } if (str->fail()) {
+    return FMOD_ERR_FILE_BAD;
+  } else {
+    return FMOD_OK;
+  }
+}
+  
+////////////////////////////////////////////////////////////////////
+//     Function: FmodAudioSound::seek_callback
+//       Access: Private, Static
+//  Description: A hook into Panda's virtual file system.
+////////////////////////////////////////////////////////////////////
+FMOD_RESULT F_CALLBACK FmodAudioSound::
+seek_callback(void *handle, unsigned int pos, void *user_data) {
+  VirtualFile *file = (VirtualFile *)user_data;
+  if (fmodAudio_cat.is_spam()) {
+    fmodAudio_cat.spam()
+      << "seek_callback(" << *file << ", " << pos << ")\n";
+  }
+
+  istream *str = (istream *)handle;
+  str->clear();
+  str->seekg(pos);
+
+  if (str->fail() && !str->eof()) {
+    return FMOD_ERR_FILE_COULDNOTSEEK;
+  } else {
+    return FMOD_OK;
+  }
+}
+
+
 #endif //]

+ 20 - 4
panda/src/audiotraits/fmodAudioSound.h

@@ -204,10 +204,26 @@ class EXPCL_FMOD_AUDIO FmodAudioSound : public AudioSound {
   // other mismanagement.
   PT(FmodAudioSound) _self_ref;
 
-  friend FMOD_RESULT F_CALLBACK sound_end_callback(FMOD_CHANNEL *  channel, 
-                                                   FMOD_CHANNEL_CALLBACKTYPE  type, 
-                                                   void *commanddata1, 
-                                                   void *commanddata2);
+  static FMOD_RESULT F_CALLBACK
+  sound_end_callback(FMOD_CHANNEL *  channel, 
+		     FMOD_CHANNEL_CALLBACKTYPE  type, 
+		     void *commanddata1, 
+		     void *commanddata2);
+
+  static FMOD_RESULT F_CALLBACK 
+  open_callback(const char *name, int unicode, unsigned int *file_size,
+                void **handle, void **user_data);
+
+  static FMOD_RESULT F_CALLBACK 
+  close_callback(void *handle, void *user_data);
+
+  static FMOD_RESULT F_CALLBACK 
+  read_callback(void *handle, void *buffer, unsigned int size_bytes,
+                unsigned int *bytes_read, void *user_data);
+  
+  static FMOD_RESULT F_CALLBACK 
+  seek_callback(void *handle, unsigned int pos, void *user_data);
+  
 
   ////////////////////////////////////////////////////////////
   //These are needed for Panda's Pointer System. DO NOT ERASE!

+ 3 - 0
panda/src/express/Sources.pp

@@ -23,6 +23,7 @@
     encrypt_string.h \
     error_utils.h \
     export_dtool.h \
+    fileSystemInfo.h fileSystemInfo.I \
     hashGeneratorBase.I hashGeneratorBase.h \
     hashVal.I hashVal.h \
     indirectLess.I indirectLess.h \
@@ -84,6 +85,7 @@
     datagramSink.cxx dcast.cxx \
     encrypt_string.cxx \
     error_utils.cxx \
+    fileSystemInfo.cxx \
     hashGeneratorBase.cxx hashVal.cxx \
     memoryInfo.cxx memoryUsage.cxx memoryUsagePointerCounts.cxx \
     memoryUsagePointers.cxx multifile.cxx \
@@ -140,6 +142,7 @@
     dcast.T dcast.h \
     encrypt_string.h \
     error_utils.h \
+    fileSystemInfo.h fileSystemInfo.I \
     hashGeneratorBase.I hashGeneratorBase.h \
     hashVal.I hashVal.h \
     indirectLess.I indirectLess.h \

+ 1 - 0
panda/src/express/express_composite1.cxx

@@ -10,6 +10,7 @@
 #include "dcast.cxx"
 #include "encrypt_string.cxx"
 #include "error_utils.cxx"
+#include "fileSystemInfo.cxx"
 #include "hashGeneratorBase.cxx"
 #include "hashVal.cxx"
 #include "memoryInfo.cxx"

+ 103 - 0
panda/src/express/fileSystemInfo.I

@@ -0,0 +1,103 @@
+// Filename: fileSystemInfo.I
+// Created by:  drose (20Jun11)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSystemInfo::Default Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE FileSystemInfo::
+FileSystemInfo() :
+  _file_start(0),
+  _file_size(0)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSystemInfo::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE FileSystemInfo::
+FileSystemInfo(const string &os_file_name, streampos file_start, streamsize file_size) :
+  _os_file_name(os_file_name),
+  _file_start(file_start),
+  _file_size(file_size)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSystemInfo::Copy Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE FileSystemInfo::
+FileSystemInfo(const FileSystemInfo &copy) :
+  _os_file_name(copy._os_file_name),
+  _file_start(copy._file_start),
+  _file_size(copy._file_size)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSystemInfo::Copy Assignment Operator
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void FileSystemInfo::
+operator = (const FileSystemInfo &copy) {
+  _os_file_name = copy._os_file_name;
+  _file_start = copy._file_start;
+  _file_size = copy._file_size;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSystemInfo::get_os_file_name
+//       Access: Published
+//  Description: Returns the os-specific filename that may be used to
+//               open this file.
+////////////////////////////////////////////////////////////////////
+INLINE const string &FileSystemInfo::
+get_os_file_name() const {
+  return _os_file_name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSystemInfo::get_file_start
+//       Access: Published
+//  Description: Returns the offset within the file at which this file
+//               data begins.
+////////////////////////////////////////////////////////////////////
+INLINE streampos FileSystemInfo::
+get_file_start() const {
+  return _file_start;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSystemInfo::get_file_size
+//       Access: Published
+//  Description: Returns the number of consecutive bytes, beginning at
+//               get_file_start(), that correspond to this file data.
+////////////////////////////////////////////////////////////////////
+INLINE streamsize FileSystemInfo::
+get_file_size() const {
+  return _file_size;
+}
+
+INLINE ostream &
+operator << (ostream &out, const FileSystemInfo &info) {
+  info.output(out);
+  return out;
+}

+ 26 - 0
panda/src/express/fileSystemInfo.cxx

@@ -0,0 +1,26 @@
+// Filename: fileSystemInfo.cxx
+// Created by:  drose (20Jun11)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "fileSystemInfo.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: FileSystemInfo::output
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void FileSystemInfo::
+output(ostream &out) const {
+  out << "FileSystemInfo(" << _os_file_name << ", " << _file_start
+      << ", " << _file_size << ")";
+}

+ 48 - 0
panda/src/express/fileSystemInfo.h

@@ -0,0 +1,48 @@
+// Filename: fileSystemInfo.h
+// Created by:  drose (20Jun11)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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."
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef FILESYSTEMINFO_H
+#define FILESYSTEMINFO_H
+
+#include "pandabase.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : FileSystemInfo
+// Description : This class is used to return data about an actual
+//               file on disk by VirtualFile::get_system_info().
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDAEXPRESS FileSystemInfo {
+PUBLISHED:
+  INLINE FileSystemInfo();
+  INLINE FileSystemInfo(const string &os_file_name, streampos file_start, streamsize file_size);
+  INLINE FileSystemInfo(const FileSystemInfo &copy);
+  INLINE void operator = (const FileSystemInfo &copy);
+
+  INLINE const string &get_os_file_name() const;
+  INLINE streampos get_file_start() const;
+  INLINE streamsize get_file_size() const;
+
+  void output(ostream &out) const;
+
+private:
+  string _os_file_name;
+  streampos _file_start;
+  streamsize _file_size;
+};
+
+INLINE ostream &operator << (ostream &out, const FileSystemInfo &info);
+
+#include "fileSystemInfo.I"
+
+#endif

+ 1 - 0
panda/src/express/multifile.h

@@ -26,6 +26,7 @@
 #include "referenceCount.h"
 #include "pvector.h"
 #include "openSSLWrapper.h"
+#include "fileSystemInfo.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : Multifile

+ 15 - 0
panda/src/express/virtualFile.cxx

@@ -213,6 +213,21 @@ get_timestamp() const {
   return 0;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFile::get_system_info
+//       Access: Published, Virtual
+//  Description: Populates the FileSystemInfo structure with the data
+//               representing where the file actually resides on disk,
+//               if this is knowable.  Returns true if the file might
+//               reside on disk, and the info is populated, or false
+//               if it does not (or it is not known where the file
+//               resides), in which case the info is meaningless.
+////////////////////////////////////////////////////////////////////
+bool VirtualFile::
+get_system_info(FileSystemInfo &info) {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFile::close_read_file
 //       Access: Public

+ 3 - 0
panda/src/express/virtualFile.h

@@ -18,6 +18,7 @@
 #include "pandabase.h"
 
 #include "filename.h"
+#include "fileSystemInfo.h"
 #include "pointerTo.h"
 #include "typedReferenceCount.h"
 #include "ordered_vector.h"
@@ -60,6 +61,8 @@ PUBLISHED:
   BLOCKING virtual off_t get_file_size() const;
   BLOCKING virtual time_t get_timestamp() const;
 
+  virtual bool get_system_info(FileSystemInfo &info);
+
 public:
   INLINE void set_original_filename(const Filename &filename);
   bool read_file(string &result, bool auto_unwrap) const;

+ 15 - 0
panda/src/express/virtualFileMount.cxx

@@ -140,6 +140,21 @@ close_read_file(istream *stream) const {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMount::get_system_info
+//       Access: Public, Virtual
+//  Description: Populates the FileSystemInfo structure with the data
+//               representing where the file actually resides on disk,
+//               if this is knowable.  Returns true if the file might
+//               reside on disk, and the info is populated, or false
+//               if it does not (or it is not known where the file
+//               resides), in which case the info is meaningless.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMount::
+get_system_info(const Filename &file, FileSystemInfo &info) {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileMount::output
 //       Access: Public, Virtual

+ 1 - 0
panda/src/express/virtualFileMount.h

@@ -58,6 +58,7 @@ public:
   virtual off_t get_file_size(const Filename &file, istream *stream) const=0;
   virtual off_t get_file_size(const Filename &file) const=0;
   virtual time_t get_timestamp(const Filename &file) const=0;
+  virtual bool get_system_info(const Filename &file, FileSystemInfo &info);
 
   virtual bool scan_directory(vector_string &contents, 
                               const Filename &dir) const=0;

+ 32 - 0
panda/src/express/virtualFileMountMultifile.cxx

@@ -171,6 +171,38 @@ get_timestamp(const Filename &file) const {
   return _multifile->get_subfile_timestamp(subfile_index);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountMultifile::get_system_info
+//       Access: Public, Virtual
+//  Description: Populates the FileSystemInfo structure with the data
+//               representing where the file actually resides on disk,
+//               if this is knowable.  Returns true if the file might
+//               reside on disk, and the info is populated, or false
+//               if it might not (or it is not known where the file
+//               resides), in which case the info is meaningless.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMountMultifile::
+get_system_info(const Filename &file, FileSystemInfo &info) {
+  Filename multifile_name = _multifile->get_multifile_name();
+  if (multifile_name.empty()) {
+    return false;
+  }
+  int subfile_index = _multifile->find_subfile(file);
+  if (subfile_index < 0) {
+    return false;
+  }
+  if (_multifile->is_subfile_compressed(subfile_index) ||
+      _multifile->is_subfile_encrypted(subfile_index)) {
+    return false;
+  }
+
+  streampos start = _multifile->get_subfile_internal_start(subfile_index);
+  size_t length = _multifile->get_subfile_internal_length(subfile_index);
+
+  info = FileSystemInfo(multifile_name.to_os_specific(), start, length); 
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileMountMultifile::scan_directory
 //       Access: Public, Virtual

+ 1 - 0
panda/src/express/virtualFileMountMultifile.h

@@ -45,6 +45,7 @@ public:
   virtual off_t get_file_size(const Filename &file, istream *stream) const;
   virtual off_t get_file_size(const Filename &file) const;
   virtual time_t get_timestamp(const Filename &file) const;
+  virtual bool get_system_info(const Filename &file, FileSystemInfo &info);
 
   virtual bool scan_directory(vector_string &contents, 
                               const Filename &dir) const;

+ 17 - 0
panda/src/express/virtualFileMountSystem.cxx

@@ -181,6 +181,23 @@ get_timestamp(const Filename &file) const {
   return pathname.get_timestamp();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileMountSystem::get_system_info
+//       Access: Public, Virtual
+//  Description: Populates the FileSystemInfo structure with the data
+//               representing where the file actually resides on disk,
+//               if this is knowable.  Returns true if the file might
+//               reside on disk, and the info is populated, or false
+//               if it does not (or it is not known where the file
+//               resides), in which case the info is meaningless.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileMountSystem::
+get_system_info(const Filename &file, FileSystemInfo &info) {
+  Filename pathname(_physical_filename, file);
+  info = FileSystemInfo(pathname.to_os_specific(), 0, pathname.get_file_size());
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileMountSystem::scan_directory
 //       Access: Public, Virtual

+ 1 - 0
panda/src/express/virtualFileMountSystem.h

@@ -39,6 +39,7 @@ public:
   virtual off_t get_file_size(const Filename &file, istream *stream) const;
   virtual off_t get_file_size(const Filename &file) const;
   virtual time_t get_timestamp(const Filename &file) const;
+  virtual bool get_system_info(const Filename &file, FileSystemInfo &info);
 
   virtual bool scan_directory(vector_string &contents, 
                               const Filename &dir) const;

+ 15 - 0
panda/src/express/virtualFileSimple.cxx

@@ -160,6 +160,21 @@ get_timestamp() const {
   return _mount->get_timestamp(_local_filename);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VirtualFileSimple::get_system_info
+//       Access: Published, Virtual
+//  Description: Populates the FileSystemInfo structure with the data
+//               representing where the file actually resides on disk,
+//               if this is knowable.  Returns true if the file might
+//               reside on disk, and the info is populated, or false
+//               if it does not (or it is not known where the file
+//               resides), in which case the info is meaningless.
+////////////////////////////////////////////////////////////////////
+bool VirtualFileSimple::
+get_system_info(FileSystemInfo &info) {
+  return _mount->get_system_info(_local_filename, info);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VirtualFileSimple::read_file
 //       Access: Public, Virtual

+ 1 - 0
panda/src/express/virtualFileSimple.h

@@ -46,6 +46,7 @@ PUBLISHED:
   virtual off_t get_file_size(istream *stream) const;
   virtual off_t get_file_size() const;
   virtual time_t get_timestamp() const;
+  virtual bool get_system_info(FileSystemInfo &info);
 
 public:
   virtual bool read_file(pvector<unsigned char> &result, bool auto_unwrap) const;