MultithreadingExample.cpp

<< Click to Display Table of Contents >>

Navigation:  Simplygon 7.1 examples >

MultithreadingExample.cpp

///////////////////////////////////////////////////////////////////////////
//
//  System:    Simplygon
//  File:      MultithreadingExample.cpp
//  Language:  C++
//
//  Copyright (c) 2015 Donya Labs AB. 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[] )
    {
    // Initiate
    InitExample();
    // Run the example code
    RunExample();
    // Deinitialize the SDK
    DeinitExample();
    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