Show / Hide Table of Contents
    ///////////////////////////////////////////////////////////////////////////
    //
    //  System:    Simplygon
    //  File:      MultithreadingExample.cpp
    //  Language:  C++
    //
    //  Copyright (c) 2019 Microsoft. All rights reserved.
    //
    ///////////////////////////////////////////////////////////////////////////
    //
    //  #Description#
    //
    //  This example demonstrates how to use Simplygon in a multithreaded 
    //  environment, to utilize all cores of a multicore system. The example
    //  spawns 100 threads, but keeps the number of concurrent threads at or
    //  below 12.
    //
    ///////////////////////////////////////////////////////////////////////////
    
    #include "../Common/Example.h"
    
    /////////////////////////////////////////////////////////////////////
    // Main settings
    
    // total number of threads to spawn
    const int num_threads = 100;
    
    // number of threads to run concurrently
    const int num_concurrent_threads = 12;
    
    /////////////////////////////////////////////////////////////////////
    // Thread-handling declarations
    
    rhandle ReCreateCriticalSection();
    void ReEnterCriticalSection( rhandle cs );
    void ReLeaveCriticalSection( rhandle cs );
    void ReDeleteCriticalSection( rhandle cs );
    rhandle ReCreateThread( void( *start_address )(void *), void * arg );
    void ReSleep( int milliseconds );
    
    /////////////////////////////////////////////////////////////////////
    // Example main code
    
    int spawned_threads = 0;
    rid threadids[num_threads];
    bool threaddoneflags[num_threads];
    IGeometryDataCollection *src_geoms;
    rhandle common_lock;
    
    
    void log_string( int threadid, const char *str )
        {
        printf( "Thread (%d): %s\n", threadid, str );
        }
    
    // This function copies from the source geometries list a
    // geometry that will be reduced within the thread processing. 
    // To keep the system thread safe, the function must lock the source 
    // geometries list using a critical section, during the copy procedure. 
    // Apart from this lock, no other thread collision has 
    // to be accounted for, as all other data is thread-local.
    spGeometryData ThreadSafeCopyGeometryFromSourceGeometriesList( int threadid )
        {
        // shared object: src_geoms, lock the common lock
        ReEnterCriticalSection( common_lock );
    
        // the geometry copied is based on the index of the geometry
        int index = (threadid % src_geoms->GetItemCount());
    
        // get the source geometry, copy to new object
        spGeometryData threadgeom = sg->CreateGeometryData();
        threadgeom->DeepCopy( src_geoms->GetGeometryData( src_geoms->GetItem( index ) ), true );
    
        // Remove material ids. For this example the materials are not interesting,
        // but if they were important, the materials from the material table of the
        // original scene would have to be inserted into the material table of the
        // scene created for export below. This because material ids in a scene
        // geometry must have a corresponding material in the scene material table.
        threadgeom->RemoveMaterialIds();
    
        // done with the shared object
        ReLeaveCriticalSection( common_lock );
    
        return threadgeom;
        }
    
    // This function runs the Simplygon reduction processing on the thread-local 
    // geometry data object. 
    void RunReductionProcessing( spGeometryData geom, float triangle_ratio )
        {
        // Create the reduction processor. Set the geometry that is to be processed
        spReductionProcessor reducer = sg->CreateReductionProcessor();
        spScene tempScene = sg->CreateScene();
        tempScene->GetRootNode()->CreateChildMesh( geom );
        reducer->SetScene( tempScene );
    
        ///////////////////////////////////////////////////
        //
        // Set the Repair Settings. Current settings will mean that all visual gaps will remain in the geometry and thus
        // hinder the reduction on geometries that contains gaps, holes and tjunctions.
        spRepairSettings repair_settings = reducer->GetRepairSettings();
        // Only vertices that actually share the same position will be welded together
        repair_settings->SetWeldDist( 0.0f );
        // Only t-junctions with no actual visual distance will be fixed.
        repair_settings->SetTjuncDist( 0.0f );
    
        ///////////////////////////////////////////////////
        //
        // Set the Reduction Settings.
        spReductionSettings reduction_settings = reducer->GetReductionSettings();
    
        // Will reduce to 1/4 of the original trianglecount.
        reduction_settings->SetTriangleRatio( triangle_ratio );
    
        ///////////////////////////////////////////////////
        //
        // Set the Normal Calculation Settings.
        spNormalCalculationSettings normal_settings = reducer->GetNormalCalculationSettings();
    
        // Will completely recalculate the normals.
        normal_settings->SetReplaceNormals( true );
        normal_settings->SetHardEdgeAngleInRadians( 3.14159f * 90.f / 180.0f );
    
        // Run the process
        reducer->RunProcessing();
        }
    
    // This is the per-thread processing function. It copies 
    // a geometry from the source geometry list, processes it, and
    // stores the result into a wavefront .obj file.
    void ThreadProc( void *arg )
        {
        int threadid = *((int*)arg);
    
        //log_string(threadid,"Thread started...\n");
    
        // Copy the geometry, lock the common lock, as the src_geoms object is shared
        spGeometryData threadgeom = ThreadSafeCopyGeometryFromSourceGeometriesList( threadid );
    
        //log_string(threadid,"Run reduction...\n");
    
        // Run reduction.
        // Reduce the geometry to 25% of the original number of triangles.
        RunReductionProcessing( threadgeom, 0.25f );
    
        //log_string(threadid,"Reduction done...\n");
    
        // Create scene for export
        spScene scene = sg->CreateScene();
        spSceneMesh sceneMesh = sg->CreateSceneMesh();
        sceneMesh->SetGeometry( threadgeom );
        scene->GetRootNode()->AddChild( sceneMesh );
    
        // Store in wavefront file
        spWavefrontExporter exp = sg->CreateWavefrontExporter();
    
        std::string path = GetExecutablePath() + "output_thread_" + std::to_string( threadid ) + ".obj";
    
        exp->SetExportFilePath( path.c_str() );
        exp->SetScene( scene );
        exp->RunExport();
        exp = NULL;
    
        //log_string(threadid,"Thread done...\n");
        threaddoneflags[threadid] = true;
    
        }
    
    // This function spawns a new thread using the ReCreateThread function.
    void SpawnNewThread()
        {
        printf( "Main thread: Spawning thread #%d.\n", spawned_threads );
    
        threaddoneflags[spawned_threads] = false;
        threadids[spawned_threads] = spawned_threads;
        ReCreateThread( &ThreadProc, &threadids[spawned_threads] );
        ++spawned_threads;
        }
    
    // This function keeps track of the number of running threads.
    int CountRunningThreads()
        {
        int RunningThreads = 0;
        for( int i = 0; i < spawned_threads; ++i )
            {
            if( !threaddoneflags[i] )
                {
                ++RunningThreads;
                }
            }
        return RunningThreads;
        }
    
    // This function checks if all threads have been spawned and 
    // have also completed.
    bool AreAllThreadsDone()
        {
        // if not all have been started, we are not done
        if( spawned_threads < num_threads )
            {
            return false;
            }
    
        // if none is running, we are done
        if( CountRunningThreads() == 0 )
            return true;
        return false;
        }
    
    // This is the example main thread function, that only spawns new threads
    // as long as there are still threads to be run, and the number of
    // concurrent threads are less than "num_concurrent_threads", by default 4.
    void RunExample()
        {
        common_lock = ReCreateCriticalSection();
    
        printf( "Loading Wavefront OBJ...\n" );
        spWavefrontImporter is = sg->CreateWavefrontImporter();
    
        std::string assetPath = GetAssetPath();
        is->SetImportFilePath( (assetPath + "testmodel.obj").c_str() );
        is->SetExtractGroups( true );
        if( !is->RunImport() )
            {
            ExitWithError( "Could not open input test file" );
            }
    
        spScene scene = is->GetScene();
        int selectionSetAllSceneMeshesID = scene->SelectNodes( "ISceneMesh" );
        spSelectionSet selectionSetAllSceneMeshes = scene->GetSelectionSetTable()->GetSelectionSet( selectionSetAllSceneMeshesID );
        spGeometryDataCollection geoms = sg->CreateGeometryDataCollection();
    
        for( uint i = 0; i < selectionSetAllSceneMeshes->GetItemCount(); ++i )
            {
            // Get the node from the selection set and cast it to an ISceneMesh
            spSceneMesh sceneMesh = Cast<ISceneMesh>( scene->GetNodeByGUID( selectionSetAllSceneMeshes->GetItem( i ) ) );
    
            // Get the geometry from the scene mesh and insert into geometry list for processing
            spGeometryData geom = sceneMesh->GetGeometry();
            geoms->AddGeometryData( geom );
            }
    
        src_geoms = geoms;
    
        while( !AreAllThreadsDone() )
            {
            while( (spawned_threads < num_threads) &&
                   (CountRunningThreads() < num_concurrent_threads) )
                {
                SpawnNewThread();
                }
    
            ReSleep( 10 );
            }
    
        printf( "All threads done.\n" );
    
        ReDeleteCriticalSection( common_lock );
        }
    
    /////////////////////////////////////////////////////////////////////
    // Main function with startup and shutdown code
    
    int main( int argc, char* argv[] )
        {
        try
        {
            // Initiate
            InitExample();
    
            // Set per-process core limit, so the processes aren't competing for internal multithreading
            sg->SetGlobalSetting("LogicalCoreLimit", 1);
    
            // Run the example code
            RunExample();
    
            // Deinitialize the SDK
            DeinitExample();
        }
        catch (const std::exception& ex)
        {
            std::cerr << ex.what() << std::endl;
            return -1;
        }
    
        return 0;
        }
    
    /////////////////////////////////////////////////////////////////////
    // Platform-specific thread handling
    
    #ifdef _WIN32
    
    // Windows implementation
    
    rhandle ReCreateCriticalSection()
        {
        CRITICAL_SECTION *p = new CRITICAL_SECTION;
        InitializeCriticalSection( p );
        return (rhandle)p;
        }
    
    void ReEnterCriticalSection( rhandle cs )
        {
        CRITICAL_SECTION *p = (CRITICAL_SECTION*)cs;
        EnterCriticalSection( p );
        }
    
    void ReLeaveCriticalSection( rhandle cs )
        {
        CRITICAL_SECTION *p = (CRITICAL_SECTION*)cs;
        LeaveCriticalSection( p );
        }
    
    void ReDeleteCriticalSection( rhandle cs )
        {
        CRITICAL_SECTION *p = (CRITICAL_SECTION*)cs;
        DeleteCriticalSection( p );
        delete p;
        }
    
    rhandle ReCreateThread( void( *start_address )(void *), void * arg )
        {
        return (rhandle)_beginthread( start_address, 0, arg );
        }
    
    void ReSleep( int milliseconds )
        {
        Sleep( milliseconds );
        }
    
    
    #elif defined(__linux__) || defined(__APPLE__)
    
    // Linux implementation
    
    rhandle ReCreateCriticalSection()
        {
        pthread_mutex_t *mutex = new pthread_mutex_t(); // Mutex
        pthread_mutexattr_t mutexattr;   // Mutex attribute variable
        pthread_mutexattr_init( &mutexattr );
    
        // Set the mutex as a recursive mutex
    #if defined(__linux__)
        pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE_NP );
    #elif defined(__APPLE__)
        pthread_mutexattr_settype( &mutexattr, PTHREAD_MUTEX_RECURSIVE );
    #endif
    
        // create the mutex with the attributes set
        pthread_mutex_init( mutex, &mutexattr );
    
        // After initializing the mutex, the thread attribute can be destroyed
        pthread_mutexattr_destroy( &mutexattr );
    
        return (rhandle)mutex;
        }
    
    void ReEnterCriticalSection( rhandle cs )
        {
        pthread_mutex_t *mutex = (pthread_mutex_t*)cs;
        pthread_mutex_lock( mutex );
        }
    
    void ReLeaveCriticalSection( rhandle cs )
        {
        pthread_mutex_t *mutex = (pthread_mutex_t*)cs;
        pthread_mutex_unlock( mutex );
        }
    
    void ReDeleteCriticalSection( rhandle cs )
        {
        pthread_mutex_t *mutex = (pthread_mutex_t*)cs;
        pthread_mutex_destroy( mutex );
        delete mutex;
        }
    
    typedef void( *p_thread_start )(void*);
    
    struct thread_info
        {
        p_thread_start start_address;
        void *arg;
        };
    
    void *thread_entry_func( void *args )
        {
        thread_info *info = (thread_info*)args;
        (*info->start_address)(info->arg);
        delete info;
        return NULL;
        }
    
    rhandle ReCreateThread( void( *start_address )(void *), void * arg )
        {
        thread_info *info = new thread_info();
        info->start_address = start_address;
        info->arg = arg;
    
        pthread_t threadid;
        pthread_attr_t threadAttr;
    
        // initialize the thread attribute
        // Set the stack size of the thread
        // Set thread to detached state. 
        pthread_attr_init( &threadAttr );
        pthread_attr_setstacksize( &threadAttr, 120 * 1024 );
        pthread_attr_setdetachstate( &threadAttr, PTHREAD_CREATE_DETACHED );
    
        // Create the threads
        pthread_create( &threadid, &threadAttr, &thread_entry_func, info );
    
        // Destroy the thread attributes
        pthread_attr_destroy( &threadAttr );
    
        return (rhandle)threadid;
        }
    
    void ReSleep( int milliseconds )
        {
        usleep( milliseconds * 1000 );
        }
    
    #endif
    
    Back to top Terms of Use | Privacy and cookies | Trademarks | Copyright © 2019 Microsoft