/* ----------------------------------------------------------------------------- This source file is part of OGRE (Object-oriented Graphics Rendering Engine) For the latest info, see http://www.ogre3d.org/ Copyright (c) 2000-2012 Torus Knot Software Ltd Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ----------------------------------------------------------------------------- */ #ifndef __InstanceManager_H__ #define __InstanceManager_H__ #include "OgrePrerequisites.h" #include "OgreMesh.h" #include "OgreRenderOperation.h" namespace Ogre { /** \addtogroup Core * @{ */ /** \addtogroup Scene * @{ */ /** This is the main starting point for the new instancing system. Each InstanceManager can control one technique and one mesh, but it can manage multiple materials at the same time. @see SceneManager::createInstanceManager, which creates this InstanceManager. Each one must have a unique name. It's wasteless to create two InstanceManagers with the same mesh reference, instancing technique and instances per batch count. This class takes care of managing batches automatically, so that more are created when needed, and reuse existing ones as much as posible; thus the user doesn't have to worry of managing all those low level issues. @see InstanceBatch & @see InstanceEntity for more information. @remarks Design discussion webpage: http://www.ogre3d.org/forums/viewtopic.php?f=4&t=59902 @author Matias N. Goldberg ("dark_sylinc") @version 1.0 */ class _OgreExport InstanceManager : public FactoryAlloc { public: enum InstancingTechnique { ShaderBased, //Any SM 2.0+ @See InstanceBatchShader TextureVTF, //Needs Vertex Texture Fetch & SM 3.0+ @See InstanceBatchVTF HWInstancingBasic, //Needs SM 3.0+ and HW instancing support @See InstanceBatchHW HWInstancingVTF, //Needs SM 3.0+, HW instancing support & VTF @See InstanceBatchHW_VTF InstancingTechniquesCount }; /** Values to be used in setSetting() & BatchSettings::setting */ enum BatchSettingId { /// Makes all batches from same material cast shadows CAST_SHADOWS = 0, /// Makes each batch to display it's bounding box. Useful for debugging or profiling SHOW_BOUNDINGBOX, NUM_SETTINGS }; private: struct BatchSettings { //These are all per material bool setting[NUM_SETTINGS]; BatchSettings() { setting[CAST_SHADOWS] = true; setting[SHOW_BOUNDINGBOX] = false; } }; typedef vector::type InstanceBatchVec; //vec[batchN] = Batch typedef map::type InstanceBatchMap; //map[materialName] = Vec typedef map::type BatchSettingsMap; const String mName; //Not the name of the mesh MeshPtr mMeshReference; InstanceBatchMap mInstanceBatches; size_t mIdCount; InstanceBatchVec mDirtyBatches; RenderOperation mSharedRenderOperation; size_t mInstancesPerBatch; InstancingTechnique mInstancingTechnique; uint16 mInstancingFlags; //@see InstanceManagerFlags unsigned short mSubMeshIdx; BatchSettingsMap mBatchSettings; SceneManager* mSceneManager; size_t mMaxLookupTableInstances; /** Finds a batch with at least one free instanced entity we can use. If none found, creates one. */ inline InstanceBatch* getFreeBatch( const String &materialName ); /** Called when batches are fully exhausted (can't return more instances) so a new batch is created. For the first time use, it can take big build time. It takes care of getting the render operation which will be shared by further batches, which decreases their build time, and prevents GPU RAM from skyrocketing. @param materialName The material name, to know where to put this batch in the map @param firstTime True if this is the first time it is called @return The created InstancedManager for convenience */ InstanceBatch* buildNewBatch( const String &materialName, bool firstTime ); /** @see defragmentBatches overload, this takes care of an array of batches for a specific material */ void defragmentBatches( bool optimizeCull, vector::type &entities, InstanceBatchVec &fragmentedBatches ); /** @see setSetting. This function helps it by setting the given parameter to all batches in container. */ void applySettingToBatches( BatchSettingId id, bool value, const InstanceBatchVec &container ); /** Called when we you use a mesh which has shared vertices, the function creates separate vertex/index buffers and also recreates the bone assignments. */ void unshareVertices(const Ogre::MeshPtr &mesh); public: InstanceManager( const String &customName, SceneManager *sceneManager, const String &meshName, const String &groupName, InstancingTechnique instancingTechnique, uint16 instancingFlags, size_t instancesPerBatch, unsigned short subMeshIdx, bool useBoneMatrixLookup = false); virtual ~InstanceManager(); const String& getName() const { return mName; } /** Raises an exception if trying to change it after creating the first InstancedEntity @remarks The actual value may be less if the technique doesn't support having so much @see getMaxOrBestNumInstancesPerBatches for the usefulness of this function @param instancesPerBatch New instances per batch number */ void setInstancesPerBatch( size_t instancesPerBatch ); /** Sets the size of the lookup table for techniques supporting bone lookup table. Raises an exception if trying to change it after creating the first InstancedEntity. Setting this value below the number of unique (non-sharing) entity instance animations will produce a crash during runtime. Setting this value above will increase memory consumption and reduce framerate. @remarks The value should be as close but not below the actual value. @param maxLookupTableInstances New size of the lookup table */ void setMaxLookupTableInstances( size_t maxLookupTableInstances ); /** Calculates the maximum (or the best amount, depending on flags) of instances per batch given the suggested size for the technique this manager was created for. @remarks This is done automatically when creating an instanced entity, but this function in conjunction with @see setInstancesPerBatch allows more flexible control over the amount of instances per batch @param materialName Name of the material to base on @param suggestedSize Suggested amount of instances per batch @param flags @see InstanceManagerFlags @return The max/best amount of instances per batch given the suggested size and flags */ size_t getMaxOrBestNumInstancesPerBatch( String materialName, size_t suggestedSize, uint16 flags ); /** @copydoc SceneManager::createInstancedEntity */ InstancedEntity* createInstancedEntity( const String &materialName ); /** This function can be useful to improve CPU speed after having too many instances created, which where now removed, thus freeing many batches with zero used Instanced Entities However the batches aren't automatically removed from memory until the InstanceManager is destroyed, or this function is called. This function removes those batches which are completely unused (only wasting memory). */ void cleanupEmptyBatches(void); /** After creating many entities (which turns in many batches) and then removing entities that are in the middle of these batches, there might be many batches with many free entities. Worst case scenario, there could be left one batch per entity. Imagine there can be 80 entities per batch, there are 80 batches, making a total of 6400 entities. Then 6320 of those entities are removed in a very specific way, which leads to having 80 batches, 80 entities, and GPU vertex shader still needs to process 6400! This is called fragmentation. This function reparents the InstancedEntities to fewer batches, in this case leaving only one batch with 80 entities @remarks This function takes time. Make sure to call this only when you're sure there's too much of fragmentation and you won't be creating more InstancedEntities soon Also in many cases cleanupEmptyBatches() ought to be enough Defragmentation is done per material Static batches won't be defragmented. If you want to degragment them, set them to dynamic again, and switch back to static after calling this function. @param optimizeCulling When true, entities close together will be reorganized in the same batch for more efficient CPU culling. This can take more CPU time. You want this to be false if you now you're entities are moving very randomly which tends them to get separated and spread all over the scene (which nullifies any CPU culling) */ void defragmentBatches( bool optimizeCulling ); /** Applies a setting for all batches using the same material_ existing ones and those that will be created in the future. @par For example setSetting( BatchSetting::CAST_SHADOWS, false ) disables shadow casting for all instanced entities (@see MovableObject::setCastShadow) @par For example setSetting( BatchSetting::SHOW_BOUNDINGBOX, true, "MyMat" ) will display the bounding box of the batch (not individual InstancedEntities) from all batches using material "MyMat" @note If the material name hasn't been used, the settings are still stored This allows setting up batches before they get even created. @param id Setting Id to setup, @see BatchSettings::BatchSettingId @param enabled Boolean value. It's meaning depends on the id. @param materialName When Blank, the setting is applied to all existing materials */ void setSetting( BatchSettingId id, bool value, const String &materialName = StringUtil::BLANK ); /// If settings for the given material didn't exist, default value is returned bool getSetting( BatchSettingId id, const String &materialName ) const; /** Returns true if settings were already created for the given material name. If false is returned, it means getSetting will return default settings. */ bool hasSettings( const String &materialName ) const { return mBatchSettings.find( materialName ) != mBatchSettings.end(); } /** @copydoc InstanceBatch::setStaticAndUpdate */ void setBatchesAsStaticAndUpdate( bool bStatic ); /** Called by an InstanceBatch when it requests their bounds to be updated for proper culling @param dirtyBatch The batch which is dirty, usually same as caller. */ void _addDirtyBatch( InstanceBatch *dirtyBatch ); /** Called by SceneManager when we told it we have at least one dirty batch */ void _updateDirtyBatches(void); typedef ConstMapIterator InstanceBatchMapIterator; typedef ConstVectorIterator InstanceBatchIterator; /// Get non-updateable iterator over instance batches per material InstanceBatchMapIterator getInstanceBatchMapIterator(void) const { return InstanceBatchMapIterator( mInstanceBatches.begin(), mInstanceBatches.end() ); } /** Get non-updateable iterator over instance batches for given material @remarks Each InstanceBatch pointer may be modified for low level usage (i.e. setCustomParameter), but there's no synchronization mechanism when multithreading or creating more instances, that's up to the user. */ InstanceBatchIterator getInstanceBatchIterator( const String &materialName ) const { InstanceBatchMap::const_iterator it = mInstanceBatches.find( materialName ); return InstanceBatchIterator( it->second.begin(), it->second.end() ); } }; } #endif