Ogre high performance thread support - request for comments

来源:互联网 发布:Java使用redis长连接 编辑:程序博客网 时间:2024/06/06 19:55
by sparkprime » Fri Mar 28, 2008 2:46 am
Currently, threading in OGRE means background-loading of resources to avoid blocking on IO in the rendering thread. This results in smoother rendering. While threading is not the only solution to this problem, it works well enough and it's the way OGRE currently works.

If you have few resources, you will want to just load them all into the graphics memory on program start. However if you have so many resources that you can't load them all into video RAM or even host RAM simultaneously, then you will need threading in order to spool resources nicely. If you define OGRE_THREAD_SUPPORT to something other than 0, you will get an OGRE that:
  • depends on boost
  • spawns another thread on startup called Ogre::BackgroundResourceQueue that you can use to load arbitrary resources asynchronously, receiving a callback when loading is complete
  • is quite extensively instrumented with locking code
  • tells the backend (GL, D3D9 etc) that it needs to be threadsafe
The last point incurs a significant CPU performance penalty. Here are some ballpark figures rendering 1024 robot entities in direct3d on a core2duo 6300@1.86ghz with a gig of ram, on a 7900gs, compiling with VS9 with svn boost:

OGRE_THREAD_SUPPORT=0 117fps
OGRE_THREAD_SUPPORT=1 84fps

I have spent some time trying to eliminate this performance penalty. Since we are only trying to avoid blocking on IO, there is really no need to have a threadsafe GL / D3D9. The following patch adds a third threading option OGRE_THREAD_SUPPORT=2. If you define this, you will get an OGRE that:
  • depends on boost
  • is quite extensively instrumented with locking code

Running the same test as above:

OGRE_THREAD_SUPPORT=0 117fps
OGRE_THREAD_SUPPORT=1 84fps
OGRE_THREAD_SUPPORT=2 110fps

It can probably be further optimised, e.g. by using more atomic operations instead of locks.

Code: Select all
diff -ruN ../cvs_fresh/OgreMain/include/OgreConfig.h ./OgreMain/include/OgreConfig.h--- ../cvs_fresh/OgreMain/include/OgreConfig.h  2008-02-15 20:40:50.000000000 +0000+++ ./OgreMain/include/OgreConfig.h     2008-03-27 22:09:33.696458748 +0000@@ -102,6 +102,9 @@ #ifndef OGRE_THREAD_SUPPORT #define OGRE_THREAD_SUPPORT 0 #endif+#if OGRE_THREAD_SUPPORT != 0 && OGRE_THREAD_SUPPORT != 1 && OGRE_THREAD_SUPPORT != 2+#define OGRE_THREAD_SUPPORT 1+#endif /** Disables use of the FreeImage image library for loading images. WARNING: Use only when you want to provide your own image loading code via codecs.diff -ruN ../cvs_fresh/OgreMain/src/OgreResourceBackgroundQueue.cpp ./OgreMain/src/OgreResourceBackgroundQueue.cpp--- ../cvs_fresh/OgreMain/src/OgreResourceBackgroundQueue.cpp   2008-01-18 14:58:05.000000000 +0000+++ ./OgreMain/src/OgreResourceBackgroundQueue.cpp      2008-03-27 22:09:33.696458748 +0000@@ -52,7 +52,7 @@        //------------------------------------------------------------------------        ResourceBackgroundQueue::ResourceBackgroundQueue()                :mNextTicketID(0), mStartThread(true), mThread(0)-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1         , mShuttingDown(false) #endif        {@@ -65,7 +65,7 @@        //------------------------------------------------------------------------        void ResourceBackgroundQueue::initialise(void)        {-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1                if (mStartThread)                {                        {@@ -107,7 +107,7 @@        //------------------------------------------------------------------------        void ResourceBackgroundQueue::shutdown(void)        {-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1                if (mThread)                {                        // Put a shutdown request on the queue@@ -127,7 +127,7 @@        BackgroundProcessTicket ResourceBackgroundQueue::initialiseResourceGroup(                const String& name, ResourceBackgroundQueue::Listener* listener)        {-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1                if (!mThread && mStartThread)                {                        OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,@@ -151,7 +151,7 @@        ResourceBackgroundQueue::initialiseAllResourceGroups(                ResourceBackgroundQueue::Listener* listener)        {-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1                if (!mThread && mStartThread)                {                        OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,@@ -173,7 +173,7 @@        BackgroundProcessTicket ResourceBackgroundQueue::loadResourceGroup(                const String& name, ResourceBackgroundQueue::Listener* listener)        {-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1                if (!mThread && mStartThread)                {                        OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,@@ -200,7 +200,7 @@                const NameValuePairList* loadParams,                ResourceBackgroundQueue::Listener* listener)        {-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1                if (!mThread && mStartThread)                {                        OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,@@ -230,7 +230,7 @@        BackgroundProcessTicket ResourceBackgroundQueue::unload(                const String& resType, const String& name, Listener* listener)        {-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1                if (!mThread && mStartThread)                {                        OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,@@ -257,7 +257,7 @@        BackgroundProcessTicket ResourceBackgroundQueue::unload(                const String& resType, ResourceHandle handle, Listener* listener)        {-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1                if (!mThread && mStartThread)                {                        OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,@@ -284,7 +284,7 @@        BackgroundProcessTicket ResourceBackgroundQueue::unloadResourceGroup(                const String& name, Listener* listener)        {-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1                if (!mThread && mStartThread)                {                        OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR,@@ -314,7 +314,7 @@                return mRequestTicketMap.find(ticket) == mRequestTicketMap.end();        }        //-------------------------------------------------------------------------#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1        BackgroundProcessTicket ResourceBackgroundQueue::addRequest(Request& req)        {                // Lock@@ -441,7 +441,7 @@                        break;                case RT_SHUTDOWN:                        // That's all folks-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1                        mShuttingDown = true;                        Root::getSingleton().getRenderSystem()->unregisterThread(); #endifdiff -ruN ../cvs_fresh/RenderSystems/Direct3D9/src/OgreD3D9RenderWindow.cpp ./RenderSystems/Direct3D9/src/OgreD3D9RenderWindow.cpp--- ../cvs_fresh/RenderSystems/Direct3D9/src/OgreD3D9RenderWindow.cpp   2007-12-08 18:37:28.000000000 +0000+++ ./RenderSystems/Direct3D9/src/OgreD3D9RenderWindow.cpp      2008-03-27 22:09:33.696458748 +0000@@ -542,7 +542,7 @@                                if (opti != options.end() && opti->second.currentValue == "Consistent")                                        extraFlags |= D3DCREATE_FPU_PRESERVE;-#if OGRE_THREAD_SUPPORT+#if OGRE_THREAD_SUPPORT == 1                                extraFlags |= D3DCREATE_MULTITHREADED; #endif                                // Set default settings (use the one Ogre discovered as a default)


All this does is to disable the background thread, which on GL will automatically make the RenderSystem sequential, whereas on D3D9 we also need to toggle a bit to ensure the RenderSystem is sequential. In all cases, the behavour when OGRE_THREAD_SUPPORT==1 is the same as before.

This is not much use by itself. You can make a new thread and poke various bits of ogre, but if your thread ventures into the RenderSystem then your face will probably explode and your house burn down. This means you can't call load() on any interesting resources in your background thread because this will interact with the RenderSystem in order to put your resource in the video RAM, So although load() is the function that blocks on IO, you can't call it in a background thread. A tricky situation.

My solution to this is to factor out the IO from Resource::load() and put it into a new function Resource::prepare(). The idea is that if you *want* to, you can call prepare() before load(). If you don't do this, it will happen implicitly inside the call to load(), so this is fully backwards-compatible. Sometimes I was able to pull more than just the IO from the load() function into prepare(), so long (and this is crucial) that any RenderSystem stuff is left in the load() function. If doing more work in prepare() is possible, it is slightly beneficial because it enables a bit of parallelism.

The following patch rehashes the resource loading system quite extensively. I will talk through it:


