#pragma once #include "AftrGlobals.h" #include "AftrOpenGLIncludes.h" #include "ModelDataSharedID.h" #include "Vector.h" #include #include #include #include namespace Aftr { class ModelDataShared; /** This class manages all multiplicities of all ModelDataShared objects that were loaded from a file format such as .3ds or .wrl. */ enum class RENDER_HW_MODE : unsigned char { rhmVA_VERTEX_ARRAY, rhmVBO_VERTEX_BUFFER_OBJECT, rhmNUM_RENDER_HW_MODE }; std::ostream& operator<<( std::ostream& out, const RENDER_HW_MODE& rhm ); class ManagerModelMultiplicity { public: ///The func_resume *will always occur* on the *main thread*, so it can mutate OpenGL! ///This is the lower-level function that sends VBO data to GPU memory. using func_send_to_GPU_on_main_thread = std::function; ///The func_resume *will always occur* on the *main thread*, so it can mutate OpenGL! ///This stores the user's lambda which may access the internals of the MDS to set ///things like skin or shader values after the MDS has been sent to the GPU and is ///fully loaded. using func_resume_Model_onCreate_after_MDS_completed = std::function; static void init(); static void shutdown(); /** Loads and manages all multiplicities of a model's geometry, textures, colors, material properties, shaders, etc. The in passed modelData is overwritten by this method. When this method returns, modelData points to the valid ModelDataShared object populated from the corresponding file. */ static void loadModelDataShared( ModelDataShared** modelData, const std::string& modelFileName, const Vector& scale, const MESH_SHADING_TYPE& shadingType ); /** Same as loadModelDataShared but runs asynchronously. If this is a unique filename / scale pair, the filename will be loaded and parsed asychronously via the parser, such as ASSIMP. Once the ModelDataShared has been parsed and loaded into CPU-side memory, a callable continuation will be scheduled on the main thread (which owns the OpenGL context). The next time the GLView::updateWorld() is called within the main thread, the CPU-side data will be streamed to OpenGL (this take significantly less time than the parsing and texture loading. Any subsequent loads to the same filename / scale will return immediately with the shared ModelDataShared instance already shared across AftrBurner. Using this will reduce loadMap times but will cause WOs to stream into existance *after* the 3DVW begins rendering frames, so the user may need to ensure the model data is only accessed after it is loaded. */ static void loadModelDataSharedAsync( ModelDataShared** modelData, const std::string& modelFileName, const Vector& scale, const MESH_SHADING_TYPE& shadingType, func_resume_Model_onCreate_after_MDS_completed func ); /// This method can only be called from the *MAIN THREAD* or else OpenGL will enter into undefined behavior. /// When async loading 3D models from a file, worker threads are dispatched to parse the 3D model, build the /// OpenGL indexed geometry structures, and prepare them to be sent to the GPU. However, OpenGL function /// calls may only be called from the main thread which created the OpenGL context; ie, the MAIN THREAD. /// As a result, the main loop (on the main thread) may call this function when the user wants to /// perform the final step of loading a 3D model from file. If any worker threads have finished parsing /// and building the data structures, then this method simply sends those data structures to OpenGL. /// Internally, this method calls on completion handlers for each model. This includes the /// func_resume_Model_onCreate_after_MDS_completed which correspond to the lambdas enqueued via /// Model::upon_async_model_loaded( ...completion_handler_func...). /// This is how one ensures each Model's upon_async_model_loaded(...) is *always* run on the main /// thread. static int loadPendingAsyncModelDataSharedInstances(); ///This method is called automatically within a ModelDataShared destructor. Internally, this calls /// destroyModelSahredData(...) to free the memory of the static void unRegisterOneModelDataSharedInstance( const ModelDataSharedID& id ); /// This method is used by unRegisterOneModelDataSharedInstance static void destroyModelSharedData( ModelDataShared* s ); static RENDER_HW_MODE getRenderMode() { return hardwareRenderMode; } static void sendModelDataSharedServerSide( ModelDataShared* m ); /// Queries if the in passed ModelDataShared is fully initialized on the GPU and /// safe to use. For example, if this was a request to load a duplicate MDS, this method /// would return true iff the main thread had already initialized this MDS on the GPU. /// If so, this method returns true and the completion handler lamdas for this MDS copy /// could be safely called. If false is returned, the async parser may still be reading /// from file, so dispatching any lambdas on the MDS's completion handlers would result /// in undefined behavior. /// This method MUST ONLY BE CALLED FROM THE MAIN THREAD; otherwise OpenGL may enter /// into undefined behavior! This method is intended to be called in /// ManagerModelMultiplicity::sendPendingAsyncModelDataSharedToGfxMemory() static bool has_sent_ModelDataShared_to_ServerSide( ModelDataShared const* mds ); /// Typically called by an MGL that has begun to asychronously create its own ModelDataShared via a /// call similar to: /// std::future< ManagerModelMultiplicity::func_send_to_GPU_on_main_thread > fut = std::async( std::launch::async, /// do_async_terrain_loading_on_worker_thread, std::move( resumeOnCreateFunc ) ); /// This completes everything except actually sending the ModelDataShared to the GPU, which must be done on the main /// thread which created the OpenGL context. /// This method consumes the pending future and enqueues it until it is ready. Upon becoming ready, the callable /// inside the future is invoked on the main thread to transfer the data to the GPU. static void async_sendModelDataSharedServerSide( std::future&& fut ); protected: static ModelDataShared* getModelDataSharedFromFile( const std::string& modelFileName, const Vector& scale, const MESH_SHADING_TYPE& shadingType ); static func_send_to_GPU_on_main_thread getModelDataSharedFromFileAsync( ModelDataShared** modelData, const std::string modelFileName, const Vector scale, const MESH_SHADING_TYPE shadingType, std::map< ModelDataSharedID, ModelDataShared* >::iterator iter, ManagerModelMultiplicity::func_resume_Model_onCreate_after_MDS_completed resumeOnCreateFunc ); /// After the first unique instance of a ModelDataShared has been fully uploaded to the /// GPU and is rendering normally, any subsequent requests to load another copy of that /// MDS instance cannot "hook" into the notification that occurs when the first instance completes (because that has already happened). /// MUTATES only ModelManagerMultiplicit::duplicate_MDS_queued via a calls to /// complete_construction_of_duplicate_MDS_now_that_async_MDS_has_loaded(). static int loadPendingAsyncModelDataSharedInstances_after_first_instance_has_completed_notification_callbacks(); /// Invokes callbacks if the corresponding MDS of the in passed ID is fully ready on the GPU/OpenGL-side. /// After invoking all callbacks, the in passed ID and all awaiting functors are removed from /// duplicate_MDS_queue since they have been fully processed. This means a call to this method /// MAY invalid any iterators to duplicate_MDS_queue if it returns a non-zero value indicating some MDSes /// were created / processed to completion. static int complete_construction_of_duplicate_MDS_now_that_async_MDS_has_loaded( const ModelDataSharedID& id_of_ready_MDS ); static RENDER_HW_MODE hardwareRenderMode; ///< If VBOs are supported, they are used; otherwise, VAs are used. static std::map< ModelDataSharedID, ModelDataShared* > models; //These callables are guaranteed to execute on the main thread which created the OpenGL context. //They are called at the bottom of GLView::updateWorld(...) via //a call to ManagerModelMultiplicity::sendPendingAsyncModelDataSharedToGfxMemory(); static std::vector< std::future< func_send_to_GPU_on_main_thread > > parsed_MDS_ready_to_send_to_GPU_via_func; static std::mutex parsed_MDS_ready_to_send_to_GPU_via_func_MUTEX; //For duplicate models being loaded while an async thread is concurrently creating the 3D ModelDataShared, //we enqueue the ModelDataSharedID and its corresponding completion lambda. Once the async thread completes //loading and the main OpenGL thread has sent the data to the GPU, we then do the following: //All duplicate models that have a matching ModelDataSharedID (but are not yet loaded) will have their //corresponding lambdas invoked from this container. The lambda will create a new ModelDataShared with //modifiable ModelMeshes / Skins -- exactly providing the behavior expected from a ModelDataShared. //By only touching this on the main thread, no mutual exclusion is necessary. static std::unordered_multimap< ModelDataSharedID, func_resume_Model_onCreate_after_MDS_completed > duplicate_MDS_queued; }; } //namespace Aftr