Code: Select all
diff -ruN ../cvs_fresh/OgreMain/include/OgreResourceManager.h ./OgreMain/include/OgreResourceManager.h--- ../cvs_fresh/OgreMain/include/OgreResourceManager.h   2008-02-11 11:10:37.000000000 +0000+++ ./OgreMain/include/OgreResourceManager.h   2008-03-27 22:14:25.661517252 +0000@@ -309,6 +309,23 @@       */       virtual void _notifyResourceUnloaded(Resource* res); +      /** Generic prepare method, used to create a Resource specific to this +         ResourceManager without using one of the SPAM 'prepare' methods+         (containing per-Resource-type parameters).+      @param name The name of the Resource+      @param group The resource group to which this resource will belong+      @param isManual Is the resource to be manually loaded? If so, you should+         provide a value for the loader parameter+      @param loader The manual loader which is to perform the required actions+         when this resource is loaded; only applicable when you specify true+         for the previous parameter+        @param loadParams Optional pointer to a list of name/value pairs +            containing loading parameters for this type of resource.+      */+      virtual ResourcePtr prepare(const String& name, +            const String& group, bool isManual = false, +         ManualResourceLoader* loader = 0, const NameValuePairList* loadParams = 0);+       /** Generic load method, used to create a Resource specific to this           ResourceManager without using one of the SPAM 'load' methods          (containing per-Resource-type parameters).@@ -369,6 +386,12 @@       /** Gets a string identifying the type of resource this manager handles. */       const String& getResourceType(void) const { return mResourceType; } +        /** Sets whether this manager and its resources habitually produce log output */+        virtual void setVerbose(bool v) { mVerbose = v; }++        /** Gets whether this manager and its resources habitually produce log output */+        virtual bool getVerbose(void) { return mVerbose; }+     protected:          /** Allocates the next handle. */@@ -417,6 +440,8 @@         size_t mMemoryBudget; // In bytes         size_t mMemoryUsage; // In bytes +        bool mVerbose;+       // IMPORTANT - all subclasses must populate the fields below        /// Patterns to use to look for scripts if supported (e.g. *.overlay)diff -ruN ../cvs_fresh/OgreMain/src/OgreResourceManager.cpp ./OgreMain/src/OgreResourceManager.cpp--- ../cvs_fresh/OgreMain/src/OgreResourceManager.cpp   2008-03-02 20:35:07.000000000 +0000+++ ./OgreMain/src/OgreResourceManager.cpp   2008-03-27 22:14:35.160380515 +0000@@ -40,7 +40,7 @@      //-----------------------------------------------------------------------     ResourceManager::ResourceManager()-      : mNextHandle(1), mMemoryUsage(0), mLoadOrder(0)+      : mNextHandle(1), mMemoryUsage(0), mLoadOrder(0), mVerbose(true)     {         // Init memory limit & usage         mMemoryBudget = std::numeric_limits<unsigned long>::max();@@ -87,18 +87,24 @@       return ResourceCreateOrRetrieveResult(res, created);    }     //-----------------------------------------------------------------------+    ResourcePtr ResourceManager::prepare(const String& name, +        const String& group, bool isManual, ManualResourceLoader* loader, +        const NameValuePairList* loadParams)+    {+        ResourcePtr r = createOrRetrieve(name,group,isManual,loader,loadParams).first;+      // ensure prepared+        r->prepare();+        return r;+    }+    //-----------------------------------------------------------------------     ResourcePtr ResourceManager::load(const String& name,          const String& group, bool isManual, ManualResourceLoader* loader,          const NameValuePairList* loadParams)     {-        ResourcePtr ret = getByName(name);-        if (ret.isNull())-        {-            ret = create(name, group, isManual, loader, loadParams);-        }+        ResourcePtr r = createOrRetrieve(name,group,isManual,loader,loadParams).first;       // ensure loaded-        ret->load();-        return ret;+        r->load();+        return r;     }     //-----------------------------------------------------------------------     void ResourceManager::addImpl( ResourcePtr& res )


A few boring things going on here. I added a prepare() function that you can call just like load(). I also added a verbose feature so that apps can control the messages relating to texture and mesh loading, since that got quite annoying. It defaults to true so you get the messages if you don't turn them off explicitly. I refactored the load() funtion a bit, to avoid duplication.

Now I will go into the details of the resource loading code. I have abstracted the mLoadState variable into an "AtomicScalar<LoadState>" template. This patch creates the file OgreMain/include/OgreAtomicVar.h. This provides an interface that is thread-safe to the extent we need. Note that I provided an implementation using atomic operations for a recent version of GCC. It would be possible to do similar to other platforms. This eliminates some of the locking overhead on supported platforms, while the resource system is oblivious. This class could also be used, probably with a few extensions, in the SharedPtr implementation, to eliminate locking code there, too. I have no figures on speed improvements when doing this, but it seems like a good idea. It is important to use the basic locked version when OGRE_THREAD_SUPPORT==0 because then the locks won't be compiled in, and it will be a basic really-fast sequential implementation. AtomicVar isn't actually used anywhere. I was hoping to have an implementation whereby atomic ops were only used on appropriate data (i.e. ints, enums, etc) but I couldn't come up with a good way of doing this in c++. The correct thing to do at current is to use AtomicVar<BigDataStructure> but AtomicScalar<T> where T is sig_atomic_t, int, LoadingState, etc.

Code: Select all
diff -ruN ../cvs_fresh/OgreMain/include/OgreAtomicVar.h ./OgreMain/include/OgreAtomicVar.h--- ../cvs_fresh/OgreMain/include/OgreAtomicVar.h   1970-01-01 01:00:00.000000000 +0100+++ ./OgreMain/include/OgreAtomicVar.h   2008-03-27 23:04:21.802997619 +0000@@ -0,0 +1,222 @@+/*+-----------------------------------------------------------------------------+This source file is part of OGRE+    (Object-oriented Graphics Rendering Engine)+For the latest info, see http://www.ogre3d.org/++Copyright (c) 2000-2006 Torus Knot Software Ltd+Also see acknowledgements in Readme.html++This program is free software; you can redistribute it and/or modify it under+the terms of the GNU Lesser General Public License as published by the Free Software+Foundation; either version 2 of the License, or (at your option) any later+version.++This program is distributed in the hope that it will be useful, but WITHOUT+ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS+FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.++You should have received a copy of the GNU Lesser General Public License along with+this program; if not, write to the Free Software Foundation, Inc., 59 Temple+Place - Suite 330, Boston, MA 02111-1307, USA, or go to+http://www.gnu.org/copyleft/lesser.txt.++You may alternatively use this source under the terms of a specific version of+the OGRE Unrestricted License provided you have obtained such a license from+Torus Knot Software Ltd.+-----------------------------------------------------------------------------+*/+#ifndef _AtomicVar_H__+#define _AtomicVar_H__++#include <signal.h>++namespace Ogre {++    template <class T> class AtomicVar {++        public:++        AtomicVar (const T &initial)+            : mField(initial)+        {   }++        AtomicVar (const AtomicVar<T> &cousin)+            : mField(cousin.mField)+        {   }++        AtomicVar ()+        {   }++        void operator= (const AtomicVar<T> &cousin)+        {+            OGRE_LOCK_AUTO_MUTEX+            mField = cousin.mField;+        }++        T get (void) const+        {+            // no lock required here+            // since get will not interfere with set or cas+            // we may get a stale value, but this is ok+            return mField;+        }++        void set (const T &v)+        {+            OGRE_LOCK_AUTO_MUTEX+            mField = v;+        }++        bool cas (const T &old, const T &nu)+        {+            OGRE_LOCK_AUTO_MUTEX+            if (mField != old) return false;+            mField = nu;+            return true;+        }++        void operator++ (void)+        {+            OGRE_LOCK_AUTO_MUTEX+            mField++;+        }++        void operator-- (void)+        {+            OGRE_LOCK_AUTO_MUTEX+            mField--;+        }++        protected:++        OGRE_AUTO_MUTEX++        volatile T mField;++    };++    #if OGRE_COMPILER == OGRE_COMPILER_GNUC && OGRE_COMP_VER >= 412 && OGRE_THREAD_SUPPORT++    template<class T> class AtomicScalar+    {++        public:++        AtomicScalar (const T &initial)+            : mField(initial)+        {   }++        AtomicScalar (const AtomicScalar<T> &cousin)+            : mField(cousin.mField)+        {   }++        AtomicScalar () +        {   }++        void operator= (const AtomicScalar<T> &cousin)+        {+            mField = cousin.mField;+        }++        T get (void) const+        {+            return mField;+        }++        void set (const T &v)+        {+            mField = v; +        }   ++        bool cas (const T &old, const T &nu)+        {+            return __sync_bool_compare_and_swap (&mField, old, nu);+        }+            +        void operator++ (void)+        {+            __sync_fetch_and_add (&mField, 1);+        }+            +        void operator-- (void)+        {+            __sync_fetch_and_add (&mField, -1);+        }+++        volatile T mField;++    };++    #else++    template <class T> class AtomicScalar {++        public:++        AtomicScalar (const T &initial)+            : mField(initial)+        {   }++        AtomicScalar (const AtomicScalar<T> &cousin)+            : mField(cousin.mField)+        {   }++        AtomicScalar ()+        {   }++        void operator= (const AtomicScalar<T> &cousin)+        {+            OGRE_LOCK_AUTO_MUTEX+            mField = cousin.mField;+        }++        T get (void) const+        {+            // no lock required here+            // since get will not interfere with set or cas+            // we may get a stale value, but this is ok+            return mField;+        }++        void set (const T &v)+        {+            OGRE_LOCK_AUTO_MUTEX+            mField = v;+        }++        bool cas (const T &old, const T &nu)+        {+            OGRE_LOCK_AUTO_MUTEX+            if (mField != old) return false;+            mField = nu;+            return true;+        }++        void operator++ (void)+        {+            OGRE_LOCK_AUTO_MUTEX+            mField++;+        }++        void operator-- (void)+        {+            OGRE_LOCK_AUTO_MUTEX+            mField--;+        }++        protected:++        OGRE_AUTO_MUTEX++        volatile T mField;++    };++    #endif++}++#endif+


The following changes to Resource.h add more states for prepared and preparing. Note also the use of AtomicScalar<LoadingState>. The code is a lot simpler when refactored to use cas (compare and swap) instead of taking a mutex all the time. Subclasses don't have to implement prepareImpl and unprepareImpl. So far I have only implemented these for GLTexture, Material, and Mesh.

Code: Select all
diff -ruN ../cvs_fresh/OgreMain/include/OgreResource.h ./OgreMain/include/OgreResource.h--- ../cvs_fresh/OgreMain/include/OgreResource.h   2008-02-11 11:10:37.000000000 +0000+++ ./OgreMain/include/OgreResource.h   2008-03-27 23:12:15.246458995 +0000@@ -33,6 +33,7 @@ #include "OgreString.h" #include "OgreSharedPtr.h" #include "OgreStringInterface.h"+#include "OgreAtomicVar.h"  namespace Ogre { @@ -95,14 +96,18 @@       /// Enum identifying the loading state of the resource       enum LoadingState       {-         /// Not loaded-         LOADSTATE_UNLOADED,-         /// Loading is in progress-         LOADSTATE_LOADING,-         /// Fully loaded-         LOADSTATE_LOADED,-         /// Currently unloading-         LOADSTATE_UNLOADING+            /// Not loaded+            LOADSTATE_UNLOADED,+            /// Loading is in progress+            LOADSTATE_LOADING,+            /// Fully loaded+            LOADSTATE_LOADED,+            /// Currently unloading+            LOADSTATE_UNLOADING,+            /// Fully prepared+            LOADSTATE_PREPARED,+            /// Preparing is in progress+            LOADSTATE_PREPARING       };     protected:       /// Creator@@ -114,11 +119,9 @@       /// Numeric handle for more efficient look up than name         ResourceHandle mHandle;       /// Is the resource currently loaded?-        volatile LoadingState mLoadingState;+        AtomicScalar<LoadingState> mLoadingState;       /// Is this resource going to be background loaded? Only applicable for multithreaded       volatile bool mIsBackgroundLoaded;-      /// Mutex to cover the status of loading-      OGRE_MUTEX(mLoadingStatusMutex)       /// The size of the resource in bytes         size_t mSize;       /// Is this file manually loaded?@@ -167,6 +170,14 @@       */       virtual void postUnloadImpl(void) {} +        /** Internal implementation of the meat of the 'prepare' action. +        */+        virtual void prepareImpl(void) {}+        /** Internal function for undoing the 'prepare' action.  Called when+            the load is completed, and when resources are unloaded when they+            are prepared but not yet loaded.+        */+        virtual void unprepareImpl(void) {}       /** Internal implementation of the meat of the 'load' action, only called if this           resource is not being loaded from a ManualResourceLoader.        */@@ -206,7 +217,22 @@         */         virtual ~Resource(); -        /** Loads the resource, if it is not already.+        /** Prepares the resource for load, if it is not already.  One can call prepare()+            before load(), but this is not required as load() will call prepare() +            itself, if needed.  When OGRE_THREAD_SUPPORT==1 both load() and prepare() +            are thread-safe.  When OGRE_THREAD_SUPPORT==2 however, only prepare() +            is thread-safe.  The reason for this function is to allow a background +            thread to do some of the loading work, without requiring the whole render+            system to be thread-safe.  The background thread would call+            prepare() while the main render loop would later call load().  So long as+            prepare() remains thread-safe, subclasses can arbitrarily split the work of+            loading a resource between load() and prepare().  It is best to try and+            do as much work in prepare(), however, since this will leave less work for+            the main render thread to do and thus increase FPS.+        */+        virtual void prepare();++     /** Loads the resource, if it is not already.       @remarks          If the resource is loaded from a file, loading is automatic. If not,          if for example this resource gained it's data from procedural calls@@ -267,12 +293,20 @@             return mHandle;         } +        /** Returns true if the Resource has been prepared, false otherwise.+        */+        virtual bool isPrepared(void) const +        { +         // No lock required to read this state since no modify+            return (mLoadingState.get() == LOADSTATE_PREPARED); +        }+         /** Returns true if the Resource has been loaded, false otherwise.         */         virtual bool isLoaded(void) const          {           // No lock required to read this state since no modify-            return (mLoadingState == LOADSTATE_LOADED); +            return (mLoadingState.get() == LOADSTATE_LOADED);          }        /** Returns whether the resource is currently in the process of@@ -280,14 +314,14 @@       */       virtual bool isLoading() const       {-         return (mLoadingState == LOADSTATE_LOADING);+         return (mLoadingState.get() == LOADSTATE_LOADING);       }        /** Returns the current loading state.       */       virtual LoadingState getLoadingState() const       {-         return mLoadingState;+         return mLoadingState.get();       }  diff -ruN ../cvs_fresh/OgreMain/src/OgreResource.cpp ./OgreMain/src/OgreResource.cpp--- ../cvs_fresh/OgreMain/src/OgreResource.cpp   2008-01-18 14:58:05.000000000 +0000+++ ./OgreMain/src/OgreResource.cpp   2008-03-27 23:14:30.730230126 +0000@@ -57,7 +57,53 @@       load(true);    }    //------------------------------------------------------------------------   void Resource::load(bool background)+   void Resource::prepare()+   {+        // quick check that avoids any synchronisation+        if (mLoadingState.get() != LOADSTATE_UNLOADED) return;++        // atomically do slower check to make absolutely sure,+        // and set the load state to PREPARING+      if (!mLoadingState.cas(LOADSTATE_UNLOADED,LOADSTATE_PREPARING))+         return;++      // Scope lock for actual loading+        try+      {++         OGRE_LOCK_AUTO_MUTEX++         if (mIsManual)+         {+            // not implemented, let load() do all the work+         }+         else+         {+            if (mGroup == ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME)+            {+               // Derive resource group+               changeGroupOwnership(+                  ResourceGroupManager::getSingleton()+                  .findGroupContainingResource(mName));+            }+            prepareImpl();+         }+      }+        catch (...)+        {+            mLoadingState.set(LOADSTATE_UNLOADED);+            throw;+        }++        mLoadingState.set(LOADSTATE_PREPARED);++      // Notify manager TODO: add a _notifyResourcePrepared ?+      //if(mCreator)+      //   mCreator->_notifyResourceLoaded(this);++   }++    void Resource::load(bool background)    {       // Early-out without lock (mitigate perf cost of ensuring loaded)       // Don't load if:@@ -65,30 +111,28 @@       // 2. Another thread is loading right now       // 3. We're marked for background loading and this is not the background       //    loading thread we're being called by-      if (mLoadingState != LOADSTATE_UNLOADED || (mIsBackgroundLoaded && !background))-         return; -      // Scope lock over load status-      {-         OGRE_LOCK_MUTEX(mLoadingStatusMutex)-         // Check again just in case status changed (since we didn't lock above)-         if (mLoadingState != LOADSTATE_UNLOADED || (mIsBackgroundLoaded && !background))-         {-            // no loading to be done-            return;-         }-         mLoadingState = LOADSTATE_LOADING;-      }+        if (mIsBackgroundLoaded && !background) return;++        // quick check that avoids any synchronisation+        LoadingState old = mLoadingState.get();+        if (old!=LOADSTATE_UNLOADED && old!=LOADSTATE_PREPARED) return;++        // atomically do slower check to make absolutely sure,+        // and set the load state to LOADING+        if (!mLoadingState.cas(old,LOADSTATE_LOADING)) return;        // Scope lock for actual loading         try       {           OGRE_LOCK_AUTO_MUTEX-         preLoadImpl();++           if (mIsManual)          {+                preLoadImpl();             // Load from manual loader             if (mLoader)             {@@ -103,9 +147,18 @@                   "loaded, but no manual loader was provided. This Resource "                   "will be lost if it has to be reloaded.");             }+                postLoadImpl();          }          else          {++                if (old==LOADSTATE_UNLOADED)+                    prepareImpl();++                preLoadImpl();++                old = LOADSTATE_PREPARED;+             if (mGroup == ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME)             {                // Derive resource group@@ -113,30 +166,26 @@                   ResourceGroupManager::getSingleton()                   .findGroupContainingResource(mName));             }+             loadImpl();++                postLoadImpl();          }+          // Calculate resource size          mSize = calculateSize(); -         postLoadImpl();       }         catch (...)         {             // Reset loading in-progress flag in case failed for some reason-            OGRE_LOCK_MUTEX(mLoadingStatusMutex)-            mLoadingState = LOADSTATE_UNLOADED;+            mLoadingState.set(old);             // Re-throw             throw;         } -      // Scope lock for loading progress-      {-         OGRE_LOCK_MUTEX(mLoadingStatusMutex)-      -         // Now loaded-         mLoadingState = LOADSTATE_LOADED;-         _dirtyState();-      }+        mLoadingState.set(LOADSTATE_LOADED);+        _dirtyState();        // Notify manager       if(mCreator)@@ -170,37 +219,25 @@    void Resource::unload(void)     {        // Early-out without lock (mitigate perf cost of ensuring unloaded)-      if (mLoadingState != LOADSTATE_LOADED)-         return;+        LoadingState old = mLoadingState.get();+        if (old!=LOADSTATE_LOADED && old!=LOADSTATE_PREPARED) return; -      // Scope lock for loading status-      {-         OGRE_LOCK_MUTEX(mLoadingStatusMutex)-         if (mLoadingState == LOADSTATE_LOADING)-         {-            OGRE_EXCEPT(Exception::ERR_INTERNAL_ERROR, -               "Cannot unload resource " + mName + " whilst loading is in progress!", -               "Resource::unload");-         }-         if (mLoadingState != LOADSTATE_LOADED)-            return; // nothing to do -         mLoadingState = LOADSTATE_UNLOADING;-      }+        if (!mLoadingState.cas(old,LOADSTATE_UNLOADING)) return;        // Scope lock for actual unload       {          OGRE_LOCK_AUTO_MUTEX-         preUnloadImpl();-         unloadImpl();-         postUnloadImpl();+            if (old==LOADSTATE_PREPARED) {+                unprepareImpl();+            } else {+                preUnloadImpl();+                unloadImpl();+                postUnloadImpl();+            }       } -      // Scope lock for loading status-      {-         OGRE_LOCK_MUTEX(mLoadingStatusMutex)-         mLoadingState = LOADSTATE_UNLOADED;-      }+        mLoadingState.set(LOADSTATE_UNLOADED);        // Notify manager       if(mCreator)@@ -211,7 +248,7 @@    void Resource::reload(void)     {        OGRE_LOCK_AUTO_MUTEX-      if (mLoadingState == LOADSTATE_LOADED)+      if (mLoadingState.get() == LOADSTATE_LOADED)       {          unload();          load();@@ -220,7 +257,6 @@    //-----------------------------------------------------------------------    void Resource::touch(void)     {-      OGRE_LOCK_AUTO_MUTEX         // make sure loaded         load();


Now we can get more concrete. The MeshManager has a couple of extra functions that allow apps to prepare a given mesh, following in the style of the ResourceManager functions I added. I changed the interface a bit though. I overloaded the createOrRetrieve function to allow me to avoid duplication in the load and prepare functions. I think it is better this way, but it's just a suggestion.

Code: Select all
diff -ruN ../cvs_fresh/OgreMain/include/OgreMeshManager.h ./OgreMain/include/OgreMeshManager.h--- ../cvs_fresh/OgreMain/include/OgreMeshManager.h   2008-03-02 19:34:46.000000000 +0000+++ ./OgreMain/include/OgreMeshManager.h   2008-03-25 21:53:36.555108363 +0000@@ -59,9 +59,56 @@         /** Initialises the manager, only to be called by OGRE internally. */         void _initialise(void); +        /** Create a new mesh, or retrieve an existing one with the same+            name if it already exists.+            @param vertexBufferUsage The usage flags with which the vertex buffer(s)+                will be created+            @param indexBufferUsage The usage flags with which the index buffer(s) created for +                this mesh will be created with.+            @param vertexBufferShadowed If true, the vertex buffers will be shadowed by system memory +                copies for faster read access+            @param indexBufferShadowed If true, the index buffers will be shadowed by system memory +                copies for faster read access+        @see ResourceManager::createOrRetrieve+        */+        ResourceCreateOrRetrieveResult createOrRetrieve(+            const String& name,+            const String& group,+            bool isManual=false, ManualResourceLoader* loader=0,+            const NameValuePairList* params=0,+         HardwareBuffer::Usage vertexBufferUsage = HardwareBuffer::HBU_STATIC_WRITE_ONLY, +         HardwareBuffer::Usage indexBufferUsage = HardwareBuffer::HBU_STATIC_WRITE_ONLY, +         bool vertexBufferShadowed = true, bool indexBufferShadowed = true);++        /** Prepares a mesh for loading from a file.  This does the IO in advance of the call to load().+            @note+                If the model has already been created (prepared or loaded), the existing instance+                will be returned.+            @remarks+                Ogre loads model files from it's own proprietary+                format called .mesh. This is because having a single file+                format is better for runtime performance, and we also have+                control over pre-processed data (such as+                collision boxes, LOD reductions etc).+         @param filename The name of the .mesh file+            @param groupName The name of the resource group to assign the mesh to +         @param vertexBufferUsage The usage flags with which the vertex buffer(s)+            will be created+         @param indexBufferUsage The usage flags with which the index buffer(s) created for +            this mesh will be created with.+         @param vertexBufferShadowed If true, the vertex buffers will be shadowed by system memory +                copies for faster read access+         @param indexBufferShadowed If true, the index buffers will be shadowed by system memory +                copies for faster read access+        */+        MeshPtr prepare( const String& filename, const String& groupName,+         HardwareBuffer::Usage vertexBufferUsage = HardwareBuffer::HBU_STATIC_WRITE_ONLY, +         HardwareBuffer::Usage indexBufferUsage = HardwareBuffer::HBU_STATIC_WRITE_ONLY, +         bool vertexBufferShadowed = true, bool indexBufferShadowed = true);+         /** Loads a mesh from a file, making it immediately available for use.             @note-                If the model has already been loaded, the existing instance+                If the model has already been created (prepared or loaded), the existing instance                 will be returned.             @remarks                 Ogre loads model files from it's own proprietary@@ -79,7 +126,6 @@                 copies for faster read access          @param indexBufferShadowed If true, the index buffers will be shadowed by system memory                  copies for faster read access-         @param priority The priority of this mesh in the resource system         */         MeshPtr load( const String& filename, const String& groupName,          HardwareBuffer::Usage vertexBufferUsage = HardwareBuffer::HBU_STATIC_WRITE_ONLY, diff -ruN ../cvs_fresh/OgreMain/src/OgreMeshManager.cpp ./OgreMain/src/OgreMeshManager.cpp--- ../cvs_fresh/OgreMain/src/OgreMeshManager.cpp   2008-03-02 19:34:46.000000000 +0000+++ ./OgreMain/src/OgreMeshManager.cpp   2008-03-25 21:53:37.055048490 +0000@@ -82,12 +82,16 @@       createPrefabSphere();     }     //------------------------------------------------------------------------    MeshPtr MeshManager::load( const String& filename, const String& groupName, +    MeshManager::ResourceCreateOrRetrieveResult MeshManager::createOrRetrieve(+        const String& name, const String& group,+        bool isManual, ManualResourceLoader* loader,+        const NameValuePairList* params,       HardwareBuffer::Usage vertexBufferUsage,        HardwareBuffer::Usage indexBufferUsage,        bool vertexBufferShadowed, bool indexBufferShadowed)     {-        ResourceCreateOrRetrieveResult res = createOrRetrieve(filename, groupName);+        ResourceCreateOrRetrieveResult res = +            ResourceManager::createOrRetrieve(name,group,isManual,loader,params);       MeshPtr pMesh = res.first;       // Was it created?         if (res.second)@@ -95,9 +99,32 @@          pMesh->setVertexBufferPolicy(vertexBufferUsage, vertexBufferShadowed);          pMesh->setIndexBufferPolicy(indexBufferUsage, indexBufferShadowed);         }+        return res;++    }+    //-----------------------------------------------------------------------+    MeshPtr MeshManager::prepare( const String& filename, const String& groupName, +      HardwareBuffer::Usage vertexBufferUsage, +      HardwareBuffer::Usage indexBufferUsage, +      bool vertexBufferShadowed, bool indexBufferShadowed)+    {+      MeshPtr pMesh = createOrRetrieve(filename,groupName,false,0,0,+                                         vertexBufferUsage,indexBufferUsage,+                                         vertexBufferShadowed,indexBufferShadowed).first;+      pMesh->prepare();+        return pMesh;+    }+    //-----------------------------------------------------------------------+    MeshPtr MeshManager::load( const String& filename, const String& groupName, +      HardwareBuffer::Usage vertexBufferUsage, +      HardwareBuffer::Usage indexBufferUsage, +      bool vertexBufferShadowed, bool indexBufferShadowed)+    {+      MeshPtr pMesh = createOrRetrieve(filename,groupName,false,0,0,+                                         vertexBufferUsage,indexBufferUsage,+                                         vertexBufferShadowed,indexBufferShadowed).first;       pMesh->load();         return pMesh;-     }     //-----------------------------------------------------------------------     MeshPtr MeshManager::createManual( const String& name, const String& groupName,


In the mesh class, we can see the prepareImpl function at work: It does the IO, cacheing the result in a MemoryDataStream. The MeshSerializer stuff I left in load(). The RenderSystem calls were too tightly integrated with the mesh parsing. Note how the datastream is cleaned up both in loadImpl and unprepareImpl. unprepareImpl is only called when going from a PREPARED state to an UNLOADED state. There is currently no way to go from LOADED back to PREPARED. Notice we are using the getVerbose() function to suppress the printing of the "Loading" message if requested by the app.

Code: Select all
diff -ruN ../cvs_fresh/OgreMain/include/OgreMesh.h ./OgreMain/include/OgreMesh.h--- ../cvs_fresh/OgreMain/include/OgreMesh.h   2008-02-11 11:10:36.000000000 +0000+++ ./OgreMain/include/OgreMesh.h   2008-03-25 21:53:36.555108363 +0000@@ -41,6 +41,7 @@ #include "OgreSkeleton.h" #include "OgreAnimationTrack.h" #include "OgrePose.h"+#include "OgreDataStream.h"   namespace Ogre {@@ -119,6 +120,9 @@             protected:++        DataStreamPtr mFreshFromDisk;+       SubMeshNameMap mSubMeshNameMap ;          /// Local bounding box volume@@ -172,6 +176,15 @@       PoseList mPoseList;  +        /** Loads the mesh from disk.  This call only performs IO, it+            does not parse the bytestream or check for any errors therein.+            It also does not set up submeshes, etc.  You have to call load()+            to do that.+         */+        void prepareImpl(void);+        /** Destroys data cached by prepareImpl.+         */+        void unprepareImpl(void);         /// @copydoc Resource::loadImpl         void loadImpl(void);       /// @copydoc Resource::postLoadImpldiff -ruN ../cvs_fresh/OgreMain/src/OgreMesh.cpp ./OgreMain/src/OgreMesh.cpp--- ../cvs_fresh/OgreMain/src/OgreMesh.cpp   2008-03-11 03:04:29.000000000 +0000+++ ./OgreMain/src/OgreMesh.cpp   2008-03-27 22:23:33.595877270 +0000@@ -198,17 +198,30 @@       }    }    //------------------------------------------------------------------------    void Mesh::loadImpl()+    void Mesh::prepareImpl()     {         // Load from specified 'name'-        MeshSerializer serializer;-      serializer.setListener(MeshManager::getSingleton().getListener());-        LogManager::getSingleton().logMessage("Mesh: Loading " + mName + ".");+        if (getCreator()->getVerbose())+            LogManager::getSingleton().logMessage("Mesh: Loading "+mName+"."); -        DataStreamPtr stream =+        mFreshFromDisk =             ResourceGroupManager::getSingleton().openResource(             mName, mGroup, true, this);-        serializer.importMesh(stream, this);+ +        // fully prebuffer into RAM+        mFreshFromDisk = DataStreamPtr(new MemoryDataStream(mFreshFromDisk));+    }+    //-----------------------------------------------------------------------+    void Mesh::unprepareImpl()+    {+        mFreshFromDisk.setNull();+    }+    void Mesh::loadImpl()+    {+        MeshSerializer serializer;+      serializer.setListener(MeshManager::getSingleton().getListener());+        serializer.importMesh(mFreshFromDisk, this);+        mFreshFromDisk.setNull();          /* check all submeshes to see if their materials should be            updated.  If the submesh has texture aliases that match those


Materials don't consume any video ram by themselves, but they do trigger texture loads that have to be controlled. I added a prepareImpl() implementation that calls down the techniques, passes, to the texture unit.

Code: Select all
diff -ruN ../cvs_fresh/OgreMain/include/OgreMaterial.h ./OgreMain/include/OgreMaterial.h--- ../cvs_fresh/OgreMain/include/OgreMaterial.h   2008-02-11 11:10:36.000000000 +0000+++ ./OgreMain/include/OgreMaterial.h   2008-03-25 21:53:36.555108363 +0000@@ -126,6 +126,14 @@        /** Overridden from Resource.       */+      void prepareImpl(void);++      /** Overridden from Resource.+      */+      void unprepareImpl(void);++      /** Overridden from Resource.+      */       void loadImpl(void);        /** Unloads the material, frees resources etc.diff -ruN ../cvs_fresh/OgreMain/include/OgrePass.h ./OgreMain/include/OgrePass.h--- ../cvs_fresh/OgreMain/include/OgrePass.h   2008-02-11 11:10:36.000000000 +0000+++ ./OgreMain/include/OgrePass.h   2008-03-25 21:53:36.555108363 +0000@@ -1259,6 +1259,10 @@       /** Internal method to adjust pass index. */       void _notifyIndex(unsigned short index); +      /** Internal method for preparing to load this pass. */+      void _prepare(void);+      /** Internal method for undoing the load preparartion for this pass. */+      void _unprepare(void);       /** Internal method for loading this pass. */       void _load(void);       /** Internal method for unloading this pass. */diff -ruN ../cvs_fresh/OgreMain/include/OgreTechnique.h ./OgreMain/include/OgreTechnique.h--- ../cvs_fresh/OgreMain/include/OgreTechnique.h   2008-03-21 14:33:50.000000000 +0000+++ ./OgreMain/include/OgreTechnique.h   2008-03-25 21:53:36.555108363 +0000@@ -218,6 +218,10 @@       */       bool isTransparentSortingEnabled(void) const; +        /** Internal prepare method, derived from call to Material::prepare. */+        void _prepare(void);+        /** Internal unprepare method, derived from call to Material::unprepare. */+        void _unprepare(void);         /** Internal load method, derived from call to Material::load. */         void _load(void);         /** Internal unload method, derived from call to Material::unload. */diff -ruN ../cvs_fresh/OgreMain/include/OgreTextureUnitState.h ./OgreMain/include/OgreTextureUnitState.h--- ../cvs_fresh/OgreMain/include/OgreTextureUnitState.h   2008-02-11 11:10:38.000000000 +0000+++ ./OgreMain/include/OgreTextureUnitState.h   2008-03-25 21:53:37.055048490 +0000@@ -1022,6 +1022,10 @@         /// Gets the parent Pass object         Pass* getParent(void) const { return mParent; } +      /** Internal method for preparing this object for load, as part of Material::prepare*/+      void _prepare(void);+      /** Internal method for undoing the preparation this object as part of Material::unprepare*/+      void _unprepare(void);       /** Internal method for loading this object as part of Material::load */       void _load(void);       /** Internal method for unloading this object as part of Material::unload */@@ -1166,6 +1170,8 @@         */         void createEffectController(TextureEffect& effect); +      /** Internal method for ensuring the texture for a given frame is prepared. */+      void ensurePrepared(size_t frame) const;       /** Internal method for ensuring the texture for a given frame is loaded. */       void ensureLoaded(size_t frame) const; 


I'm assuming it is ok to compile from a thread without a thread-safe rendersystem. The script compilers use thread-local-storage mechanisms from boost. The fun doesn't really start until we get to TextureUnitState.

Code: Select all
diff -ruN ../cvs_fresh/OgreMain/src/OgreMaterial.cpp ./OgreMain/src/OgreMaterial.cpp--- ../cvs_fresh/OgreMain/src/OgreMaterial.cpp   2007-06-04 18:47:31.000000000 +0100+++ ./OgreMain/src/OgreMaterial.cpp   2008-03-25 21:53:37.055048490 +0000@@ -118,7 +118,7 @@       //------------------------------------------------------------------------    void Material::loadImpl(void)+    void Material::prepareImpl(void)     {       // compile if required         if (mCompilationRequired)@@ -129,6 +129,29 @@         iend = mSupportedTechniques.end();         for (i = mSupportedTechniques.begin(); i != iend; ++i)         {+            (*i)->_prepare();+        }+    }+    //-----------------------------------------------------------------------+    void Material::unprepareImpl(void)+    {+        // Load all supported techniques+        Techniques::iterator i, iend;+        iend = mSupportedTechniques.end();+        for (i = mSupportedTechniques.begin(); i != iend; ++i)+        {+            (*i)->_unprepare();+        }+    }+    //-----------------------------------------------------------------------+    void Material::loadImpl(void)+    {++        // Load all supported techniques+        Techniques::iterator i, iend;+        iend = mSupportedTechniques.end();+        for (i = mSupportedTechniques.begin(); i != iend; ++i)+        {             (*i)->_load();         } diff -ruN ../cvs_fresh/OgreMain/src/OgreTechnique.cpp ./OgreMain/src/OgreTechnique.cpp--- ../cvs_fresh/OgreMain/src/OgreTechnique.cpp   2008-03-21 14:33:51.000000000 +0000+++ ./OgreMain/src/OgreTechnique.cpp   2008-03-25 21:53:37.055048490 +0000@@ -519,6 +519,37 @@         }     }     //-----------------------------------------------------------------------------+    void Technique::_prepare(void)+    {+      assert (mIsSupported && "This technique is not supported");+      // Load each pass+      Passes::iterator i, iend;+      iend = mPasses.end();+      for (i = mPasses.begin(); i != iend; ++i)+      {+         (*i)->_prepare();+      }++      IlluminationPassList::iterator il, ilend;+      ilend = mIlluminationPasses.end();+      for (il = mIlluminationPasses.begin(); il != ilend; ++il)+      {+         if((*il)->pass != (*il)->originalPass)+             (*il)->pass->_prepare();+      }+    }+    //-----------------------------------------------------------------------------+    void Technique::_unprepare(void)+    {+      // Unload each pass+      Passes::iterator i, iend;+      iend = mPasses.end();+      for (i = mPasses.begin(); i != iend; ++i)+      {+         (*i)->_unprepare();+      }+    }+    //-----------------------------------------------------------------------------     void Technique::_load(void)     {       assert (mIsSupported && "This technique is not supported");diff -ruN ../cvs_fresh/OgreMain/src/OgrePass.cpp ./OgreMain/src/OgrePass.cpp--- ../cvs_fresh/OgreMain/src/OgrePass.cpp   2008-02-05 12:57:03.000000000 +0000+++ ./OgreMain/src/OgrePass.cpp   2008-03-25 21:53:37.055048490 +0000@@ -1068,6 +1068,33 @@       }    }     //-----------------------------------------------------------------------+   void Pass::_prepare(void)+   {+      // We assume the Technique only calls this when the material is being+      // prepared++      // prepare each TextureUnitState+      TextureUnitStates::iterator i, iend;+      iend = mTextureUnitStates.end();+      for (i = mTextureUnitStates.begin(); i != iend; ++i)+      {+         (*i)->_prepare();+      }++   }+    //-----------------------------------------------------------------------+   void Pass::_unprepare(void)+   {+      // unprepare each TextureUnitState+      TextureUnitStates::iterator i, iend;+      iend = mTextureUnitStates.end();+      for (i = mTextureUnitStates.begin(); i != iend; ++i)+      {+         (*i)->_unprepare();+      }++   }+    //-----------------------------------------------------------------------    void Pass::_load(void)    {       // We assume the Technique only calls this when the material is being


OK, I add an ensurePrepared function that mostly is the same as the ensureLoaded version. I'm not sure what the "Unload first" bit is all about. The material should be unloaded when this is called, anyway.

Code: Select all
diff -ruN ../cvs_fresh/OgreMain/src/OgreTextureUnitState.cpp ./OgreMain/src/OgreTextureUnitState.cpp--- ../cvs_fresh/OgreMain/src/OgreTextureUnitState.cpp   2008-02-11 11:46:23.000000000 +0000+++ ./OgreMain/src/OgreTextureUnitState.cpp   2008-03-25 21:53:37.055048490 +0000@@ -966,10 +966,20 @@         addEffect(eff);     }     //------------------------------------------------------------------------    void TextureUnitState::_load(void)+    void TextureUnitState::_prepare(void)     {         // Unload first-        _unload();+        //_unload();++        // Load textures+      for (unsigned int i = 0; i < mFrames.size(); ++i)+      {+         ensurePrepared(i);+      }+    }+    //-----------------------------------------------------------------------+    void TextureUnitState::_load(void)+    {          // Load textures       for (unsigned int i = 0; i < mFrames.size(); ++i)@@ -1031,6 +1041,38 @@       mFramePtrs[frame] = texptr;    }     //-----------------------------------------------------------------------+   void TextureUnitState::ensurePrepared(size_t frame) const+   {+      if (!mFrames[frame].empty())+      {+         // Ensure texture is loaded, specified number of mipmaps and+         // priority+         if (mFramePtrs[frame].isNull())+         {+            try {+               mFramePtrs[frame] = +                  TextureManager::getSingleton().prepare(mFrames[frame], +                     mParent->getResourceGroup(), mTextureType, +                     mTextureSrcMipmaps, 1.0f, mIsAlpha, mDesiredFormat);+            }+            catch (Exception &e) {+               String msg;+               msg = msg + "Error loading texture " + mFrames[frame]  + +                  ". Texture layer will be blank. Loading the texture "+                  "failed with the following exception: " +                  + e.getFullDescription();+               LogManager::getSingleton().logMessage(msg);+               mTextureLoadFailed = true;+            }   +         }+         else+         {+            // Just ensure existing pointer is prepared+            mFramePtrs[frame]->prepare();+         }+      }+   }+    //-----------------------------------------------------------------------    void TextureUnitState::ensureLoaded(size_t frame) const    {       if (!mFrames[frame].empty())@@ -1219,6 +1261,17 @@    }     //-----------------------------------------------------------------------+    void TextureUnitState::_unprepare(void)+    {+        // Unreference textures+        std::vector<TexturePtr>::iterator ti, tiend;+        tiend = mFramePtrs.end();+        for (ti = mFramePtrs.begin(); ti != tiend; ++ti)+        {+            ti->setNull();+        }+    }+   //-----------------------------------------------------------------------     void TextureUnitState::_unload(void)     {         // Destroy animation controller


I seem to have hit the maximum limit for a thread, so will continue in a followup...
Last edited by sparkprime on Fri Mar 28, 2008 4:40 am, edited 7 times in total.
User avatar
sparkprime
Gargoyle
 
Posts: 1079
Kudos: 16
Joined: 07 May 2007
Location: Ossining, New York
  • Website
Top

Postbysparkprime » Fri Mar 28, 2008 2:50 am

There isn't much to do in the Texture class itself, but I did add some things to the Texture manager, as I did with the Mesh and Material managers.

Code: Select all
diff -ruN ../cvs_fresh/OgreMain/include/OgreTextureManager.h ./OgreMain/include/OgreTextureManager.h--- ../cvs_fresh/OgreMain/include/OgreTextureManager.h   2008-02-11 11:10:38.000000000 +0000+++ ./OgreMain/include/OgreTextureManager.h   2008-03-27 22:08:43.202498807 +0000@@ -59,6 +59,71 @@         TextureManager(void);         virtual ~TextureManager(); +        /** Create a new texture, or retrieve an existing one with the same+            name if it already exists.+            @param+                texType The type of texture to load/create, defaults to normal 2D textures+            @param+                numMipmaps The number of pre-filtered mipmaps to generate. If left to MIP_DEFAULT then+                the TextureManager's default number of mipmaps will be used (see setDefaultNumMipmaps())+                If set to MIP_UNLIMITED mipmaps will be generated until the lowest possible+                level, 1x1x1.+            @param+                gamma The gamma adjustment factor to apply to this texture (brightening/darkening)+            @param +                isAlpha Only applicable to greyscale images. If true, specifies that+                the image should be loaded into an alpha texture rather than a+                single channel colour texture - useful for fixed-function systems.+            @param +                desiredFormat The format you would like to have used instead of+                the format being based on the contents of the texture+         @param hwGammaCorrection Pass 'true' to enable hardware gamma correction+            (sRGB) on this texture. The hardware will convert from gamma space+            to linear space when reading from this texture. Only applicable for +            8-bits per channel textures, will be ignored for other types. Has the advantage+            over pre-applied gamma that the texture precision is maintained.+            @see ResourceManager::createOrRetrieve+        */+        virtual ResourceCreateOrRetrieveResult createOrRetrieve(+            const String &name, const String& group, bool isManual = false,+            ManualResourceLoader* loader = 0, const NameValuePairList* createParams = 0,+            TextureType texType = TEX_TYPE_2D, int numMipmaps = MIP_DEFAULT, +            Real gamma = 1.0f, bool isAlpha = false,+            PixelFormat desiredFormat = PF_UNKNOWN, bool hwGammaCorrection = false);++        /** Prepares to loads a texture from a file.+            @param+                name The file to load, or a String identifier in some cases+            @param+                group The name of the resource group to assign the texture to+            @param+                texType The type of texture to load/create, defaults to normal 2D textures+            @param+                numMipmaps The number of pre-filtered mipmaps to generate. If left to MIP_DEFAULT then+                the TextureManager's default number of mipmaps will be used (see setDefaultNumMipmaps())+                If set to MIP_UNLIMITED mipmaps will be generated until the lowest possible+                level, 1x1x1.+            @param+                gamma The gamma adjustment factor to apply to this texture (brightening/darkening)+            @param +                isAlpha Only applicable to greyscale images. If true, specifies that+                the image should be loaded into an alpha texture rather than a+                single channel colour texture - useful for fixed-function systems.+            @param +                desiredFormat The format you would like to have used instead of+                the format being based on the contents of the texture+         @param hwGammaCorrection Pass 'true' to enable hardware gamma correction+            (sRGB) on this texture. The hardware will convert from gamma space+            to linear space when reading from this texture. Only applicable for +            8-bits per channel textures, will be ignored for other types. Has the advantage+            over pre-applied gamma that the texture precision is maintained.+        */+        virtual TexturePtr prepare( +            const String& name, const String& group, +            TextureType texType = TEX_TYPE_2D, int numMipmaps = MIP_DEFAULT, +            Real gamma = 1.0f, bool isAlpha = false,+            PixelFormat desiredFormat = PF_UNKNOWN, bool hwGammaCorrection = false);+         /** Loads a texture from a file.             @param                 name The file to load, or a String identifier in some casesdiff -ruN ../cvs_fresh/OgreMain/src/OgreTextureManager.cpp ./OgreMain/src/OgreTextureManager.cpp--- ../cvs_fresh/OgreMain/src/OgreTextureManager.cpp   2007-12-08 18:37:27.000000000 +0000+++ ./OgreMain/src/OgreTextureManager.cpp   2008-03-27 22:08:43.202498807 +0000@@ -60,15 +60,17 @@      }     //------------------------------------------------------------------------    TexturePtr TextureManager::load(const String &name, const String& group,-        TextureType texType, int numMipmaps, Real gamma, bool isAlpha, PixelFormat desiredFormat, -      bool hwGamma)+    TextureManager::ResourceCreateOrRetrieveResult TextureManager::createOrRetrieve(+            const String &name, const String& group, bool isManual, ManualResourceLoader* loader,+            const NameValuePairList* createParams, TextureType texType, int numMipmaps, Real gamma,+            bool isAlpha, PixelFormat desiredFormat, bool hwGamma)     {-      ResourceCreateOrRetrieveResult res = createOrRetrieve(name, group);-        TexturePtr tex = res.first;+      ResourceCreateOrRetrieveResult res =+            Ogre::ResourceManager::createOrRetrieve(name, group, isManual, loader, createParams);       // Was it created?       if(res.second)         {+            TexturePtr tex = res.first;             tex->setTextureType(texType);             tex->setNumMipmaps((numMipmaps == MIP_DEFAULT)? mDefaultNumMipmaps :             static_cast<size_t>(numMipmaps));@@ -77,8 +79,28 @@             tex->setFormat(desiredFormat);          tex->setHardwareGammaEnabled(hwGamma);         }+        return res;+    }+    //-----------------------------------------------------------------------+    TexturePtr TextureManager::prepare(const String &name, const String& group, TextureType texType,+                                       int numMipmaps, Real gamma, bool isAlpha,+                                       PixelFormat desiredFormat, bool hwGamma)+    {+      ResourceCreateOrRetrieveResult res =+            createOrRetrieve(name,group,false,0,0,texType,numMipmaps,gamma,isAlpha,desiredFormat,hwGamma);+        TexturePtr tex = res.first;+      tex->prepare();+        return tex;+    }+    //-----------------------------------------------------------------------+    TexturePtr TextureManager::load(const String &name, const String& group, TextureType texType,+                                    int numMipmaps, Real gamma, bool isAlpha, PixelFormat desiredFormat,+                                    bool hwGamma)+    {+      ResourceCreateOrRetrieveResult res =+            createOrRetrieve(name,group,false,0,0,texType,numMipmaps,gamma,isAlpha,desiredFormat,hwGamma);+        TexturePtr tex = res.first;       tex->load();-         return tex;     } 


This code that interfaces directly with the resource loading system, so needs updating to use the AtomicScalar instance. I also suppress the logging output using getVerbose().

Code: Select all
diff -ruN ../cvs_fresh/OgreMain/src/OgreTexture.cpp ./OgreMain/src/OgreTexture.cpp--- ../cvs_fresh/OgreMain/src/OgreTexture.cpp   2008-01-18 21:58:33.000000000 +0000+++ ./OgreMain/src/OgreTexture.cpp   2008-03-27 23:14:53.727473653 +0000@@ -93,16 +93,11 @@    //--------------------------------------------------------------------------        void Texture::loadImage( const Image &img )    {-      // Scope lock over load status-      {-         OGRE_LOCK_MUTEX(mLoadingStatusMutex)-         if (mLoadingState != LOADSTATE_UNLOADED)-         {-            // no loading to be done-            return;-         }-         mLoadingState = LOADSTATE_LOADING;-      }++        LoadingState old = mLoadingState.get();+        if (old!=LOADSTATE_UNLOADED && old!=LOADSTATE_PREPARED) return;++        if (!mLoadingState.cas(old,LOADSTATE_LOADING)) return;        // Scope lock for actual loading       try@@ -116,19 +111,12 @@       catch (...)       {          // Reset loading in-progress flag in case failed for some reason-         OGRE_LOCK_MUTEX(mLoadingStatusMutex)-         mLoadingState = LOADSTATE_UNLOADED;+         mLoadingState.set(old);          // Re-throw          throw;       } -      // Scope lock for loading progress-      {-         OGRE_LOCK_MUTEX(mLoadingStatusMutex)--         // Now loaded-         mLoadingState = LOADSTATE_LOADED;-      }+        mLoadingState.set(LOADSTATE_LOADED);        // Notify manager       if(mCreator)@@ -257,38 +245,40 @@       if(faces > getNumFaces())          faces = getNumFaces();       -      // Say what we're doing-      StringUtil::StrStreamType str;-      str << "Texture: " << mName << ": Loading " << faces << " faces"-         << "(" << PixelUtil::getFormatName(images[0]->getFormat()) << "," <<-         images[0]->getWidth() << "x" << images[0]->getHeight() << "x" << images[0]->getDepth() <<-         ") with ";-      if (!(mMipmapsHardwareGenerated && mNumMipmaps == 0))-         str << mNumMipmaps;-      if(mUsage & TU_AUTOMIPMAP)-      {-         if (mMipmapsHardwareGenerated)-            str << " hardware";+        if (TextureManager::getSingleton().getVerbose()) {+            // Say what we're doing+            StringUtil::StrStreamType str;+            str << "Texture: " << mName << ": Loading " << faces << " faces"+                << "(" << PixelUtil::getFormatName(images[0]->getFormat()) << "," <<+                images[0]->getWidth() << "x" << images[0]->getHeight() << "x" << images[0]->getDepth() <<+                ") with ";+            if (!(mMipmapsHardwareGenerated && mNumMipmaps == 0))+                str << mNumMipmaps;+            if(mUsage & TU_AUTOMIPMAP)+            {+                if (mMipmapsHardwareGenerated)+                    str << " hardware"; -         str << " generated mipmaps";-      }-      else-      {-         str << " custom mipmaps";-      }-       if(multiImage)-         str << " from multiple Images.";-      else-         str << " from Image.";-      // Scoped-      {-         // Print data about first destination surface-         HardwarePixelBufferSharedPtr buf = getBuffer(0, 0); -         str << " Internal format is " << PixelUtil::getFormatName(buf->getFormat()) << -         "," << buf->getWidth() << "x" << buf->getHeight() << "x" << buf->getDepth() << ".";-      }-      LogManager::getSingleton().logMessage( -            LML_NORMAL, str.str());+                str << " generated mipmaps";+            }+            else+            {+                str << " custom mipmaps";+            }+            if(multiImage)+                str << " from multiple Images.";+            else+                str << " from Image.";+            // Scoped+            {+                // Print data about first destination surface+                HardwarePixelBufferSharedPtr buf = getBuffer(0, 0); +                str << " Internal format is " << PixelUtil::getFormatName(buf->getFormat()) << +                "," << buf->getWidth() << "x" << buf->getHeight() << "x" << buf->getDepth() << ".";+            }+            LogManager::getSingleton().logMessage( +                    LML_NORMAL, str.str());+        }              // Main loading loop         // imageMips == 0 if the image has no custom mipmaps, otherwise contains the number of custom mips


I didn't touch the D3D9 textures except to suppress the logging if the app requests it.

Code: Select all
diff -ruN ../cvs_fresh/RenderSystems/Direct3D9/src/OgreD3D9Texture.cpp ./RenderSystems/Direct3D9/src/OgreD3D9Texture.cpp--- ../cvs_fresh/RenderSystems/Direct3D9/src/OgreD3D9Texture.cpp   2008-02-28 11:28:13.000000000 +0000+++ ./RenderSystems/Direct3D9/src/OgreD3D9Texture.cpp   2008-03-27 22:10:35.189097455 +0000@@ -1018,6 +1018,7 @@       mSrcDepth = depth;         mSrcFormat = format;       // say to the world what we are doing+        if (!TextureManager::getSingleton().getVerbose()) return;       switch (this->getTextureType())       {       case TEX_TYPE_1D:



Finally, I implemented prepareImpl for GLTexture. It uses Image objects (sometimes 1 of them, but for cube maps, 6 of them), rather than a MemoryDataStream, to cache the loaded data in RAM between calls to prepareImpl() and loadImpl(). I pulled some duplicated code out into a little function to make managing it easier. The diff doesn't work very well here, because of changes to whitespace. The changes are actually prety minimal.


Code: Select all
diff -ruN ../cvs_fresh/RenderSystems/GL/include/OgreGLTexture.h ./RenderSystems/GL/include/OgreGLTexture.h--- ../cvs_fresh/RenderSystems/GL/include/OgreGLTexture.h   2007-02-03 21:59:16.000000000 +0000+++ ./RenderSystems/GL/include/OgreGLTexture.h   2008-03-25 21:53:37.055048490 +0000@@ -63,6 +63,10 @@     protected:       /// @copydoc Texture::createInternalResourcesImpl       void createInternalResourcesImpl(void);+        /// @copydoc Resource::prepareImpl+        void prepareImpl(void);+        /// @copydoc Resource::unprepareImpl+        void unprepareImpl(void);         /// @copydoc Resource::loadImpl         void loadImpl(void);         /// @copydoc Resource::freeInternalResourcesImpl@@ -74,6 +78,14 @@          actually allocate the buffer       */       void _createSurfaceList();++        /** Vector of pointers to images that were pulled from disk by+            prepareLoad  but have yet to be pushed into texture memory+            by loadImpl.  Images should be deleted by loadImpl.+        */+        ConstImagePtrList mLoadImagePtrs;++     private:         GLuint mTextureID;         GLSupport& mGLSupport;diff -ruN ../cvs_fresh/RenderSystems/GL/src/OgreGLTexture.cpp ./RenderSystems/GL/src/OgreGLTexture.cpp--- ../cvs_fresh/RenderSystems/GL/src/OgreGLTexture.cpp   2008-01-18 21:58:33.000000000 +0000+++ ./RenderSystems/GL/src/OgreGLTexture.cpp   2008-03-26 02:12:48.689038996 +0000@@ -235,95 +235,100 @@       // This already does everything neccessary         createInternalResources();     }++    static inline void do_image_io(const String &name, const String &group,+                                   const String &ext,+                                   Image *img, ConstImagePtrList &imagePtrs,+                                   Resource *r)+    {+        DataStreamPtr dstream = +            ResourceGroupManager::getSingleton().openResource(+                name, group, true, r);+                +        img->load(dstream, ext);+        imagePtrs.push_back(img);+    }+    -    void GLTexture::loadImpl()+    void GLTexture::prepareImpl()     {-        if( mUsage & TU_RENDERTARGET )+        if( mUsage & TU_RENDERTARGET ) return;++        String baseName, ext;+        size_t pos = mName.find_last_of(".");+        baseName = mName.substr(0, pos);+        if( pos != String::npos )+            ext = mName.substr(pos+1);++        if(mTextureType == TEX_TYPE_1D || mTextureType == TEX_TYPE_2D || +            mTextureType == TEX_TYPE_3D)         {-            createRenderTexture();+            Image *img = new Image;++            do_image_io(mName, mGroup, ext, img, mLoadImagePtrs, this);++            // If this is a cube map, set the texture type flag accordingly.+            if (img->hasFlag(IF_CUBEMAP))+                mTextureType = TEX_TYPE_CUBE_MAP;+            // If this is a volumetric texture set the texture type flag accordingly.+            if(img->getDepth() > 1)+                mTextureType = TEX_TYPE_3D;+         }-        else+        else if (mTextureType == TEX_TYPE_CUBE_MAP)         {-         String baseName, ext;-         size_t pos = mName.find_last_of(".");-         baseName = mName.substr(0, pos);-         if( pos != String::npos )-            ext = mName.substr(pos+1);--         if(mTextureType == TEX_TYPE_1D || mTextureType == TEX_TYPE_2D || -                mTextureType == TEX_TYPE_3D)+            if(getSourceFileType() == "dds")             {-                Image img;-               // find & load resource data intro stream to allow resource-            // group changes if required-            DataStreamPtr dstream = -               ResourceGroupManager::getSingleton().openResource(-                  mName, mGroup, true, this);--                img.load(dstream, ext);--            // If this is a cube map, set the texture type flag accordingly.-                if (img.hasFlag(IF_CUBEMAP))-               mTextureType = TEX_TYPE_CUBE_MAP;-            // If this is a volumetric texture set the texture type flag accordingly.-            if(img.getDepth() > 1)-               mTextureType = TEX_TYPE_3D;--            // Call internal _loadImages, not loadImage since that's external and -            // will determine load status etc again-            ConstImagePtrList imagePtrs;-            imagePtrs.push_back(&img);-            _loadImages( imagePtrs );+                // XX HACK there should be a better way to specify whether +                // all faces are in the same file or not+                do_image_io(mName, mGroup, ext, new Image(), mLoadImagePtrs, this);             }-            else if (mTextureType == TEX_TYPE_CUBE_MAP)+            else             {-            if(getSourceFileType() == "dds")-            {-               // XX HACK there should be a better way to specify whether -               // all faces are in the same file or not-               Image img;-                  // find & load resource data intro stream to allow resource-               // group changes if required-               DataStreamPtr dstream = -                  ResourceGroupManager::getSingleton().openResource(-                     mName, mGroup, true, this);--                   img.load(dstream, ext);-               // Call internal _loadImages, not loadImage since that's external and -               // will determine load status etc again-               ConstImagePtrList imagePtrs;-               imagePtrs.push_back(&img);-               _loadImages( imagePtrs );-            }-            else-            {-               std::vector<Image> images(6);-               ConstImagePtrList imagePtrs;-               static const String suffixes[6] = {"_rt", "_lf", "_up", "_dn", "_fr", "_bk"};-   -               for(size_t i = 0; i < 6; i++)-               {-                  String fullName = baseName + suffixes[i];-                  if (!ext.empty())-                     fullName = fullName + "." + ext;-                     // find & load resource data intro stream to allow resource-                  // group changes if required-                  DataStreamPtr dstream = -                     ResourceGroupManager::getSingleton().openResource(-                        fullName, mGroup, true, this);-   -                  images[i].load(dstream, ext);-                  imagePtrs.push_back(&images[i]);-               }-   -               _loadImages( imagePtrs );-            }+                std::vector<Image> images(6);+                ConstImagePtrList imagePtrs;+                static const String suffixes[6] = {"_rt", "_lf", "_up", "_dn", "_fr", "_bk"};++                for(size_t i = 0; i < 6; i++)+                {+                    String fullName = baseName + suffixes[i];+                    if (!ext.empty())+                        fullName = fullName + "." + ext;+                    // find & load resource data intro stream to allow resource+                    // group changes if required+                    do_image_io(fullName,mGroup,ext,new Image(),mLoadImagePtrs,this);+                }             }-            else-                OGRE_EXCEPT( Exception::ERR_NOT_IMPLEMENTED, "**** Unknown texture type ****", "GLTexture::load" );         }+        else+            OGRE_EXCEPT( Exception::ERR_NOT_IMPLEMENTED, "**** Unknown texture type ****", "GLTexture::prepare" );     }    +    void GLTexture::unprepareImpl()+    {+        mLoadImagePtrs.clear();+    }++    void GLTexture::loadImpl()+    {+        if( mUsage & TU_RENDERTARGET )+        {+            createRenderTexture();+            return;+        }++        // Call internal _loadImages, not loadImage since that's external and +        // will determine load status etc again+        _loadImages(mLoadImagePtrs);++        for (ConstImagePtrList::iterator i=mLoadImagePtrs.begin(),+                                         j=mLoadImagePtrs.end() ; i!=j ; ++i) {+            delete (*i);+        }++        mLoadImagePtrs.clear();+    }+    //*************************************************************************          void GLTexture::freeInternalResourcesImpl()



I will put these patches into the tracker so people can get at them directly from there. It is probably best not to use these directly as they may have errors due to my editting into neat sections.
 
http://www.ogre3d.org/forums/viewtopic.php?p=278043&sid=8e59d16a735bc81596f21464b208c9e1#p278043
原创粉丝点击